diff --git a/README.md b/README.md index 0b259c518..8aac9b9fe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ Marello ======================== -[![GitHub release](https://img.shields.io/github/release/marellocommerce/marello.svg)](https://github.com/marellocommerce/marello/releases) What is Marello? ----------- @@ -25,7 +24,7 @@ Use as dependency in composer Run unit tests -------------- -Please make sure you have at least phpunit 6.5 or above. +Please make sure you have at least phpunit 7.5 or above. To run unit tests of any bundles: ```bash diff --git a/composer.json b/composer.json index dd3e7bd8d..d57ff137c 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "OSL-3.0", "authors": [ { - "name": "Madia B.V.", + "name": "Marello B.V.", "homepage": "https://www.marello.com" } ], @@ -20,16 +20,16 @@ } }, "require": { - "php": ">=7.1.26", - "oro/platform": "~3.1.1", - "oro/platform-serialised-fields": "~3.1.1", - "oro/calendar-bundle": "~3.1.1" + "oro/platform": "4.1.*", + "oro/platform-serialised-fields": "4.1.*", + "oro/calendar-bundle": "4.1.*", + "mpdf/mpdf": "^7.1" }, "minimum-stability": "dev", "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "3.0-dev" } }, "autoload": { diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 000000000..1ca997513 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,10 @@ + + + PSR2 ruleset with Marello configuration + + */marellocommerce/*/*/*/*/*/Tests/* + */marellocommerce/*/*/*/*/*/Migrations/Schema/* + + + + diff --git a/src/Marello/Bundle/AddressBundle/Entity/MarelloAddress.php b/src/Marello/Bundle/AddressBundle/Entity/MarelloAddress.php index 40f8c01c8..d0cfc166c 100644 --- a/src/Marello/Bundle/AddressBundle/Entity/MarelloAddress.php +++ b/src/Marello/Bundle/AddressBundle/Entity/MarelloAddress.php @@ -4,11 +4,13 @@ use Doctrine\ORM\Mapping as ORM; use Marello\Bundle\AddressBundle\Model\ExtendMarelloAddress; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Oro\Bundle\EntityConfigBundle\Metadata\Annotation as Oro; /** - * @ORM\Entity + * @ORM\Entity( + * repositoryClass="Marello\Bundle\AddressBundle\Entity\Repository\MarelloAddressRepository" + * ) * @ORM\HasLifecycleCallbacks() * @ORM\Table(name="marello_address") * @ORM\AssociationOverrides({ @@ -62,7 +64,7 @@ class MarelloAddress extends ExtendMarelloAddress protected $company; /** - * @ORM\ManyToOne(targetEntity="Marello\Bundle\OrderBundle\Entity\Customer", inversedBy="addresses", + * @ORM\ManyToOne(targetEntity="Marello\Bundle\CustomerBundle\Entity\Customer", inversedBy="addresses", * cascade={"persist"}) * @ORM\JoinColumn(onDelete="SET NULL", nullable=true) * diff --git a/src/Marello/Bundle/AddressBundle/Entity/Repository/MarelloAddressRepository.php b/src/Marello/Bundle/AddressBundle/Entity/Repository/MarelloAddressRepository.php new file mode 100644 index 000000000..2699b5b51 --- /dev/null +++ b/src/Marello/Bundle/AddressBundle/Entity/Repository/MarelloAddressRepository.php @@ -0,0 +1,74 @@ +aclHelper = $aclHelper; + } + + /** + * @param MarelloAddress $address + * + * @return MarelloEnterpriseAddress[] + */ + public function findByAddressParts(MarelloAddress $address) + { + $qb = $this->createQueryBuilder('addr'); + $qb + ->innerJoin('addr.country', 'country') + ->andWhere($qb->expr()->eq('country.iso2Code', ':countryIso2')) + ->andWhere($qb->expr()->eq('addr.city', ':city')) + ->andWhere($qb->expr()->eq('addr.street', ':street')) + ->setParameter('countryIso2', $address->getCountryIso2()) + ->setParameter('city', $address->getCity()) + ->setParameter('street', $address->getStreet()); + + if ($address->getRegion() === null) { + $qb->andWhere('addr.region IS NULL'); + } else { + $qb + ->innerJoin('addr.region', 'region') + ->andWhere($qb->expr()->eq('region.code', ':regionCode')) + ->setParameter('regionCode', $address->getRegionCode()); + } + if ($address->getRegionText() === null) { + $qb->andWhere('addr.regionText IS NULL'); + } else { + $qb + ->andWhere($qb->expr()->eq('addr.regionText', ':regionText')) + ->setParameter('regionText', $address->getRegionText()); + } + if ($address->getStreet2() === null) { + $qb->andWhere('addr.street2 IS NULL'); + } else { + $qb + ->andWhere($qb->expr()->eq('addr.street2', ':street2')) + ->setParameter('street2', $address->getStreet2()); + } + if ($address->getPostalCode() === null) { + $qb->andWhere('addr.postalCode IS NULL'); + } else { + $qb + ->andWhere($qb->expr()->eq('addr.postalCode', ':postalCode')) + ->setParameter('postalCode', $address->getPostalCode()); + } + + return $this->aclHelper->apply($qb)->getResult(); + } +} diff --git a/src/Marello/Bundle/AddressBundle/Migrations/Schema/v1_1/MarelloAddressBundle.php b/src/Marello/Bundle/AddressBundle/Migrations/Schema/v1_1/MarelloAddressBundle.php index 943e3edbe..9cf8775eb 100644 --- a/src/Marello/Bundle/AddressBundle/Migrations/Schema/v1_1/MarelloAddressBundle.php +++ b/src/Marello/Bundle/AddressBundle/Migrations/Schema/v1_1/MarelloAddressBundle.php @@ -25,6 +25,7 @@ public function up(Schema $schema, QueryBag $queries) * Update marello_address table * * @param Schema $schema + * @param QueryBag $queries */ protected function updateDatetimeMarelloAddressTable(Schema $schema, QueryBag $queries) { diff --git a/src/Marello/Bundle/AddressBundle/Resources/config/jsmodules.yml b/src/Marello/Bundle/AddressBundle/Resources/config/jsmodules.yml new file mode 100644 index 000000000..831b0ef12 --- /dev/null +++ b/src/Marello/Bundle/AddressBundle/Resources/config/jsmodules.yml @@ -0,0 +1,4 @@ +dynamic-imports: + marelloaddress: + - marelloaddress/js/address + - marelloaddress/js/app/components/address-component \ No newline at end of file diff --git a/src/Marello/Bundle/AddressBundle/Resources/config/oro/placeholders.yml b/src/Marello/Bundle/AddressBundle/Resources/config/oro/placeholders.yml new file mode 100644 index 000000000..61c7980c0 --- /dev/null +++ b/src/Marello/Bundle/AddressBundle/Resources/config/oro/placeholders.yml @@ -0,0 +1,10 @@ +placeholders: + placeholders: + marello_address_map: + items: + marello_address_item_map: + order: 100 + + items: + marello_address_item_map: + template: MarelloAddressBundle:Address:addressMap.html.twig \ No newline at end of file diff --git a/src/Marello/Bundle/AddressBundle/Resources/config/requirejs.yml b/src/Marello/Bundle/AddressBundle/Resources/config/requirejs.yml deleted file mode 100644 index 07f022bca..000000000 --- a/src/Marello/Bundle/AddressBundle/Resources/config/requirejs.yml +++ /dev/null @@ -1,3 +0,0 @@ -config: - paths: - 'marelloaddress/js/address': 'bundles/marelloaddress/js/address.js' \ No newline at end of file diff --git a/src/Marello/Bundle/AddressBundle/Resources/config/services.yml b/src/Marello/Bundle/AddressBundle/Resources/config/services.yml index f018d5435..d51785a5d 100644 --- a/src/Marello/Bundle/AddressBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/AddressBundle/Resources/config/services.yml @@ -1,4 +1,3 @@ - services: marello_address.formatter.address: class: Marello\Bundle\AddressBundle\Formatter\AddressFormatter @@ -13,4 +12,12 @@ services: arguments: - '@marello_address.formatter.address' tags: - - { name: twig.extension } \ No newline at end of file + - { name: twig.extension } + + marello_address.repository.marelloaddress: + class: 'Marello\Bundle\AddressBundle\Entity\Repository\MarelloAddressRepository' + parent: oro_entity.abstract_repository + arguments: + - 'Marello\Bundle\AddressBundle\Entity\MarelloAddress' + calls: + - [setAclHelper, ['@oro_security.acl_helper']] \ No newline at end of file diff --git a/src/Marello/Bundle/AddressBundle/Resources/public/js/address.js b/src/Marello/Bundle/AddressBundle/Resources/public/js/address.js index dd2ce1080..9a8931ddb 100644 --- a/src/Marello/Bundle/AddressBundle/Resources/public/js/address.js +++ b/src/Marello/Bundle/AddressBundle/Resources/public/js/address.js @@ -1,32 +1,28 @@ define([ 'underscore', - 'backbone', + 'oroui/js/app/views/base/view', 'orotranslation/js/translator', 'oroui/js/mediator', 'oroui/js/messenger', - 'oro/dialog-widget', - 'oroaddress/js/address/view' + 'oro/dialog-widget' ], function( _, - Backbone, + BaseView, __, mediator, messenger, - DialogWidget, - AddressView + DialogWidget ) { 'use strict'; - var $ = Backbone.$; - /** * @export marelloaddress/js/address * @class marelloaddress.Address * @extends Backbone.View */ - return Backbone.View.extend({ + const AddressView = BaseView.extend({ options: { - 'addressUpdateUrl': null, + 'addressUpdateUrl': null }, initialize: function(options) { @@ -77,6 +73,7 @@ define([ } }, this) ); + this.addressEditDialog.on('formSave', _.bind(function() { this.addressEditDialog.remove(); messenger.notificationFlashMessage('success', __('Address saved')); @@ -87,6 +84,8 @@ define([ reloadAddress: function() { this.getAddressWidget().render(); - }, + } }); + + return AddressView; }); diff --git a/src/Marello/Bundle/AddressBundle/Resources/public/js/app/components/address-component.js b/src/Marello/Bundle/AddressBundle/Resources/public/js/app/components/address-component.js new file mode 100644 index 000000000..b58418efa --- /dev/null +++ b/src/Marello/Bundle/AddressBundle/Resources/public/js/app/components/address-component.js @@ -0,0 +1,48 @@ +define(function(require) { + 'use strict'; + + const BaseComponent = require('oroui/js/app/components/base/component'); + const widgetManager = require('oroui/js/widget-manager'); + const Address = require('marelloaddress/js/address'); + const routing = require('routing'); + const _ = require('underscore'); + + const AddressWidgetComponent = BaseComponent.extend({ + optionNames: BaseComponent.prototype.optionNames.concat([ + 'wid', 'addressCreateUrl', 'addressUpdateRoute', 'addressDeleteRoute' + ]), + + options: null, + + /** + * @inheritDoc + */ + constructor: function AddressWidgetComponent(options) { + AddressWidgetComponent.__super__.constructor.call(this, options); + }, + + /** + * @inheritDoc + */ + initialize: function(options) { + this.options = options; + AddressWidgetComponent.__super__.initialize.call(this, options); + widgetManager.getWidgetInstance(this.wid, this._initializeAddress.bind(this)); + }, + + _initializeAddress: function(widget) { + const options = this.options; + return new Address({ + el: this.options.el, + addressId: this.options.addressId, + addressUpdateUrl: function() { + return routing.generate(options.addressUpdateRoute.route, {'id': options.addressUpdateRoute.id }) + }, + widget: widget + }); + } + + }); + + return AddressWidgetComponent; +}); \ No newline at end of file diff --git a/src/Marello/Bundle/OrderBundle/Resources/views/Customer/customerAddressMap.html.twig b/src/Marello/Bundle/AddressBundle/Resources/views/Address/addressMap.html.twig similarity index 90% rename from src/Marello/Bundle/OrderBundle/Resources/views/Customer/customerAddressMap.html.twig rename to src/Marello/Bundle/AddressBundle/Resources/views/Address/addressMap.html.twig index 7ec3ab3eb..47a54a647 100644 --- a/src/Marello/Bundle/OrderBundle/Resources/views/Customer/customerAddressMap.html.twig +++ b/src/Marello/Bundle/AddressBundle/Resources/views/Address/addressMap.html.twig @@ -1,5 +1,5 @@ {% set apiKey = oro_config_value('oro_google_integration.google_api_key') %} -{% if apiKey is not empty%} +{% if apiKey is not empty and address is not null %}
labels = new ArrayCollection(); + } + + /** + * @return Collection|LocalizedFallbackValue[] + */ + public function getLabels() + { + return $this->labels; + } + + /** + * @param LocalizedFallbackValue $label + * + * @return $this + */ + public function addLabel(LocalizedFallbackValue $label) + { + if (!$this->labels->contains($label)) { + $this->labels->add($label); + } + + return $this; + } + + /** + * @param LocalizedFallbackValue $label + * + * @return $this + */ + public function removeLabel(LocalizedFallbackValue $label) + { + if ($this->labels->contains($label)) { + $this->labels->removeElement($label); + } + + return $this; + } + + /** + * @return ParameterBag + */ + public function getSettingsBag() + { + if (null === $this->settings) { + $this->settings = new ParameterBag( + [ + 'labels' => $this->getLabels()->toArray(), + ] + ); + } + + return $this->settings; + } +} diff --git a/src/Marello/Bundle/BankTransferBundle/Entity/Repository/BankTransferSettingsRepository.php b/src/Marello/Bundle/BankTransferBundle/Entity/Repository/BankTransferSettingsRepository.php new file mode 100644 index 000000000..50467b325 --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Entity/Repository/BankTransferSettingsRepository.php @@ -0,0 +1,38 @@ +aclHelper = $aclHelper; + } + + /** + * @return BankTransferSettings[] + */ + public function findWithEnabledChannel() + { + $qb = $this->createQueryBuilder('bts'); + + $qb + ->join('bts.channel', 'ch') + ->where('ch.enabled = true') + ->orderBy('bts.id'); + + return $this->aclHelper->apply($qb)->getResult(); + } +} diff --git a/src/Marello/Bundle/BankTransferBundle/Form/Type/BankTransferOptionsType.php b/src/Marello/Bundle/BankTransferBundle/Form/Type/BankTransferOptionsType.php new file mode 100755 index 000000000..01b02b23a --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Form/Type/BankTransferOptionsType.php @@ -0,0 +1,48 @@ +add(BankTransferMethod::INSTRUCTIONS_OPTION, TextareaType::class, [ + 'required' => true, + 'label' => 'marello.bank_transfer.method.instructions.label', + ]); + } + + /** + * @param OptionsResolver $resolver + * + * @throws AccessException + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'label' => 'marello.bank_transfer.form.marello_bank_transfer_options_type.label', + ]); + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } +} diff --git a/src/Marello/Bundle/BankTransferBundle/Form/Type/BankTransferSettingsType.php b/src/Marello/Bundle/BankTransferBundle/Form/Type/BankTransferSettingsType.php new file mode 100644 index 000000000..141dd59a2 --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Form/Type/BankTransferSettingsType.php @@ -0,0 +1,63 @@ +add( + 'labels', + LocalizedFallbackValueCollectionType::class, + [ + 'label' => 'marello.bank_transfer.settings.labels.label', + 'required' => true, + 'entry_options' => ['constraints' => [new NotBlank()]], + ] + ); + } + + /** + * @param OptionsResolver $resolver + * + * @throws AccessException + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults( + [ + 'data_class' => BankTransferSettings::class, + ] + ); + } + + /** + * @return string + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } +} diff --git a/src/Marello/Bundle/BankTransferBundle/Integration/BankTransferChannelType.php b/src/Marello/Bundle/BankTransferBundle/Integration/BankTransferChannelType.php new file mode 100644 index 000000000..5a7ddb976 --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Integration/BankTransferChannelType.php @@ -0,0 +1,27 @@ +extension) { + $this->extension = new MarelloBankTransferExtension(); + } + + return $this->extension; + } +} diff --git a/src/Marello/Bundle/BankTransferBundle/Method/BankTransferMethod.php b/src/Marello/Bundle/BankTransferBundle/Method/BankTransferMethod.php new file mode 100644 index 000000000..05cdb26cc --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Method/BankTransferMethod.php @@ -0,0 +1,102 @@ +identifier = $identifier; + $this->label = $label; + $this->icon = $icon; + $this->enabled = $enabled; + } + + /** + * {@inheritDoc} + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * {@inheritDoc} + */ + public function isEnabled() + { + return $this->enabled; + } + + /** + * {@inheritDoc} + */ + public function getLabel() + { + return $this->label; + } + + /** + * {@inheritDoc} + */ + public function getIcon() + { + return $this->icon; + } + + /** + * {@inheritDoc} + */ + public function getOptionsConfigurationFormType() + { + return BankTransferOptionsType::class; + } + + /** + * {@inheritDoc} + */ + public function getOptions() + { + return []; + } + + /** + * {@inheritDoc} + */ + public function getSortOrder() + { + return 10; + } +} diff --git a/src/Marello/Bundle/BankTransferBundle/Method/Factory/BankTransferMethodFromChannelFactory.php b/src/Marello/Bundle/BankTransferBundle/Method/Factory/BankTransferMethodFromChannelFactory.php new file mode 100644 index 000000000..8e916e57e --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Method/Factory/BankTransferMethodFromChannelFactory.php @@ -0,0 +1,82 @@ +identifierGenerator = $identifierGenerator; + $this->localizationHelper = $localizationHelper; + $this->integrationIconProvider = $integrationIconProvider; + } + + /** + * @param Channel $channel + * + * @return ManualShippingMethod + */ + public function create(Channel $channel) + { + $id = $this->identifierGenerator->generateIdentifier($channel); + $label = $this->getChannelLabel($channel); + $icon = $this->getIcon($channel); + + return new BankTransferMethod($id, $label, $icon, $channel->isEnabled()); + } + + /** + * @param Channel $channel + * + * @return string + */ + private function getChannelLabel(Channel $channel) + { + /** @var ManualShippingSettings $transport */ + $transport = $channel->getTransport(); + + return (string) $this->localizationHelper->getLocalizedValue($transport->getLabels()); + } + + /** + * @param Channel $channel + * + * @return string|null + */ + private function getIcon(Channel $channel) + { + return $this->integrationIconProvider->getIcon($channel); + } +} diff --git a/src/Marello/Bundle/BankTransferBundle/Method/Provider/BankTransferMethodProvider.php b/src/Marello/Bundle/BankTransferBundle/Method/Provider/BankTransferMethodProvider.php new file mode 100644 index 000000000..59658fd7d --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Method/Provider/BankTransferMethodProvider.php @@ -0,0 +1,21 @@ +createMarelloBankTransferTransportLabelTable($schema); + $this->addMarelloBankTransferTransportLabelForeignKeys($schema); + } + + /** + * @param Schema $schema + */ + private function createMarelloBankTransferTransportLabelTable(Schema $schema) + { + $table = $schema->createTable('marello_bank_transfer_tr_lbl'); + + $table->addColumn('transport_id', 'integer', []); + $table->addColumn('localized_value_id', 'integer', []); + + $table->setPrimaryKey(['transport_id', 'localized_value_id']); + $table->addIndex(['transport_id'], 'marello_bank_transfer_trans_label_transport_id', []); + $table->addUniqueIndex(['localized_value_id'], 'marello_bank_transfer_trans_label_localized_value_id', []); + } + + /** + * @param Schema $schema + * + * @throws SchemaException + */ + private function addMarelloBankTransferTransportLabelForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_bank_transfer_tr_lbl'); + + $table->addForeignKeyConstraint( + $schema->getTable('oro_fallback_localization_val'), + ['localized_value_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + $table->addForeignKeyConstraint( + $schema->getTable('oro_integration_transport'), + ['transport_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + } +} diff --git a/src/Marello/Bundle/BankTransferBundle/Migrations/Schema/v1_0/MarelloBankTransferBundle.php b/src/Marello/Bundle/BankTransferBundle/Migrations/Schema/v1_0/MarelloBankTransferBundle.php new file mode 100644 index 000000000..7f3ea728f --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Migrations/Schema/v1_0/MarelloBankTransferBundle.php @@ -0,0 +1,59 @@ +createMarelloBankTransferTransportLabelTable($schema); + $this->addMarelloBankTransferTransportLabelForeignKeys($schema); + } + + /** + * @param Schema $schema + */ + private function createMarelloBankTransferTransportLabelTable(Schema $schema) + { + $table = $schema->createTable('marello_bank_transfer_tr_lbl'); + + $table->addColumn('transport_id', 'integer', []); + $table->addColumn('localized_value_id', 'integer', []); + + $table->setPrimaryKey(['transport_id', 'localized_value_id']); + $table->addIndex(['transport_id'], 'marello_bank_transfer_trans_label_transport_id', []); + $table->addUniqueIndex(['localized_value_id'], 'marello_bank_transfer_trans_label_localized_value_id', []); + } + + /** + * @param Schema $schema + * + * @throws SchemaException + */ + private function addMarelloBankTransferTransportLabelForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_bank_transfer_tr_lbl'); + + $table->addForeignKeyConstraint( + $schema->getTable('oro_fallback_localization_val'), + ['localized_value_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + $table->addForeignKeyConstraint( + $schema->getTable('oro_integration_transport'), + ['transport_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + } +} diff --git a/src/Marello/Bundle/BankTransferBundle/Resources/config/factory.yml b/src/Marello/Bundle/BankTransferBundle/Resources/config/factory.yml new file mode 100644 index 000000000..06d20ef3a --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Resources/config/factory.yml @@ -0,0 +1,8 @@ +services: + marello_bank_transfer.factory.method: + class: 'Marello\Bundle\BankTransferBundle\Method\Factory\BankTransferMethodFromChannelFactory' + public: false + arguments: + - '@marello_bank_transfer.method.identifier_generator.method' + - '@oro_locale.helper.localization' + - '@oro_integration.provider.integration_icon' diff --git a/src/Marello/Bundle/BankTransferBundle/Resources/config/integration.yml b/src/Marello/Bundle/BankTransferBundle/Resources/config/integration.yml new file mode 100644 index 000000000..80c012125 --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Resources/config/integration.yml @@ -0,0 +1,16 @@ +parameters: + marello_bank_transfer.integration.channel.type: 'bank_transfer' + marello_bank_transfer.integration.transport.type: 'bank_transfer' + +services: + marello_bank_transfer.integration.channel: + class: 'Marello\Bundle\BankTransferBundle\Integration\BankTransferChannelType' + public: true + tags: + - { name: oro_integration.channel, type: bank_transfer } + + marello_bank_transfer.integration.transport: + class: 'Marello\Bundle\BankTransferBundle\Integration\BankTransferTransport' + public: false + tags: + - { name: oro_integration.transport, type: bank_transfer, channel_type: bank_transfer } diff --git a/src/Marello/Bundle/BankTransferBundle/Resources/config/oro/actions.yml b/src/Marello/Bundle/BankTransferBundle/Resources/config/oro/actions.yml new file mode 100755 index 000000000..bba4c4133 --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Resources/config/oro/actions.yml @@ -0,0 +1,153 @@ +operations: + oro_integration_deactivate: + label: oro.integration.deactivate + preactions: + - '@assign_constant_value': + attribute: $.bankTransferType + value: Marello\Bundle\BankTransferBundle\Integration\BankTransferChannelType::TYPE + preconditions: + '@and': + - '@not_equal': [$type, $.bankTransferType] + + oro_integration_delete: + label: oro.integration.delete + preactions: + - '@assign_constant_value': + attribute: $.bankTransferType + value: Marello\Bundle\BankTransferBundle\Integration\BankTransferChannelType::TYPE + preconditions: + '@and': + - '@not_equal': [$type, $.bankTransferType] + + marello_bank_transfer_integration_deactivate: + label: oro.integration.deactivate + extends: oro_integration_deactivate + for_all_entities: false + for_all_datagrids: false + replace: + - preactions + - preconditions + - frontend_options + preactions: + - '@assign_constant_value': + attribute: $.bankTransferType + value: Marello\Bundle\BankTransferBundle\Integration\BankTransferChannelType::TYPE + - '@call_service_method': + attribute: $.actionAllowed + service: oro_integration.utils.edit_mode + method: isSwitchEnableAllowed + method_parameters: [$.data.editMode] + - '@call_service_method': + attribute: $.methodIdentifier + service: marello_bank_transfer.method.identifier_generator.method + method: generateIdentifier + method_parameters: [$.data] + - '@call_service_method': + attribute: $.linkGrid + service: marello_payment.helper.filtered_datagrid_route + method: generate + method_parameters: [{'methodConfigs': $.methodIdentifier}] + preconditions: + '@and': + - '@marello_payment_method_has_enabled_payment_rules': + parameters: + method_identifier: $.methodIdentifier + - '@equal': [$type, $.bankTransferType] + - '@equal': [$.actionAllowed, true] + - '@equal': [$.data.enabled, true] + frontend_options: + confirmation: + title: marello.payment.integration.deactivate.title + okText: marello.payment.integration.deactivate.button.okText + message: marello.payment.integration.deactivate.message + message_parameters: + linkGrid: $.linkGrid + component: oroui/js/standart-confirmation + + marello_bank_transfer_integration_deactivate_without_rules: + label: oro.integration.deactivate + extends: marello_bank_transfer_integration_deactivate + for_all_entities: false + for_all_datagrids: false + replace: + - preconditions + - frontend_options + preconditions: + '@and': + - '@not': + - '@marello_payment_method_has_enabled_payment_rules': + parameters: + method_identifier: $.methodIdentifier + - '@equal': [$type, $.bankTransferType] + - '@equal': [$.actionAllowed, true] + - '@equal': [$.data.enabled, true] + frontend_options: ~ + + marello_bank_transfer_integration_delete: + label: oro.integration.delete + extends: oro_integration_delete + for_all_entities: false + for_all_datagrids: false + replace: + - preactions + - preconditions + - frontend_options + preactions: + - '@assign_constant_value': + attribute: $.bankTransferType + value: Marello\Bundle\BankTransferBundle\Integration\BankTransferChannelType::TYPE + - '@call_service_method': + service: oro_integration.utils.edit_mode + method: isEditAllowed + method_parameters: [$.data.editMode] + attribute: $.actionAllowed + - '@call_service_method': + attribute: $.methodIdentifier + service: marello_bank_transfer.method.identifier_generator.method + method: generateIdentifier + method_parameters: [$.data] + - '@call_service_method': + attribute: $.linkGrid + service: marello_payment.helper.filtered_datagrid_route + method: generate + method_parameters: [{'methodConfigs': $.methodIdentifier}] + preconditions: + '@and': + - '@marello_payment_method_has_payment_rules': + parameters: + method_identifier: $.methodIdentifier + - '@equal': [$type, $.bankTransferType] + - '@equal': [$.actionAllowed, true] + frontend_options: + confirmation: + title: marello.payment.integration.delete.title + okText: marello.payment.integration.delete.button.okText + message: marello.payment.integration.delete.message + message_parameters: + linkGrid: $.linkGrid + component: oroui/js/standart-confirmation + + marello_bank_transfer_integration_delete_without_rules: + label: oro.integration.delete + extends: marello_bank_transfer_integration_delete + for_all_entities: false + for_all_datagrids: false + replace: + - preconditions + - frontend_options + preconditions: + '@and': + - '@not': + - '@marello_payment_method_has_payment_rules': + parameters: + method_identifier: $.methodIdentifier + - '@equal': [$type, $.bankTransferType] + - '@equal': [$.actionAllowed, true] + frontend_options: + title: oro.action.delete_entity + confirmation: + title: oro.action.delete_entity + message: oro.action.delete_confirm + message_parameters: + entityLabel: $name + component: oroui/js/delete-confirmation diff --git a/src/Marello/Bundle/BankTransferBundle/Resources/config/oro/bundles.yml b/src/Marello/Bundle/BankTransferBundle/Resources/config/oro/bundles.yml new file mode 100644 index 000000000..08499145a --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Resources/config/oro/bundles.yml @@ -0,0 +1,2 @@ +bundles: + - { name: Marello\Bundle\BankTransferBundle\MarelloBankTransferBundle, priority: 40 } diff --git a/src/Marello/Bundle/BankTransferBundle/Resources/config/payment.yml b/src/Marello/Bundle/BankTransferBundle/Resources/config/payment.yml new file mode 100644 index 000000000..e3f1545c1 --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Resources/config/payment.yml @@ -0,0 +1,11 @@ +services: + marello_bank_transfer.payment_method_provider.bank_transfer: + class: 'Marello\Bundle\BankTransferBundle\Method\Provider\BankTransferMethodProvider' + public: false + arguments: + - '%marello_bank_transfer.integration.channel.type%' + - '@oro_entity.doctrine_helper' + - '@marello_bank_transfer.factory.method' + tags: + - { name: marello_payment.payment_method_provider } + - { name: doctrine.orm.entity_listener, entity: 'Oro\Bundle\IntegrationBundle\Entity\Channel', event: postLoad } \ No newline at end of file diff --git a/src/Marello/Bundle/BankTransferBundle/Resources/config/services.yml b/src/Marello/Bundle/BankTransferBundle/Resources/config/services.yml new file mode 100644 index 000000000..ec26737ed --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Resources/config/services.yml @@ -0,0 +1,31 @@ +parameters: + marello_bank_transfer.method.identifier_prefix.bank_transfer: 'bank_transfer' + marello_bank_transfer.admin_view.method_template: 'MarelloBankTransferBundle::method/bankTransferMethodWithOptions.html.twig' + +services: + marello_bank_transfer.method.identifier_generator.method: + parent: oro_integration.generator.prefixed_identifier_generator + public: true + arguments: + - '%marello_bank_transfer.method.identifier_prefix.bank_transfer%' + + marello_bank_transfer.repository.bank_transfer_settings: + class: 'Marello\Bundle\BankTransferBundle\Entity\Repository\BankTransferSettingsRepository' + parent: oro_entity.abstract_repository + arguments: + - 'Marello\Bundle\BankTransferBundle\Entity\BankTransferSettings' + calls: + - [setAclHelper, ['@oro_security.acl_helper']] + + marello_bank_transfer.form.type.bank_transfer_options: + class: 'Marello\Bundle\BankTransferBundle\Form\Type\BankTransferOptionsType' + tags: + - { name: form.type } + + marello_bank_transfer.event_listener.payment_method_config_data: + parent: marello_payment.admin_view.method_template.listener + arguments: + - '%marello_bank_transfer.admin_view.method_template%' + - '@marello_bank_transfer.payment_method_provider.bank_transfer' + tags: + - { name: kernel.event_listener, event: marello_payment_method.config_data, method: onGetConfigData } diff --git a/src/Marello/Bundle/BankTransferBundle/Resources/public/img/bank-transfer-icon.png b/src/Marello/Bundle/BankTransferBundle/Resources/public/img/bank-transfer-icon.png new file mode 100644 index 000000000..3f0a8bfae Binary files /dev/null and b/src/Marello/Bundle/BankTransferBundle/Resources/public/img/bank-transfer-icon.png differ diff --git a/src/Marello/Bundle/BankTransferBundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/BankTransferBundle/Resources/translations/messages.en.yml new file mode 100644 index 000000000..708013ad6 --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Resources/translations/messages.en.yml @@ -0,0 +1,11 @@ +marello: + bank_transfer: + method: + instructions.label: 'Bank Instructions' + settings: + label: 'Bank Transfer Settings' + labels.label: 'Label' + channel_type: + label: 'Bank Transfer' + menu: + bank_transfer.label: 'Bank Transfer' diff --git a/src/Marello/Bundle/BankTransferBundle/Resources/views/method/bankTransferMethodWithOptions.html.twig b/src/Marello/Bundle/BankTransferBundle/Resources/views/method/bankTransferMethodWithOptions.html.twig new file mode 100755 index 000000000..cd6799f02 --- /dev/null +++ b/src/Marello/Bundle/BankTransferBundle/Resources/views/method/bankTransferMethodWithOptions.html.twig @@ -0,0 +1,5 @@ +{% spaceless %} + {{ marello_get_payment_method_label(methodData.identifier)|trans }} +
+ {{ 'marello.bank_transfer.method.instructions.label'|trans }}: {{ methodData.options.instructions }} +{% endspaceless %} diff --git a/src/Marello/Bundle/CatalogBundle/Controller/CategoryController.php b/src/Marello/Bundle/CatalogBundle/Controller/CategoryController.php index a992c407b..c152d9e30 100644 --- a/src/Marello/Bundle/CatalogBundle/Controller/CategoryController.php +++ b/src/Marello/Bundle/CatalogBundle/Controller/CategoryController.php @@ -4,15 +4,16 @@ use Marello\Bundle\CatalogBundle\Entity\Category; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Routing\Annotation\Route; -class CategoryController extends Controller +class CategoryController extends AbstractController { /** - * @Config\Route("/", name="marello_category_index") + * @Route(path="/", name="marello_category_index") * @AclAncestor("marello_category_view") - * @Config\Template + * @Template */ public function indexAction() { @@ -20,9 +21,9 @@ public function indexAction() } /** - * @Config\Route("/create", name="marello_category_create") + * @Route(path="/create", name="marello_category_create") * @AclAncestor("marello_category_create") - * @Config\Template("MarelloCatalogBundle:Category:update.html.twig") + * @Template("MarelloCatalogBundle:Category:update.html.twig") * * @return array */ @@ -32,9 +33,9 @@ public function createAction() } /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_category_update") + * @Route(path="/update/{id}", requirements={"id"="\d+"}, name="marello_category_update") * @AclAncestor("marello_category_update") - * @Config\Template("MarelloCatalogBundle:Category:update.html.twig") + * @Template("MarelloCatalogBundle:Category:update.html.twig") * * @param Category $category * @@ -70,9 +71,9 @@ protected function update(Category $category) } /** - * @Config\Route("/view/{id}", requirements={"id"="\d+"}, name="marello_category_view") + * @Route(path="/view/{id}", requirements={"id"="\d+"}, name="marello_category_view") * @AclAncestor("marello_category_view") - * @Config\Template("MarelloCatalogBundle:Category:view.html.twig") + * @Template("MarelloCatalogBundle:Category:view.html.twig") * * @param Category $category * diff --git a/src/Marello/Bundle/CatalogBundle/EventListener/Datagrid/CategoriesDatagridListener.php b/src/Marello/Bundle/CatalogBundle/EventListener/Datagrid/CategoriesDatagridListener.php new file mode 100644 index 000000000..60b5231d3 --- /dev/null +++ b/src/Marello/Bundle/CatalogBundle/EventListener/Datagrid/CategoriesDatagridListener.php @@ -0,0 +1,162 @@ +relatedEntityClass = $relatedEntityClass; + $this->categoriesChoicesProvider = $categoriesChoicesProvider; + + $this->expressionBuilder = new Expr(); + } + + /** + * @param BuildBefore $event + */ + public function onBuildBefore(BuildBefore $event) + { + $config = $event->getConfig(); + + $this->addSelect($config); + $this->addJoin($config); + $this->addColumn($config); + $this->addSorter($config); + $this->addFilter($config); + } + + /** + * @param DatagridConfiguration $configuration + * @return string + * @throws \InvalidArgumentException when a root entity not found in the grid + */ + protected function getAlias(DatagridConfiguration $configuration) + { + $rootAlias = $configuration->getOrmQuery()->getRootAlias(); + if (!$rootAlias) { + throw new \InvalidArgumentException( + sprintf( + 'A root entity is missing for grid "%s"', + $configuration->getName() + ) + ); + } + + return $rootAlias; + } + + /** + * @return string + */ + protected function getColumnLabel() + { + return 'marello.catalog.category.entity_plural_label'; + } + + /** + * @return string + */ + protected function getDataName() + { + return self::DATA_NAME; + } + + /** + * @return string + */ + protected function getJoinAlias() + { + return self::JOIN_ALIAS; + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSelect(DatagridConfiguration $config) + { + $config->getOrmQuery()->addSelect( + sprintf('count(%s.code) AS categoriesCount', $this->getJoinAlias()) + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addJoin(DatagridConfiguration $config) + { + $config->getOrmQuery()->addLeftJoin( + $this->getAlias($config).'.categories', + $this->getJoinAlias() + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addColumn(DatagridConfiguration $config) + { + $config->offsetSetByPath(sprintf('[columns][%s]', $this->getDataName()), [ + 'label' => $this->getColumnLabel(), + 'type' => 'twig', + 'frontend_type' => 'html', + 'template' => 'MarelloCatalogBundle:Datagrid/Property:categories.html.twig', + 'renderable' => false + ]); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSorter(DatagridConfiguration $config) + { + $config->offsetSetByPath( + sprintf('[sorters][columns][%s]', $this->getDataName()), + ['data_name' => 'categoriesCount'] + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addFilter(DatagridConfiguration $config) + { + $config->offsetSetByPath( + sprintf('[filters][columns][%s]', $this->getDataName()), + [ + 'type' => 'choice', + 'data_name' => $this->getJoinAlias() . '.code', + 'enabled' => false, + 'options' => [ + 'field_options' => [ + 'multiple' => true, + 'choices' => $this->categoriesChoicesProvider->getCategories() + ] + ] + ] + ); + } +} diff --git a/src/Marello/Bundle/CatalogBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/CatalogBundle/Resources/config/oro/datagrids.yml index d3e27d9a4..da0332ab3 100644 --- a/src/Marello/Bundle/CatalogBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/CatalogBundle/Resources/config/oro/datagrids.yml @@ -35,7 +35,7 @@ datagrids: createdAt: { data_name: c.createdAt } updatedAt: { data_name: c.updatedAt } default: - name: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC" + name: 'ASC' properties: id: ~ view_link: @@ -56,7 +56,7 @@ datagrids: marello-category-products-base-grid: acl_resource: marello_product_update - extended_entity_name: '%marello_product.entity.class%' + extended_entity_name: Marello\Bundle\ProductBundle\Entity\Product source: type: orm query: @@ -67,7 +67,7 @@ datagrids: - product.createdAt - product.updatedAt from: - - { table: '%marello_product.entity.class%', alias: product } + - { table: MarelloProductBundle:Product, alias: product } columns: sku: label: marello.product.sku.label @@ -119,7 +119,7 @@ datagrids: - products sorters: default: - sku: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' + sku: 'DESC' marello-category-products-extend-grid: extends: marello-category-products-base-grid @@ -141,7 +141,7 @@ datagrids: join: left: - - join: '%marello_catalog.category.entity.class%' + join: Marello\Bundle\CatalogBundle\Entity\Category alias: productCategory conditionType: WITH condition: 'productCategory = :category_id' @@ -163,7 +163,7 @@ datagrids: inCategory: data_name: inCategory default: - inCategory: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' + inCategory: 'DESC' filters: columns: inCategory: diff --git a/src/Marello/Bundle/CatalogBundle/Resources/config/services.yml b/src/Marello/Bundle/CatalogBundle/Resources/config/services.yml index 12ebf8d28..a4ccf54b1 100644 --- a/src/Marello/Bundle/CatalogBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/CatalogBundle/Resources/config/services.yml @@ -1,5 +1,3 @@ -parameters: - marello_catalog.category.entity.class: Marello\Bundle\CatalogBundle\Entity\Category services: marello_catalog.category.form.type: class: 'Marello\Bundle\CatalogBundle\Form\Type\CategoryType' @@ -16,12 +14,20 @@ services: marello_catalog.category.form.handler: class: 'Marello\Bundle\CatalogBundle\Form\Handler\CategoryHandler' - scope: request + public: true arguments: - '@marello_catalog.category.form' - '@request_stack' - '@doctrine.orm.entity_manager' + marello_catalog.event_listener.datagrid.products_grid: + class: 'Marello\Bundle\CatalogBundle\EventListener\Datagrid\CategoriesDatagridListener' + arguments: + - 'Marello\Bundle\ProductBundle\Entity\Product' + - '@marello_catalog.provider.categories_choices' + tags: + - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before.marello-products-grid, method: onBuildBefore } + marello_catalog.listener.datagrid.category_products_column_listener: class: 'Marello\Bundle\CatalogBundle\EventListener\Datagrid\CategoryProductsColumnListener' arguments: @@ -34,6 +40,7 @@ services: marello_catalog.provider.categories_choices: class: 'Marello\Bundle\CatalogBundle\Provider\CategoriesChoicesProvider' + public: true arguments: - '@oro_entity.doctrine_helper' diff --git a/src/Marello/Bundle/CatalogBundle/Resources/views/Category/index.html.twig b/src/Marello/Bundle/CatalogBundle/Resources/views/Category/index.html.twig index 4a3cbdf6b..05149af24 100644 --- a/src/Marello/Bundle/CatalogBundle/Resources/views/Category/index.html.twig +++ b/src/Marello/Bundle/CatalogBundle/Resources/views/Category/index.html.twig @@ -5,7 +5,7 @@ {% set gridName = 'marello-categories-grid' %} {% block navButtons %} - {% if resource_granted('marello_category_create') %} + {% if is_granted('marello_category_create') %} {{ UI.addButton({ 'path': path('marello_category_create'), 'entity_label': 'marello.catalog.category.entity_label'|trans diff --git a/src/Marello/Bundle/CatalogBundle/Resources/views/Category/update.html.twig b/src/Marello/Bundle/CatalogBundle/Resources/views/Category/update.html.twig index 4d122324c..2fc35db80 100644 --- a/src/Marello/Bundle/CatalogBundle/Resources/views/Category/update.html.twig +++ b/src/Marello/Bundle/CatalogBundle/Resources/views/Category/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} @@ -12,9 +13,9 @@ {{ parent() }} {{ UI.cancelButton(path('marello_category_index')) }} - {% if resource_granted('marello_category_update') %} + {% if is_granted('marello_category_update') %} {% set html = '' %} - {% if resource_granted('marello_category_view') %} + {% if is_granted('marello_category_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_category_view', 'params': {'id': '$id'} @@ -62,7 +63,7 @@ }]} ] %} - {% if resource_granted('marello_category_update') %} + {% if is_granted('marello_category_update') %} {% set dataBlocks = dataBlocks|merge([{ 'title' : 'marello.product.entity_plural_label'|trans, 'subblocks': [ diff --git a/src/Marello/Bundle/CatalogBundle/Resources/views/Category/view.html.twig b/src/Marello/Bundle/CatalogBundle/Resources/views/Category/view.html.twig index 362231de0..6e5fe02dc 100644 --- a/src/Marello/Bundle/CatalogBundle/Resources/views/Category/view.html.twig +++ b/src/Marello/Bundle/CatalogBundle/Resources/views/Category/view.html.twig @@ -1,5 +1,6 @@ {% extends 'OroUIBundle:actions:view.html.twig' %} {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% import 'OroEntityConfigBundle::macros.html.twig' as entityConfig %} {% oro_title_set({params : {"%name%": entity.name } }) %} diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/categories.html.twig b/src/Marello/Bundle/CatalogBundle/Resources/views/Datagrid/Property/categories.html.twig similarity index 100% rename from src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/categories.html.twig rename to src/Marello/Bundle/CatalogBundle/Resources/views/Datagrid/Property/categories.html.twig diff --git a/src/Marello/Bundle/CatalogBundle/Tests/Functional/DataFixtures/LoadCategoryData.php b/src/Marello/Bundle/CatalogBundle/Tests/Functional/DataFixtures/LoadCategoryData.php index 3fe7c6e9c..d043ae7a8 100644 --- a/src/Marello/Bundle/CatalogBundle/Tests/Functional/DataFixtures/LoadCategoryData.php +++ b/src/Marello/Bundle/CatalogBundle/Tests/Functional/DataFixtures/LoadCategoryData.php @@ -5,17 +5,9 @@ use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Common\Persistence\ObjectManager; - use Marello\Bundle\CatalogBundle\Entity\Category; use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\ProductBundle\Tests\Functional\DataFixtures\LoadProductData; -use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper; - -use Marello\Bundle\OrderBundle\Entity\OrderItem; -use Marello\Bundle\ReturnBundle\Entity\ReturnEntity; -use Marello\Bundle\ReturnBundle\Entity\ReturnItem; -use Marello\Bundle\OrderBundle\Tests\Functional\DataFixtures\LoadOrderData; -use Marello\Bundle\SalesBundle\Tests\Functional\DataFixtures\LoadSalesData; class LoadCategoryData extends AbstractFixture implements DependentFixtureInterface { diff --git a/src/Marello/Bundle/CoreBundle/Mailer/Processor.php b/src/Marello/Bundle/CoreBundle/Mailer/Processor.php index 2dc46991e..a8b0bb86e 100644 --- a/src/Marello/Bundle/CoreBundle/Mailer/Processor.php +++ b/src/Marello/Bundle/CoreBundle/Mailer/Processor.php @@ -2,18 +2,13 @@ namespace Marello\Bundle\CoreBundle\Mailer; -use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; - -use Oro\Bundle\EmailBundle\Mailer\Processor as BaseProcessor; use Oro\Bundle\EmailBundle\Decoder\ContentDecoder; use Oro\Bundle\EmailBundle\Entity\EmailAttachment; use Oro\Bundle\EmailBundle\Entity\EmailAttachmentContent; -use Oro\Bundle\EmailBundle\Entity\EmailOrigin; -use Oro\Bundle\EmailBundle\Entity\EmailUser; -use Oro\Bundle\EmailBundle\Event\EmailBodyAdded; use Oro\Bundle\EmailBundle\Form\Model\Email as EmailModel; use Oro\Bundle\EmailBundle\Form\Model\EmailAttachment as EmailAttachmentModel; -use Oro\Bundle\EmailBundle\Tools\EmailAddressHelper; +use Oro\Bundle\EmailBundle\Mailer\Processor as BaseProcessor; +use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; class Processor extends BaseProcessor { @@ -34,7 +29,7 @@ protected function prepareMessage(EmailModel $model, $parentMessageId, $messageD } $addresses = $this->getAddresses($model->getFrom()); $address = $this->emailAddressHelper->extractPureEmailAddress($model->getFrom()); - $message->setDate($messageDate->getTimestamp()); + $message->setDate($messageDate); $message->setFrom($addresses); $message->setReplyTo($addresses); $message->setReturnPath($address); @@ -78,7 +73,7 @@ function ($matches) use ($message, $guesser, $model) { list($encoding, $file) = explode(',', $content); $mime = str_replace('data:', '', $mime); $fileName = sprintf('%s.%s', uniqid(), $guesser->guess($mime)); - $swiftAttachment = \Swift_Image::newInstance( + $swiftAttachment = new \Swift_Image( ContentDecoder::decode($file, $encoding), $fileName, $mime diff --git a/src/Marello/Bundle/CoreBundle/MarelloCoreBundle.php b/src/Marello/Bundle/CoreBundle/MarelloCoreBundle.php index 13a097fc1..ebf5277bb 100644 --- a/src/Marello/Bundle/CoreBundle/MarelloCoreBundle.php +++ b/src/Marello/Bundle/CoreBundle/MarelloCoreBundle.php @@ -13,7 +13,7 @@ class MarelloCoreBundle extends Bundle { public function build(ContainerBuilder $container) { - $container->addCompilerPass(new TwigSandboxConfigurationPass()); + //$container->addCompilerPass(new TwigSandboxConfigurationPass()); $container->addCompilerPass(new OroEmailProcessorOverrideServiceCompilerPass()); $container->addCompilerPass(new AdditionalPlaceholderProviderPass()); parent::build($container); diff --git a/src/Marello/Bundle/CoreBundle/Migration/UpdateExtendRelationTrait.php b/src/Marello/Bundle/CoreBundle/Migration/UpdateExtendRelationTrait.php new file mode 100644 index 000000000..b100217c1 --- /dev/null +++ b/src/Marello/Bundle/CoreBundle/Migration/UpdateExtendRelationTrait.php @@ -0,0 +1,76 @@ +clearCache(); + + $entityConfigModel = $configManager->getConfigEntityModel($entityFrom); + if (!$entityConfigModel) { + return; + } + $data = $entityConfigModel->toArray('extend'); + + $fullRelationFrom = implode( + '|', + [$relationType, $entityFrom, $entityTo, $relationFrom] + ); + $fullRelationTo = implode( + '|', + [$relationType, $entityFrom, $entityTo, $relationTo] + ); + if (array_key_exists($fullRelationFrom, $data['relation'])) { + $data['relation'][$fullRelationTo] = + $data['relation'][$fullRelationFrom]; + unset($data['relation'][$fullRelationFrom]); + + if (isset($data['relation'][$fullRelationTo]['field_id'])) { + /** @var FieldConfigId $fieldId */ + $fieldId = $data['relation'][$fullRelationTo]['field_id']; + $reflectionProperty = new \ReflectionProperty(get_class($fieldId), 'fieldName'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($fieldId, $relationTo); + $data['relation'][$fullRelationTo]['field_id'] = $fieldId; + } + } + + if (array_key_exists($relationFrom, $data['schema']['relation'])) { + $data['schema']['relation'][$relationTo] = + $data['schema']['relation'][$relationFrom]; + unset($data['schema']['relation'][$relationFrom]); + } + if (array_key_exists($relationFrom, $data['schema']['addremove'])) { + $data['schema']['addremove'][$relationTo] = + $data['schema']['addremove'][$relationFrom]; + unset($data['schema']['addremove'][$relationFrom]); + } + + $entityConfigModel->fromArray('extend', $data, []); + $configManager->updateConfigEntityModel($entityFrom, true); + + $configManager->changeFieldName($entityFrom, $relationFrom, $relationTo); + + $configManager->flush(); + $configManager->clearCache(); + } +} diff --git a/src/Marello/Bundle/CoreBundle/Resources/config/jsmodules.yml b/src/Marello/Bundle/CoreBundle/Resources/config/jsmodules.yml new file mode 100644 index 000000000..6fbac5cf7 --- /dev/null +++ b/src/Marello/Bundle/CoreBundle/Resources/config/jsmodules.yml @@ -0,0 +1,3 @@ +dynamic-imports: + marellocore: + - marellocore/js/app/elements-helper \ No newline at end of file diff --git a/src/Marello/Bundle/CoreBundle/Resources/config/oro/app.yml b/src/Marello/Bundle/CoreBundle/Resources/config/oro/app.yml index 2d6a460af..da2d90485 100644 --- a/src/Marello/Bundle/CoreBundle/Resources/config/oro/app.yml +++ b/src/Marello/Bundle/CoreBundle/Resources/config/oro/app.yml @@ -6,13 +6,9 @@ fos_js_routing: oro_help: defaults: - link: 'http://www.marello.com/' - server: http://www.marello.com/ + link: 'https://knowledgebase.marello.com/' + server: https://knowledgebase.marello.com/ prefix: Third_Party - vendors: - Marello: - prefix: ~ - alias: Marello oro_theme: active_theme: oro @@ -21,15 +17,6 @@ oro_theme: icon: bundles/marellolayout/images/marello.ico logo: bundles/marellocore/img/marello-logo.png -oro_sidebar: - sidebar_widgets: - sticky_note: - icon: "bundles/marellolayout/images/marello.svg" - iconClass: ~ - settings: - content: | - Welcome to Marello! - Marello Unified Commerce Management provides capabilities to meet and exceed rising expectations in commerce. Learn more at www.marello.com oro_ui: settings: organization_name: diff --git a/src/Marello/Bundle/CoreBundle/Resources/config/requirejs.yml b/src/Marello/Bundle/CoreBundle/Resources/config/requirejs.yml deleted file mode 100644 index 43a4830d5..000000000 --- a/src/Marello/Bundle/CoreBundle/Resources/config/requirejs.yml +++ /dev/null @@ -1,3 +0,0 @@ -config: - paths: - 'marellocore/js/app/elements-helper': 'bundles/marellocore/js/app/elements-helper.js' diff --git a/src/Marello/Bundle/CoreBundle/Resources/config/services.yml b/src/Marello/Bundle/CoreBundle/Resources/config/services.yml index b4b82c12b..dd4569f4e 100644 --- a/src/Marello/Bundle/CoreBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/CoreBundle/Resources/config/services.yml @@ -1,6 +1,3 @@ -parameters: - marello_core.entity_serializer.class: Marello\Bundle\CoreBundle\Serializer\EntitySerializer - services: marello_core.workflow.action.workflow_transit_action: class: Marello\Bundle\CoreBundle\Workflow\Action\WorkflowTransitAction @@ -52,13 +49,14 @@ services: marello_core.form.url_generator: class: Marello\Bundle\CoreBundle\Form\UrlGenerator + public: true arguments: - '@oro_platform.provider.package' - '@oro_platform.url.cache' - '@request_stack' marello_core.entity_serializer: - class: '%marello_core.entity_serializer.class%' + class: 'Marello\Bundle\CoreBundle\Serializer\EntitySerializer' arguments: - '@doctrine' - '@oro_entity_config.config_manager' @@ -67,7 +65,7 @@ services: - '@oro_soap.entity_serializer.query_factory' - '@oro_workflow.manager' calls: - - ["setFieldsFilter", ['@oro_security.serializer.filter_chain']] + - ["setFieldFilter", ['@oro_security.entity_serializer.field_filter']] marello_core.provider.additional_placeholder_provider: class: 'Marello\Bundle\CoreBundle\Provider\AdditionalPlaceholderProvider' diff --git a/src/Marello/Bundle/CoreBundle/Resources/public/js/app/elements-helper.js b/src/Marello/Bundle/CoreBundle/Resources/public/js/app/elements-helper.js index 9c641adc7..8ab513bef 100644 --- a/src/Marello/Bundle/CoreBundle/Resources/public/js/app/elements-helper.js +++ b/src/Marello/Bundle/CoreBundle/Resources/public/js/app/elements-helper.js @@ -4,8 +4,8 @@ define(function(require) { /** * This helper use in the context of component View */ - var $ = require('jquery'); - var _ = require('underscore'); + const $ = require('jquery'); + const _ = require('underscore'); require('jquery.validate'); return { diff --git a/src/Marello/Bundle/CoreBundle/Resources/public/sidebar_widgets/sticky_note/widget.yml b/src/Marello/Bundle/CoreBundle/Resources/public/sidebar_widgets/sticky_note/widget.yml new file mode 100644 index 000000000..4a46bb564 --- /dev/null +++ b/src/Marello/Bundle/CoreBundle/Resources/public/sidebar_widgets/sticky_note/widget.yml @@ -0,0 +1,11 @@ +title: oro.sidebar.sticky_note_widget.title +iconClass: "fa-sticky-note-o" +dialogIcon: "bundles/orosidebar/sidebar_widgets/sticky_note/img/icon-sticky.png" +module: "orosidebar/widget/sticky-note" +placement: "both" +cssClass: 'sidebar-widget-sticky-note' +description: oro.sidebar.sticky_note_widget.description +settings: + content: | + Welcome to Marello! + Marello is a Unified Commerce Management Solution with built-in tools for your Commerce business. Learn more at www.marello.com \ No newline at end of file diff --git a/src/Marello/Bundle/CoreBundle/Resources/views/call_on_me.html.twig b/src/Marello/Bundle/CoreBundle/Resources/views/call_on_me.html.twig index 9589b8cbd..9363c6f02 100644 --- a/src/Marello/Bundle/CoreBundle/Resources/views/call_on_me.html.twig +++ b/src/Marello/Bundle/CoreBundle/Resources/views/call_on_me.html.twig @@ -1,5 +1,5 @@ - {% endblock messages %} -
+
- {% if marello_order_order_address is not defined or resource_granted(marello_order_order_address) %} + {% if marello_order_order_address is not defined or is_granted(marello_order_order_address) %}
{% endif %} -
{{ UI.renderHtmlProperty(label|trans, address.renderAddress(orderAddress)) }} {{ UI.renderProperty('marello.order.address.phone.label'|trans, orderAddress.phone) }}
-
-
\ No newline at end of file + diff --git a/src/Marello/Bundle/OrderBundle/Resources/views/Order/widget/updateAddress.html.twig b/src/Marello/Bundle/OrderBundle/Resources/views/Order/widget/updateAddress.html.twig index 4f43bd9a7..c73aaedff 100644 --- a/src/Marello/Bundle/OrderBundle/Resources/views/Order/widget/updateAddress.html.twig +++ b/src/Marello/Bundle/OrderBundle/Resources/views/Order/widget/updateAddress.html.twig @@ -1,44 +1,25 @@ {% form_theme form with ['OroFormBundle:Form:fields.html.twig', _self] %} -
-
- {% block messages %} -
-
-
- - {% endblock messages %} -
- {% if saved %} - - {% else %} +{% if saved %} + {% set widgetResponse = { + widget: { + trigger: [{ + eventBroker: 'widget', + name: 'formSave', + args: [form.vars.value.id], + }], + } + } %} + + {{ widgetResponse|json_encode|raw }} +{% else %} +
{% if not form.vars.valid and form_errors(form) %} -
-
- {{ form_errors(form) }} +
+
+ {{ form_errors(form) }} +
-
{% endif %} -
@@ -51,5 +32,5 @@
{{ oro_form_js_validation(form) }} - {% endif %} -
+
+{% endif %} diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/OrderJsonApiTest.php b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/OrderJsonApiTest.php index 8c983ebd6..3f4e84c5d 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/OrderJsonApiTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/OrderJsonApiTest.php @@ -5,7 +5,7 @@ use Symfony\Component\HttpFoundation\Response; use Marello\Bundle\OrderBundle\Entity\Order; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\CoreBundle\Tests\Functional\RestJsonApiTestCase; use Marello\Bundle\OrderBundle\Tests\Functional\DataFixtures\LoadOrderData; use Marello\Bundle\OrderBundle\Tests\Functional\DataFixtures\LoadOrderWorkflowData; diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/requests/order_create_with_existing_customer.yml b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/requests/order_create_with_existing_customer.yml index 47b8b3dcf..66e901df3 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/requests/order_create_with_existing_customer.yml +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/requests/order_create_with_existing_customer.yml @@ -7,7 +7,7 @@ data: totalTax: 0 grandTotal: 101 currency: 'USD' - paymentMethod: null + paymentMethod: 'payment_term_1' paymentReference: null paymentDetails: null shippingAmountInclTax: null @@ -103,7 +103,6 @@ included: attributes: quantity: 10 productName: 'name)>' - quantity: 0 price: 0 originalPriceInclTax: 0 originalPriceExclTax: 0 @@ -127,7 +126,6 @@ included: attributes: quantity: 10 productName: 'name)>' - quantity: 0 price: 0 originalPriceInclTax: 0 originalPriceExclTax: 0 diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/requests/order_create_with_new_customer.yml b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/requests/order_create_with_new_customer.yml index 598ff9c6e..9b2b771ad 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/requests/order_create_with_new_customer.yml +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/requests/order_create_with_new_customer.yml @@ -3,6 +3,7 @@ data: attributes: orderReference: 'NOPENOPE' currency: 'USD' + paymentMethod: 'payment_term_1' shippingMethod: 'manual_shipping_1' shippingMethodType: 'primary' relationships: diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/cget_order_list.yml b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/cget_order_list.yml index da660add9..9fb62c1a1 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/cget_order_list.yml +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/cget_order_list.yml @@ -26,7 +26,6 @@ data: salesChannelName: channel2 purchaseDate: '@marello_order_0->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending @@ -88,7 +87,6 @@ data: salesChannelName: channel3 purchaseDate: '@marello_order_1->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending @@ -159,7 +157,6 @@ data: salesChannelName: channel2 purchaseDate: '@marello_order_2->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending @@ -227,7 +224,6 @@ data: salesChannelName: channel3 purchaseDate: '@marello_order_3->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending @@ -298,7 +294,6 @@ data: salesChannelName: channel3 purchaseDate: '@marello_order_4->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending @@ -360,7 +355,6 @@ data: salesChannelName: channel3 purchaseDate: '@marello_order_5->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending @@ -425,7 +419,6 @@ data: salesChannelName: channel3 purchaseDate: '@marello_order_6->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending @@ -496,7 +489,6 @@ data: salesChannelName: channel2 purchaseDate: '@marello_order_7->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending @@ -558,7 +550,6 @@ data: salesChannelName: channel2 purchaseDate: '@marello_order_8->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending @@ -623,7 +614,6 @@ data: salesChannelName: channel1 purchaseDate: '@marello_order_9->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/get_order_by_id.yml b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/get_order_by_id.yml index fee393626..2fa1f090e 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/get_order_by_id.yml +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/get_order_by_id.yml @@ -25,7 +25,6 @@ data: salesChannelName: channel3 purchaseDate: '@marello_order_1->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/get_order_by_orderNumber.yml b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/get_order_by_orderNumber.yml index fee393626..2fa1f090e 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/get_order_by_orderNumber.yml +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/Api/responses/get_order_by_orderNumber.yml @@ -25,7 +25,6 @@ data: salesChannelName: channel3 purchaseDate: '@marello_order_1->purchaseDate->format("Y-m-d\TH:i:s\Z")' data: null - locale: null workflowItem: currentStep: name: pending diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/Controller/OrderControllerTest.php b/src/Marello/Bundle/OrderBundle/Tests/Functional/Controller/OrderControllerTest.php index a1deb7fad..2c4bce80d 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/Controller/OrderControllerTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/Controller/OrderControllerTest.php @@ -3,9 +3,10 @@ namespace Marello\Bundle\OrderBundle\Tests\Functional\Controller; use Marello\Bundle\AddressBundle\Entity\MarelloAddress; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Tests\Functional\DataFixtures\LoadOrderData; +use Marello\Bundle\PaymentBundle\Method\PaymentMethodInterface; use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\ProductBundle\Tests\Functional\DataFixtures\LoadProductData; use Marello\Bundle\SalesBundle\Entity\SalesChannel; @@ -181,6 +182,10 @@ public function testUpdateAddress() */ private function getSubmittedData($form, $orderCustomer, $salesChannel, $orderItems) { + $paymentMethodProvider = $this->getContainer()->get('marello_payment.payment_method.composite_provider'); + $paymentMethods = $paymentMethodProvider->getPaymentMethods(); + /** @var PaymentMethodInterface $paymentMethod */ + $paymentMethod = reset($paymentMethods); /** @var ShippingMethodProviderInterface $shippingMethodsProvider */ $shippingMethodsProvider = $this->getContainer()->get('marello_shipping.shipping_method_provider'); $shippingMethods = $shippingMethodsProvider->getShippingMethods(); @@ -199,6 +204,7 @@ private function getSubmittedData($form, $orderCustomer, $salesChannel, $orderIt 'billingAddress' => $this->getAddressFormData($orderCustomer->getPrimaryAddress()), 'shippingAddress' => $this->getAddressFormData($orderCustomer->getPrimaryAddress()), 'calculateShipping' => true, + 'paymentMethod' => $paymentMethod->getIdentifier(), 'shippingMethod' => $shippingMethod->getIdentifier(), 'shippingMethodType' => $shippingMethodType->getIdentifier(), 'estimatedShippingCostAmount' => 5.00 diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/Controller/OrderOnDemandWorkflowTest.php b/src/Marello/Bundle/OrderBundle/Tests/Functional/Controller/OrderOnDemandWorkflowTest.php index 4300207f7..c2d6b56c2 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/Controller/OrderOnDemandWorkflowTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/Controller/OrderOnDemandWorkflowTest.php @@ -3,11 +3,12 @@ namespace Marello\Bundle\OrderBundle\Tests\Functional\Controller; use Marello\Bundle\AddressBundle\Entity\MarelloAddress; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Tests\Functional\DataFixtures\LoadOrderData; use Marello\Bundle\PackingBundle\Entity\PackingSlip; use Marello\Bundle\PackingBundle\Entity\PackingSlipItem; +use Marello\Bundle\PaymentBundle\Method\PaymentMethodInterface; use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\ProductBundle\Tests\Functional\DataFixtures\LoadProductData; use Marello\Bundle\PurchaseOrderBundle\Entity\PurchaseOrder; @@ -196,6 +197,10 @@ public function testWorkflow() */ private function getSubmittedData($form, $orderCustomer, $salesChannel, $orderItems) { + $paymentMethodProvider = $this->getContainer()->get('marello_payment.payment_method.composite_provider'); + $paymentMethods = $paymentMethodProvider->getPaymentMethods(); + /** @var PaymentMethodInterface $paymentMethod */ + $paymentMethod = reset($paymentMethods); /** @var ShippingMethodProviderInterface $shippingMethodsProvider */ $shippingMethodsProvider = $this->getContainer()->get('marello_shipping.shipping_method_provider'); $shippingMethods = $shippingMethodsProvider->getShippingMethods(); @@ -214,6 +219,7 @@ private function getSubmittedData($form, $orderCustomer, $salesChannel, $orderIt 'billingAddress' => $this->getAddressFormData($orderCustomer->getPrimaryAddress()), 'shippingAddress' => $this->getAddressFormData($orderCustomer->getPrimaryAddress()), 'calculateShipping' => true, + 'paymentMethod' => $paymentMethod->getIdentifier(), 'shippingMethod' => $shippingMethod->getIdentifier(), 'shippingMethodType' => $shippingMethodType->getIdentifier(), 'estimatedShippingCostAmount' => 5.00 diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/DataFixtures/LoadOrderData.php b/src/Marello/Bundle/OrderBundle/Tests/Functional/DataFixtures/LoadOrderData.php index ec36abfa7..ff483b75c 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/DataFixtures/LoadOrderData.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/DataFixtures/LoadOrderData.php @@ -6,8 +6,9 @@ use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Common\Persistence\ObjectManager; use Marello\Bundle\AddressBundle\Entity\MarelloAddress; +use Marello\Bundle\CustomerBundle\Tests\Functional\DataFixtures\LoadCustomerData; use Marello\Bundle\InventoryBundle\Entity\Warehouse; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Entity\OrderItem; use Marello\Bundle\PricingBundle\Tests\Functional\DataFixtures\LoadProductChannelPricingData; diff --git a/src/Marello/Bundle/OrderBundle/Tests/Functional/EventListener/Doctrine/OrderWorkflowStartListenerTest.php b/src/Marello/Bundle/OrderBundle/Tests/Functional/EventListener/Doctrine/OrderWorkflowStartListenerTest.php index f0a986673..be407c0b2 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Functional/EventListener/Doctrine/OrderWorkflowStartListenerTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Functional/EventListener/Doctrine/OrderWorkflowStartListenerTest.php @@ -2,6 +2,7 @@ namespace Marello\Bundle\OrderBundle\Tests\Functional\EventListener\Doctrine; +use Marello\Bundle\PaymentBundle\Method\PaymentMethodInterface; use Symfony\Component\DomCrawler\Form; use Symfony\Component\HttpFoundation\Response; @@ -10,7 +11,7 @@ use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase; use Marello\Bundle\OrderBundle\Entity\Order; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\SalesBundle\Entity\SalesChannel; use Marello\Bundle\AddressBundle\Entity\MarelloAddress; @@ -159,6 +160,10 @@ private function createOrder() */ private function getSubmittedData($form, $orderCustomer, $salesChannel, $orderItems) { + $paymentMethodProvider = $this->getContainer()->get('marello_payment.payment_method.composite_provider'); + $paymentMethods = $paymentMethodProvider->getPaymentMethods(); + /** @var PaymentMethodInterface $paymentMethod */ + $paymentMethod = reset($paymentMethods); /** @var ShippingMethodProviderInterface $shippingMethodsProvider */ $shippingMethodsProvider = $this->getContainer()->get('marello_shipping.shipping_method_provider'); $shippingMethods = $shippingMethodsProvider->getShippingMethods(); @@ -177,6 +182,7 @@ private function getSubmittedData($form, $orderCustomer, $salesChannel, $orderIt 'billingAddress' => $this->getAddressFormData($orderCustomer->getPrimaryAddress()), 'shippingAddress' => $this->getAddressFormData($orderCustomer->getPrimaryAddress()), 'calculateShipping' => true, + 'paymentMethod' => $paymentMethod->getIdentifier(), 'shippingMethod' => $shippingMethod->getIdentifier(), 'shippingMethodType' => $shippingMethodType->getIdentifier(), 'estimatedShippingCostAmount' => 5.00 diff --git a/src/Marello/Bundle/OrderBundle/Tests/Unit/Entity/OrderTest.php b/src/Marello/Bundle/OrderBundle/Tests/Unit/Entity/OrderTest.php index 79cf00cb4..22227c963 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Unit/Entity/OrderTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Unit/Entity/OrderTest.php @@ -10,7 +10,7 @@ use Oro\Bundle\OrganizationBundle\Entity\Organization; use Marello\Bundle\OrderBundle\Entity\Order; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\OrderBundle\Entity\OrderItem; use Marello\Bundle\SalesBundle\Entity\SalesChannel; @@ -46,7 +46,6 @@ public function testAccessors() ['salesChannelName', 'some string'], ['organization', new Organization()], ['localization', new Localization()], - ['locale', 'some string'], ['createdAt', new \DateTime()], ['updatedAt', new \DateTime()] ]); diff --git a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/DiscountSubtotalProviderTest.php b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/DiscountSubtotalProviderTest.php index a7690cdfb..b114ab7b9 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/DiscountSubtotalProviderTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/DiscountSubtotalProviderTest.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\OrderBundle\Tests\Unit\Provider; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use PHPUnit\Framework\TestCase; diff --git a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderDashboardOrderItemsByStatusProviderTest.php b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderDashboardOrderItemsByStatusProviderTest.php index bd9ad83ce..89c66f008 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderDashboardOrderItemsByStatusProviderTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderDashboardOrderItemsByStatusProviderTest.php @@ -14,8 +14,9 @@ use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper; use Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper; use Symfony\Bridge\Doctrine\RegistryInterface; +use PHPUnit\Framework\TestCase; -class OrderDashboardOrderItemsByStatusProviderTest extends \PHPUnit\Framework\TestCase +class OrderDashboardOrderItemsByStatusProviderTest extends TestCase { /** @var RegistryInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $registry; diff --git a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderItem/OrderItemFormChangesProviderTest.php b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderItem/OrderItemFormChangesProviderTest.php index 80d7ffe69..69df438ce 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderItem/OrderItemFormChangesProviderTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderItem/OrderItemFormChangesProviderTest.php @@ -2,8 +2,9 @@ namespace Marello\Bundle\OrderBundle\Tests\Unit\Provider\OrderItem; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Form\FormInterface; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use PHPUnit\Framework\TestCase; @@ -37,7 +38,7 @@ protected function setUp() /** * @param $data - * @return FormChangesProviderInterface|\PHPUnit_Framework_MockObject_Builder_InvocationMocker + * @return FormChangesProviderInterface|MockObject */ protected function createProviderMock($data) { diff --git a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderItemsSubtotalProviderTest.php b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderItemsSubtotalProviderTest.php index 1479734c2..2abf26d1e 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderItemsSubtotalProviderTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderItemsSubtotalProviderTest.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\PricingBundle\Tests\Unit\Provider; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use PHPUnit\Framework\TestCase; diff --git a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderLocalizationProviderTest.php b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderLocalizationProviderTest.php index 4cfc64321..f033e2768 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderLocalizationProviderTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Unit/Provider/OrderLocalizationProviderTest.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\OrderBundle\Tests\Unit\Provider; -use Marello\Bundle\LocaleBundle\Model\LocaleAwareInterface; +use Marello\Bundle\LocaleBundle\Model\LocalizationAwareInterface; use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Entity\OrderAwareInterface; use Marello\Bundle\OrderBundle\Provider\OrderLocalizationProvider; @@ -39,7 +39,7 @@ public function testOrderAwareEntity() $order ->expects(static::once()) ->method('getLocalization'); - $entity = $this->createMock([OrderAwareInterface::class, LocaleAwareInterface::class]); + $entity = $this->createMock([OrderAwareInterface::class, LocalizationAwareInterface::class]); $entity ->expects(static::once()) ->method('getOrder') diff --git a/src/Marello/Bundle/OrderBundle/Tests/Unit/Stub/StatusEnumClassStub.php b/src/Marello/Bundle/OrderBundle/Tests/Unit/Stub/StatusEnumClassStub.php new file mode 100644 index 000000000..172934723 --- /dev/null +++ b/src/Marello/Bundle/OrderBundle/Tests/Unit/Stub/StatusEnumClassStub.php @@ -0,0 +1,17 @@ +workflowManager = $this->createMock(WorkflowManager::class); - + $this->extension = new OrderExtension(); /** @var Registry $registry */ $registry = $this ->getMockBuilder(Registry::class) @@ -44,7 +36,6 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->extension = new OrderExtension($this->workflowManager); $this->extension ->setItemsForNotificationProvider($orderItemsForNotificationProvider) ->setRegistry($registry); @@ -94,12 +85,22 @@ public function testGetFunctionsAreRegisteredInExtension() */ public function testIfOrderCanReturnIsTrue() { - /** @var Order $order */ + /** @var Order|\PHPUnit_Framework_MockObject_MockObject $order */ $order = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() ->getMock(); - $orderItem = new OrderItem(); - $orderItem->setStatus(LoadOrderItemStatusData::SHIPPED); + + $orderItemStatusMock = $this->createMock(StatusEnumClassStub::class); + $orderItemStatusMock + ->expects(static::atLeastOnce()) + ->method('getId') + ->willReturn(LoadOrderItemStatusData::SHIPPED); + + $orderItem = $this->createMock(OrderItem::class); + $orderItem + ->expects(static::atLeastOnce()) + ->method('getStatus') + ->willReturn($orderItemStatusMock); $order ->expects($this->once()) ->method('getItems') @@ -113,12 +114,23 @@ public function testIfOrderCanReturnIsTrue() */ public function testIfOrderCanReturnIsFalse() { - /** @var Order $order */ + /** @var Order|\PHPUnit_Framework_MockObject_MockObject $order */ $order = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() ->getMock(); - $orderItem = new OrderItem(); - $orderItem->setStatus(LoadOrderItemStatusData::PROCESSING); + + $orderItemStatusMock = $this->createMock(StatusEnumClassStub::class); + $orderItemStatusMock + ->expects(static::atLeastOnce()) + ->method('getId') + ->willReturn(LoadOrderItemStatusData::PROCESSING); + + $orderItem = $this->createMock(OrderItem::class); + $orderItem + ->expects(static::atLeastOnce()) + ->method('getStatus') + ->willReturn($orderItemStatusMock); + $order ->expects($this->once()) ->method('getItems') diff --git a/src/Marello/Bundle/OrderBundle/Tests/Unit/Validator/AvailableInventoryValidatorTest.php b/src/Marello/Bundle/OrderBundle/Tests/Unit/Validator/AvailableInventoryValidatorTest.php index 8d9ba8817..852e967a2 100644 --- a/src/Marello/Bundle/OrderBundle/Tests/Unit/Validator/AvailableInventoryValidatorTest.php +++ b/src/Marello/Bundle/OrderBundle/Tests/Unit/Validator/AvailableInventoryValidatorTest.php @@ -333,9 +333,10 @@ protected function getValidator() { $validator = new AvailableInventoryValidator( $this->doctrineHelper, - $this->inventoryProvider + $this->inventoryProvider, + $this->eventDispatcher ); - $validator->setEventDispatcher($this->eventDispatcher); + $validator->initialize($this->context); return $validator; diff --git a/src/Marello/Bundle/OrderBundle/Twig/OrderExtension.php b/src/Marello/Bundle/OrderBundle/Twig/OrderExtension.php index 1a58ac405..c387c1177 100644 --- a/src/Marello/Bundle/OrderBundle/Twig/OrderExtension.php +++ b/src/Marello/Bundle/OrderBundle/Twig/OrderExtension.php @@ -3,16 +3,15 @@ namespace Marello\Bundle\OrderBundle\Twig; use Doctrine\Bundle\DoctrineBundle\Registry; - -use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper; -use Oro\Bundle\WorkflowBundle\Model\WorkflowManager; - use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Entity\OrderItem; use Marello\Bundle\OrderBundle\Migrations\Data\ORM\LoadOrderItemStatusData; use Marello\Bundle\OrderBundle\Provider\OrderItem\ShippingPreparedOrderItemsForNotificationProvider; +use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; -class OrderExtension extends \Twig_Extension +class OrderExtension extends AbstractExtension { const NAME = 'marello_order'; @@ -22,19 +21,6 @@ class OrderExtension extends \Twig_Extension /** @var ShippingPreparedOrderItemsForNotificationProvider $orderItemsForNotificationProvider*/ private $orderItemsForNotificationProvider; - /** @var WorkflowManager $workflowManager */ - protected $workflowManager; - - /** - * ProductExtension constructor. - * - * @param WorkflowManager $workflowManager - */ - public function __construct(WorkflowManager $workflowManager) - { - $this->workflowManager = $workflowManager; - } - /** * Returns the name of the extension. * @@ -53,19 +39,19 @@ public function getName() public function getFunctions() { return [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_order_can_return', [$this, 'canReturn'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_order_item_shipped', [$this, 'isShippedOrderItem'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_get_order_item_status', [$this, 'findStatusByName'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_get_order_items_for_notification', [$this->orderItemsForNotificationProvider, 'getItems'] ) @@ -80,7 +66,7 @@ public function getFunctions() public function canReturn(Order $order) { foreach ($order->getItems() as $orderItem) { - if (!in_array($orderItem->getStatus(), $this->getOrderItemStatuses())) { + if (!in_array($orderItem->getStatus()->getId(), $this->getOrderItemStatuses(), true)) { return false; } } @@ -94,7 +80,7 @@ public function canReturn(Order $order) */ public function isShippedOrderItem(OrderItem $orderItem) { - if (in_array($orderItem->getStatus(), $this->getOrderItemStatuses())) { + if (in_array($orderItem->getStatus()->getId(), $this->getOrderItemStatuses(), true)) { return true; } diff --git a/src/Marello/Bundle/OrderBundle/Validator/AvailableInventoryValidator.php b/src/Marello/Bundle/OrderBundle/Validator/AvailableInventoryValidator.php index ea3707f08..538152e1e 100644 --- a/src/Marello/Bundle/OrderBundle/Validator/AvailableInventoryValidator.php +++ b/src/Marello/Bundle/OrderBundle/Validator/AvailableInventoryValidator.php @@ -55,10 +55,12 @@ class AvailableInventoryValidator extends ConstraintValidator */ public function __construct( DoctrineHelper $doctrineHelper, - AvailableInventoryProvider $availableInventoryProvider + AvailableInventoryProvider $availableInventoryProvider, + EventDispatcherInterface $eventDispatcher ) { $this->doctrineHelper = $doctrineHelper; $this->availableInventoryProvider = $availableInventoryProvider; + $this->eventDispatcher = $eventDispatcher; } /** @@ -339,17 +341,4 @@ private function getErrorPathFromConfig(Constraint $constraint, $fields) { return null !== $constraint->errorPath ? $constraint->errorPath : $fields[0]; } - - /** - * Added for keeping BC - * @deprecated will be removed in 3.0 - * @param EventDispatcherInterface $eventDispatcher - * @return $this - */ - public function setEventDispatcher(EventDispatcherInterface $eventDispatcher) - { - $this->eventDispatcher = $eventDispatcher; - - return $this; - } } diff --git a/src/Marello/Bundle/OrderBundle/Workflow/OrderShipAction.php b/src/Marello/Bundle/OrderBundle/Workflow/OrderShipAction.php index 91241948e..a18e65e74 100644 --- a/src/Marello/Bundle/OrderBundle/Workflow/OrderShipAction.php +++ b/src/Marello/Bundle/OrderBundle/Workflow/OrderShipAction.php @@ -3,19 +3,17 @@ namespace Marello\Bundle\OrderBundle\Workflow; use Doctrine\Bundle\DoctrineBundle\Registry; - +use Marello\Bundle\InventoryBundle\Entity\InventoryBatch; +use Marello\Bundle\InventoryBundle\Entity\Warehouse; +use Marello\Bundle\InventoryBundle\Event\InventoryUpdateEvent; use Marello\Bundle\InventoryBundle\Model\InventoryUpdateContextFactory; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; - -use Oro\Bundle\WorkflowBundle\Entity\WorkflowItem; -use Oro\Component\ConfigExpression\ContextAccessor; - +use Marello\Bundle\InventoryBundle\Provider\OrderWarehousesProviderInterface; use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Entity\OrderItem; -use Marello\Bundle\InventoryBundle\Event\InventoryUpdateEvent; -use Marello\Bundle\InventoryBundle\Model\InventoryUpdateContext; - -use Marello\Bundle\InventoryBundle\Provider\OrderWarehousesProviderInterface; +use Marello\Bundle\PackingBundle\Entity\PackingSlipItem; +use Oro\Bundle\WorkflowBundle\Entity\WorkflowItem; +use Oro\Component\ConfigExpression\ContextAccessor; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class OrderShipAction extends OrderTransitionAction { @@ -71,6 +69,7 @@ protected function executeAction($context) * @param $inventoryUpdateQty * @param $allocatedInventoryQty * @param Order $entity + * @param Warehouse $warehouse */ protected function handleInventoryUpdate($item, $inventoryUpdateQty, $allocatedInventoryQty, $entity, $warehouse) { @@ -82,7 +81,33 @@ protected function handleInventoryUpdate($item, $inventoryUpdateQty, $allocatedI 'order_workflow.shipped', $entity ); - + $packingSlipItem = $this->doctrine + ->getManagerForClass(PackingSlipItem::class) + ->getRepository(PackingSlipItem::class) + ->findOneBy(['orderItem' => $item]); + if ($packingSlipItem) { + if (!empty($packingSlipItem->getInventoryBatches())) { + $contextBranches = []; + foreach ($packingSlipItem->getInventoryBatches() as $batchNumber => $qty) { + /** @var InventoryBatch[] $inventoryBatches */ + $inventoryBatches = $this->doctrine + ->getManagerForClass(InventoryBatch::class) + ->getRepository(InventoryBatch::class) + ->findBy(['batchNumber' => $batchNumber]); + $inventoryBatch = null; + foreach ($inventoryBatches as $batch) { + $inventoryLevel = $batch->getInventoryLevel(); + if ($inventoryLevel && $inventoryLevel->getWarehouse() === $warehouse) { + $inventoryBatch = $batch; + } + } + if ($inventoryBatch) { + $contextBranches[] = ['batch' => $inventoryBatch, 'qty' => -$qty]; + } + } + $context->setInventoryBatches($contextBranches); + } + } $context->setValue('warehouse', $warehouse); $this->eventDispatcher->dispatch( diff --git a/src/Marello/Bundle/PackingBundle/Controller/PackingSlipController.php b/src/Marello/Bundle/PackingBundle/Controller/PackingSlipController.php index 061a1f106..2e25f10b1 100644 --- a/src/Marello/Bundle/PackingBundle/Controller/PackingSlipController.php +++ b/src/Marello/Bundle/PackingBundle/Controller/PackingSlipController.php @@ -2,21 +2,20 @@ namespace Marello\Bundle\PackingBundle\Controller; -use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\PackingBundle\Entity\PackingSlip; -use Marello\Bundle\ReturnBundle\Entity\ReturnEntity; -use Marello\Bundle\ReturnBundle\Form\Type\ReturnUpdateType; - -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Symfony\Component\HttpFoundation\Request; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Routing\Annotation\Route; -class PackingSlipController extends Controller +class PackingSlipController extends AbstractController { /** - * @Config\Route("/", name="marello_packing_packingslip_index") - * @Config\Template + * @Route( + * path="/", + * name="marello_packing_packingslip_index" + * ) + * @Template("MarelloPackingBundle:PackingSlip:index.html.twig") * @AclAncestor("marello_packing_slip_view") */ public function indexAction() @@ -25,8 +24,12 @@ public function indexAction() } /** - * @Config\Route("/view/{id}", requirements={"id"="\d+"}, name="marello_packing_packingslip_view") - * @Config\Template + * @Route( + * path="/view/{id}", + * requirements={"id"="\d+"}, + * name="marello_packing_packingslip_view" + * ) + * @Template("MarelloPackingBundle:PackingSlip:view.html.twig") * @AclAncestor("marello_packing_slip_view") * * @param PackingSlip $packingSlip diff --git a/src/Marello/Bundle/PackingBundle/Entity/PackingSlip.php b/src/Marello/Bundle/PackingBundle/Entity/PackingSlip.php index 2430d4c1e..927ba754d 100644 --- a/src/Marello/Bundle/PackingBundle/Entity/PackingSlip.php +++ b/src/Marello/Bundle/PackingBundle/Entity/PackingSlip.php @@ -2,22 +2,26 @@ namespace Marello\Bundle\PackingBundle\Entity; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Marello\Bundle\AddressBundle\Entity\MarelloAddress; -use Marello\Bundle\CoreBundle\DerivedProperty\DerivedPropertyAwareInterface; -use Marello\Bundle\CoreBundle\Model\EntityCreatedUpdatedAtTrait; -use Marello\Bundle\InventoryBundle\Entity\Warehouse; -use Marello\Bundle\OrderBundle\Entity\Customer; -use Marello\Bundle\OrderBundle\Entity\Order; -use Marello\Bundle\PackingBundle\Model\ExtendPackingSlip; -use Marello\Bundle\SalesBundle\Entity\SalesChannel; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\ArrayCollection; + +use Symfony\Component\Validator\Constraints as Assert; + use Oro\Bundle\AddressBundle\Entity\AbstractAddress; use Oro\Bundle\EntityConfigBundle\Metadata\Annotation as Oro; use Oro\Bundle\OrganizationBundle\Entity\OrganizationAwareInterface; use Oro\Bundle\OrganizationBundle\Entity\Ownership\AuditableOrganizationAwareTrait; -use Symfony\Component\Validator\Constraints as Assert; + +use Marello\Bundle\OrderBundle\Entity\Order; +use Marello\Bundle\CustomerBundle\Entity\Customer; +use Marello\Bundle\SalesBundle\Entity\SalesChannel; +use Marello\Bundle\InventoryBundle\Entity\Warehouse; +use Marello\Bundle\AddressBundle\Entity\MarelloAddress; +use Marello\Bundle\PackingBundle\Model\ExtendPackingSlip; +use Marello\Bundle\CoreBundle\Model\EntityCreatedUpdatedAtTrait; +use Marello\Bundle\SalesBundle\Model\SalesChannelAwareInterface; +use Marello\Bundle\CoreBundle\DerivedProperty\DerivedPropertyAwareInterface; /** * @ORM\Entity() @@ -43,7 +47,10 @@ * @ORM\Table(name="marello_packing_packing_slip") * @ORM\HasLifecycleCallbacks() */ -class PackingSlip extends ExtendPackingSlip implements DerivedPropertyAwareInterface, OrganizationAwareInterface +class PackingSlip extends ExtendPackingSlip implements + DerivedPropertyAwareInterface, + OrganizationAwareInterface, + SalesChannelAwareInterface { use EntityCreatedUpdatedAtTrait; use AuditableOrganizationAwareTrait; @@ -118,7 +125,7 @@ class PackingSlip extends ExtendPackingSlip implements DerivedPropertyAwareInter protected $shippingAddress; /** - * @ORM\ManyToOne(targetEntity="Marello\Bundle\OrderBundle\Entity\Customer", cascade={"persist"}) + * @ORM\ManyToOne(targetEntity="Marello\Bundle\CustomerBundle\Entity\Customer", cascade={"persist"}) * @ORM\JoinColumn(name="customer_id", referencedColumnName="id", nullable=false) * @Oro\ConfigField( * defaultValues={ diff --git a/src/Marello/Bundle/PackingBundle/Entity/PackingSlipItem.php b/src/Marello/Bundle/PackingBundle/Entity/PackingSlipItem.php index b6f1350c2..53127e70c 100644 --- a/src/Marello/Bundle/PackingBundle/Entity/PackingSlipItem.php +++ b/src/Marello/Bundle/PackingBundle/Entity/PackingSlipItem.php @@ -3,16 +3,15 @@ namespace Marello\Bundle\PackingBundle\Entity; use Doctrine\ORM\Mapping as ORM; - +use JMS\Serializer\Annotation as JMS; +use Marello\Bundle\CoreBundle\Model\EntityCreatedUpdatedAtTrait; +use Marello\Bundle\OrderBundle\Entity\OrderItem; +use Marello\Bundle\PackingBundle\Model\ExtendPackingSlipItem; +use Marello\Bundle\ProductBundle\Entity\Product; use Oro\Bundle\EntityConfigBundle\Metadata\Annotation as Oro; use Oro\Bundle\OrganizationBundle\Entity\OrganizationAwareInterface; use Oro\Bundle\OrganizationBundle\Entity\Ownership\AuditableOrganizationAwareTrait; -use Marello\Bundle\OrderBundle\Entity\OrderItem; -use Marello\Bundle\ProductBundle\Entity\Product; -use Marello\Bundle\PackingBundle\Model\ExtendPackingSlipItem; -use Marello\Bundle\CoreBundle\Model\EntityCreatedUpdatedAtTrait; - /** * @ORM\Entity() * @Oro\Config( @@ -172,6 +171,20 @@ class PackingSlipItem extends ExtendPackingSlipItem implements OrganizationAware */ protected $status; + /** + * @ORM\Column(name="inventory_batches", type="json_array", nullable=true) + * @Oro\ConfigField( + * defaultValues={ + * "dataaudit"={ + * "auditable"=true + * } + * } + * ) + * + * @var array + */ + protected $inventoryBatches; + /** * @ORM\PrePersist */ @@ -370,4 +383,23 @@ public function setStatus($status) return $this; } + + /** + * @return array + */ + public function getInventoryBatches() + { + return $this->inventoryBatches; + } + + /** + * @param array $batches + * @return $this + */ + public function setInventoryBatches(array $batches) + { + $this->inventoryBatches = $batches; + + return $this; + } } diff --git a/src/Marello/Bundle/PackingBundle/EventListener/Datagrid/PackingslipItemsBatchNumbersColumnListener.php b/src/Marello/Bundle/PackingBundle/EventListener/Datagrid/PackingslipItemsBatchNumbersColumnListener.php new file mode 100644 index 000000000..3183e47d8 --- /dev/null +++ b/src/Marello/Bundle/PackingBundle/EventListener/Datagrid/PackingslipItemsBatchNumbersColumnListener.php @@ -0,0 +1,30 @@ +getDatagrid(); + $records = $datagrid->getDatasource()->getResults(); + + $hasInventoryBatches = false; + foreach ($records as $k => $record) { + $value = $record->getValue('inventoryBatches'); + if (!empty($value)) { + $hasInventoryBatches = true; + break; + } + } + if ($hasInventoryBatches === false) { + $config = $datagrid->getConfig(); + $config->offsetSetByPath('[columns][inventoryBatches][renderable]', false); + } + } +} diff --git a/src/Marello/Bundle/PackingBundle/Mapper/OrderToPackingSlipMapper.php b/src/Marello/Bundle/PackingBundle/Mapper/OrderToPackingSlipMapper.php index ca7010929..0d234a7a3 100644 --- a/src/Marello/Bundle/PackingBundle/Mapper/OrderToPackingSlipMapper.php +++ b/src/Marello/Bundle/PackingBundle/Mapper/OrderToPackingSlipMapper.php @@ -4,17 +4,17 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; - -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; - -use Oro\Bundle\EntityBundle\Provider\EntityFieldProvider; - +use Marello\Bundle\InventoryBundle\Entity\InventoryBatch; +use Marello\Bundle\InventoryBundle\Entity\InventoryItem; +use Marello\Bundle\InventoryBundle\Entity\Warehouse; +use Marello\Bundle\InventoryBundle\Provider\OrderWarehousesProviderInterface; use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Entity\OrderItem; use Marello\Bundle\PackingBundle\Entity\PackingSlip; use Marello\Bundle\PackingBundle\Entity\PackingSlipItem; use Marello\Bundle\ProductBundle\Entity\Product; -use Marello\Bundle\InventoryBundle\Provider\OrderWarehousesProviderInterface; +use Oro\Bundle\EntityBundle\Provider\EntityFieldProvider; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; class OrderToPackingSlipMapper extends AbstractPackingSlipMapper { @@ -48,8 +48,9 @@ public function map($sourceEntity) $packingSlip = new PackingSlip(); $data = $this->getData($sourceEntity, PackingSlip::class); $data['order'] = $sourceEntity; - $data['warehouse'] = $result->getWarehouse(); - $data['items'] = $this->getItems($result->getOrderItems()); + $warehouse = $result->getWarehouse(); + $data['warehouse'] = $warehouse; + $data['items'] = $this->getItems($result->getOrderItems(), $warehouse); $this->assignData($packingSlip, $data); $packingSlips[] = $packingSlip; @@ -59,15 +60,16 @@ public function map($sourceEntity) /** * @param Collection $items + * @param Warehouse $warehouse * @return ArrayCollection */ - protected function getItems(Collection $items) + protected function getItems(Collection $items, Warehouse $warehouse) { $orderItems = $items->toArray(); $packingSlipItems = []; /** @var OrderItem $item */ foreach ($orderItems as $item) { - $packingSlipItems[] = $this->mapItem($item); + $packingSlipItems[] = $this->mapItem($item, $warehouse); } return new ArrayCollection($packingSlipItems); @@ -75,17 +77,49 @@ protected function getItems(Collection $items) /** * @param OrderItem $orderItem + * @param Warehouse $warehouse * @return PackingSlipItem */ - protected function mapItem(OrderItem $orderItem) + protected function mapItem(OrderItem $orderItem, Warehouse $warehouse) { $packingSlipItem = new PackingSlipItem(); - $packingSlipData = $this->getData($orderItem, PackingSlipItem::class); + $packingSlipItemData = $this->getData($orderItem, PackingSlipItem::class); /** @var Product $product */ $product = $orderItem->getProduct(); - $packingSlipData['weight'] = $product->getWeight(); - $packingSlipData['orderItem'] = $orderItem; - $this->assignData($packingSlipItem, $packingSlipData); + /** @var InventoryItem $inventoryItem */ + $inventoryItem = $product->getInventoryItems()->first(); + if ($inventoryItem) { + if ($inventoryLevel = $inventoryItem->getInventoryLevel($warehouse)) { + $inventoryBatches = $inventoryLevel->getInventoryBatches()->toArray(); + if (count($inventoryBatches) > 0) { + usort($inventoryBatches, function (InventoryBatch $a, InventoryBatch $b) { + if ($a->getDeliveryDate() < $b->getDeliveryDate()) { + return -1; + } elseif ($a->getDeliveryDate() > $b->getDeliveryDate()) { + return 1; + } else { + return 0; + } + }); + $data = []; + $quantity = $orderItem->getQuantity(); + /** @var InventoryBatch[] $inventoryBatches */ + foreach ($inventoryBatches as $inventoryBatch) { + if ($inventoryBatch->getQuantity() >= $quantity) { + $data[$inventoryBatch->getBatchNumber()] = $quantity; + break; + } elseif ($batchQty = $inventoryBatch->getQuantity() > 0) { + $data[$inventoryBatch->getBatchNumber()] = $batchQty; + $quantity = $quantity - $batchQty; + } + } + $packingSlipItemData['inventoryBatches'] = $data; + } + } + } + $packingSlipItemData['weight'] = $product->getWeight(); + $packingSlipItemData['orderItem'] = $orderItem; + $this->assignData($packingSlipItem, $packingSlipItemData); return $packingSlipItem; } diff --git a/src/Marello/Bundle/PackingBundle/Migrations/Schema/MarelloPackingBundleInstaller.php b/src/Marello/Bundle/PackingBundle/Migrations/Schema/MarelloPackingBundleInstaller.php index 467aece39..15ecb67ee 100644 --- a/src/Marello/Bundle/PackingBundle/Migrations/Schema/MarelloPackingBundleInstaller.php +++ b/src/Marello/Bundle/PackingBundle/Migrations/Schema/MarelloPackingBundleInstaller.php @@ -38,7 +38,7 @@ class MarelloPackingBundleInstaller implements */ public function getMigrationVersion() { - return 'v1_2'; + return 'v1_3'; } /** @@ -96,6 +96,7 @@ protected function createMarelloPackingSlipItemTable(Schema $schema) $table->addColumn('order_item_id', 'integer', []); $table->addColumn('weight', 'float', ['notnull' => true]); $table->addColumn('quantity', 'float', ['notnull' => true]); + $table->addColumn('inventory_batches', 'json_array', ['notnull' => false, 'comment' => '(DC2Type:json_array)']); $table->addColumn('comment', 'text', ['notnull' => false]); $table->addColumn('created_at', 'datetime'); $table->addColumn('updated_at', 'datetime', ['notnull' => false]); @@ -129,7 +130,7 @@ protected function addMarelloPackingSlipForeignKeys(Schema $schema) ['onDelete' => 'SET NULL', 'onUpdate' => null] ); $table->addForeignKeyConstraint( - $schema->getTable('marello_order_customer'), + $schema->getTable('marello_customer_customer'), ['customer_id'], ['id'], ['onDelete' => null, 'onUpdate' => null] diff --git a/src/Marello/Bundle/PackingBundle/Migrations/Schema/v1_3/MarelloPackingBundle.php b/src/Marello/Bundle/PackingBundle/Migrations/Schema/v1_3/MarelloPackingBundle.php new file mode 100644 index 000000000..9c819e770 --- /dev/null +++ b/src/Marello/Bundle/PackingBundle/Migrations/Schema/v1_3/MarelloPackingBundle.php @@ -0,0 +1,56 @@ +updateMarelloPackingSlipTable($schema); + $this->updateMarelloPackingSlipItemTable($schema); + } + + /** + * @param Schema $schema + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function updateMarelloPackingSlipTable(Schema $schema) + { + $table = $schema->getTable('marello_packing_packing_slip'); + if ($table->hasForeignKey('FK_B0E654D9395C3F3')) { + $table->removeForeignKey('FK_B0E654D9395C3F3'); + } + $table->addForeignKeyConstraint( + $schema->getTable('marello_customer_customer'), + ['customer_id'], + ['id'], + ['onDelete' => null, 'onUpdate' => null] + ); + } + + /** + * @param Schema $schema + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function updateMarelloPackingSlipItemTable(Schema $schema) + { + $table = $schema->getTable('marello_packing_pack_slip_item'); + $table->addColumn('inventory_batches', 'json_array', ['notnull' => false, 'comment' => '(DC2Type:json_array)']); + } + + /** + * {@inheritdoc} + */ + public function getOrder() + { + return 20; + } +} diff --git a/src/Marello/Bundle/PackingBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/PackingBundle/Resources/config/oro/datagrids.yml index 3ae3ba9ea..2d31ba27f 100644 --- a/src/Marello/Bundle/PackingBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/PackingBundle/Resources/config/oro/datagrids.yml @@ -48,7 +48,7 @@ datagrids: salesChannel: data_name: sc.name warehouse: - data_name: wh.name + data_name: wh.label billingAddress: data_name: ps.billingAddress shippingAddress: @@ -58,7 +58,7 @@ datagrids: updatedAt: data_name: ps.updatedAt default: - packingSlipNumber: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC" + packingSlipNumber: "DESC" filters: columns: packingSlipNumber: @@ -72,7 +72,7 @@ datagrids: data_name: sc.name warehouse: type: string - data_name: wh.name + data_name: wh.label billingAddress: type: string data_name: ps.billingAddress @@ -140,6 +140,11 @@ datagrids: label: marello.order.orderitem.status.label frontend_type: string data_name: status + inventoryBatches: + label: marello.packing.packingslipitem.inventory_batches.label + type: twig + frontend_type: html + template: MarelloPackingBundle:PackingSlip/Datagrid:inventoryBatches.html.twig createdAt: label: oro.ui.created_at frontend_type: datetime @@ -163,7 +168,7 @@ datagrids: updatedAt: data_name: ps.updatedAt default: - productSku: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC" + productSku: "ASC" totals: grand_total: columns: diff --git a/src/Marello/Bundle/PackingBundle/Resources/config/services.yml b/src/Marello/Bundle/PackingBundle/Resources/config/services.yml index d2e43439c..7b768071f 100644 --- a/src/Marello/Bundle/PackingBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/PackingBundle/Resources/config/services.yml @@ -22,6 +22,11 @@ services: tags: - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before.marello-packingslips, method: onBuildBefore } + marello_packing.listener.datagrid.packingslipitems_batchnumbers_column_listener: + class: 'Marello\Bundle\PackingBundle\EventListener\Datagrid\PackingslipItemsBatchNumbersColumnListener' + tags: + - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.after.marello-packingslip-items, method: onBuildAfter } + marello_packing.listener.doctrine.packing_slip_item_status: class: 'Marello\Bundle\PackingBundle\EventListener\Doctrine\PackingSlipItemStatusListener' arguments: diff --git a/src/Marello/Bundle/PackingBundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/PackingBundle/Resources/translations/messages.en.yml index 9b79998b7..264fcb119 100644 --- a/src/Marello/Bundle/PackingBundle/Resources/translations/messages.en.yml +++ b/src/Marello/Bundle/PackingBundle/Resources/translations/messages.en.yml @@ -37,6 +37,7 @@ marello: comment.label: Comment status.label: Status organization.label: Organization + inventory_batches.label: Inventory Batches datagrid: columns: diff --git a/src/Marello/Bundle/PackingBundle/Resources/views/PackingSlip/Datagrid/inventoryBatches.html.twig b/src/Marello/Bundle/PackingBundle/Resources/views/PackingSlip/Datagrid/inventoryBatches.html.twig new file mode 100644 index 000000000..525c27729 --- /dev/null +++ b/src/Marello/Bundle/PackingBundle/Resources/views/PackingSlip/Datagrid/inventoryBatches.html.twig @@ -0,0 +1,3 @@ +{% for batchNumber, quantity in record.getValue('inventoryBatches') %} + {{ batchNumber ~ ': ' ~ quantity }}
+{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/PackingBundle/Tests/Unit/Entity/PackingSlipTest.php b/src/Marello/Bundle/PackingBundle/Tests/Unit/Entity/PackingSlipTest.php index 0c59d719f..2cd260179 100644 --- a/src/Marello/Bundle/PackingBundle/Tests/Unit/Entity/PackingSlipTest.php +++ b/src/Marello/Bundle/PackingBundle/Tests/Unit/Entity/PackingSlipTest.php @@ -8,7 +8,7 @@ use Oro\Bundle\OrganizationBundle\Entity\Organization; use Marello\Bundle\OrderBundle\Entity\Order; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\SalesBundle\Entity\SalesChannel; use Marello\Bundle\InventoryBundle\Entity\Warehouse; use Marello\Bundle\PackingBundle\Entity\PackingSlip; diff --git a/src/Marello/Bundle/PackingBundle/Tests/Unit/Mapper/OrderToPackingSlipMapperTest.php b/src/Marello/Bundle/PackingBundle/Tests/Unit/Mapper/OrderToPackingSlipMapperTest.php index 7095940ac..8e03942fb 100644 --- a/src/Marello/Bundle/PackingBundle/Tests/Unit/Mapper/OrderToPackingSlipMapperTest.php +++ b/src/Marello/Bundle/PackingBundle/Tests/Unit/Mapper/OrderToPackingSlipMapperTest.php @@ -4,6 +4,9 @@ use Doctrine\Common\Collections\ArrayCollection; +use Marello\Bundle\InventoryBundle\Entity\InventoryBatch; +use Marello\Bundle\InventoryBundle\Entity\InventoryItem; +use Marello\Bundle\InventoryBundle\Entity\InventoryLevel; use Symfony\Component\PropertyAccess\PropertyAccess; use PHPUnit\Framework\TestCase; @@ -13,7 +16,7 @@ use Oro\Bundle\EntityBundle\Provider\EntityFieldProvider; use Marello\Bundle\OrderBundle\Entity\Order; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\OrderBundle\Entity\OrderItem; use Marello\Bundle\SalesBundle\Entity\SalesChannel; @@ -89,11 +92,17 @@ public function testMap() $salesChannel = new SalesChannel(); $customer = new Customer(); $organization = new Organization(); - + $product1 = $this->getEntity(Product::class, ['id' => 1, 'weight' => 2]); $product2 = $this->getEntity(Product::class, ['id' => 2, 'weight' => 3]); $product3 = $this->getEntity(Product::class, ['id' => 3, 'weight' => 5]); + $inventoryLevel1 = $this->getEntity(InventoryLevel::class, ['id' => 1, 'warehouse' => $warehouse]); + $inventoryBatch1 = $this->getEntity(InventoryBatch::class, ['id' => 1, 'batchNumber' => '000001', 'quantity' => 5]); + $inventoryItem1 = new InventoryItem($product1); + $inventoryLevel1->addInventoryBatch($inventoryBatch1); + $inventoryItem1->addInventoryLevel($inventoryLevel1); + $orderItem1 = $this->getEntity(OrderItem::class, ['id' => 1, 'product' => $product1, 'quantity' => 5]); $orderItem2 = $this->getEntity(OrderItem::class, ['id' => 2, 'product' => $product2, 'quantity' => 3]); $orderItem3 = $this->getEntity(OrderItem::class, ['id' => 3, 'product' => $product3, 'quantity' => 1]); @@ -111,6 +120,7 @@ public function testMap() $this->getEntity(PackingSlipItem::class, [ 'orderItem' => $orderItem1, 'product' => $product1, + 'inventoryBatches' => ['000001' => 5], 'quantity' => $orderItem1->getQuantity(), 'weight' => $product1->getWeight() ]), diff --git a/src/Marello/Bundle/PaymentBundle/Action/Handler/PaymentMethodsConfigsRuleToggleStatusActionHandler.php b/src/Marello/Bundle/PaymentBundle/Action/Handler/PaymentMethodsConfigsRuleToggleStatusActionHandler.php new file mode 100644 index 000000000..80b89624e --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Action/Handler/PaymentMethodsConfigsRuleToggleStatusActionHandler.php @@ -0,0 +1,45 @@ +entityManager = $entityManager; + $this->value = (boolean)$value; + } + + /** + * @param PaymentMethodsConfigsRule $configsRule + * @return boolean + */ + public function handleAction(PaymentMethodsConfigsRule $configsRule) + { + if ($configsRule->getRule()->isSystem()) { + return false; + } + $configsRule->getRule()->setEnabled($this->value); + $this->entityManager->persist($configsRule); + $this->entityManager->flush(); + + return true; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Checker/PaymentMethodEnabledByIdentifierChecker.php b/src/Marello/Bundle/PaymentBundle/Checker/PaymentMethodEnabledByIdentifierChecker.php new file mode 100755 index 000000000..c0b4a86d2 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Checker/PaymentMethodEnabledByIdentifierChecker.php @@ -0,0 +1,31 @@ +paymentMethodProvider = $paymentMethodProvider; + } + + /** + * {@inheritDoc} + */ + public function isEnabled($identifier) + { + return $this->paymentMethodProvider->getPaymentMethod($identifier) !== null ? + $this->paymentMethodProvider->getPaymentMethod($identifier)->isEnabled() : + false; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Checker/PaymentMethodEnabledByIdentifierCheckerInterface.php b/src/Marello/Bundle/PaymentBundle/Checker/PaymentMethodEnabledByIdentifierCheckerInterface.php new file mode 100755 index 000000000..203f577ba --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Checker/PaymentMethodEnabledByIdentifierCheckerInterface.php @@ -0,0 +1,13 @@ +methodEnabledChecker = $methodEnabledChecker; + } + + /** + * {@inheritdoc} + */ + public function canBeEnabled(PaymentMethodsConfigsRule $rule) + { + foreach ($rule->getMethodConfigs() as $config) { + if ($this->methodEnabledChecker->isEnabled($config->getMethod())) { + return true; + } + } + + return false; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Checker/PaymentRuleEnabledCheckerInterface.php b/src/Marello/Bundle/PaymentBundle/Checker/PaymentRuleEnabledCheckerInterface.php new file mode 100755 index 000000000..dda0f43da --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Checker/PaymentRuleEnabledCheckerInterface.php @@ -0,0 +1,15 @@ +propertyPath = $option; + + if (!$this->propertyPath) { + throw new \InvalidArgumentException('Missing "method_identifier" option'); + } + + return $this; + } + + /** + * {@inheritDoc} + */ + protected function isConditionAllowed($context) + { + $paymentMethodIdentifier = $this->resolveValue($context, $this->propertyPath, false); + $methodConfigRules = $this->getRulesByMethod($paymentMethodIdentifier); + + return count($methodConfigRules) !== 0; + } + + /** + * @param $paymentMethodIdentifier + * + * @return PaymentMethodsConfigsRule[] + */ + abstract protected function getRulesByMethod($paymentMethodIdentifier); + + /** + * {@inheritDoc} + */ + public function toArray() + { + return $this->convertToArray([$this->propertyPath]); + } + + /** + * {@inheritDoc} + */ + public function compile($factoryAccessor) + { + return $this->convertToPhpCode([$this->propertyPath], $factoryAccessor); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Condition/HasApplicablePaymentMethods.php b/src/Marello/Bundle/PaymentBundle/Condition/HasApplicablePaymentMethods.php new file mode 100644 index 000000000..52f60ac0e --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Condition/HasApplicablePaymentMethods.php @@ -0,0 +1,103 @@ +paymentMethodProvider = $paymentMethodProvider; + $this->paymentMethodsViewsProvider = $paymentMethodsViewsProvider; + } + + /** + * {@inheritdoc} + */ + public function initialize(array $options) + { + if (array_key_exists('paymentContext', $options)) { + $this->paymentContext = $options['paymentContext']; + } elseif (array_key_exists(0, $options)) { + $this->paymentContext = $options[0]; + } + + if (!$this->paymentContext) { + throw new InvalidArgumentException('Missing "paymentContext" option'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return self::NAME; + } + + /** + * {@inheritdoc} + */ + protected function isConditionAllowed($context) + { + /** @var PaymentContextInterface $paymentContext */ + $paymentContext = $this->resolveValue($context, $this->paymentContext, false); + + $methodsData = []; + if (null !== $paymentContext) { + $methodsData = $this->paymentMethodsViewsProvider->getApplicableMethodsViews($paymentContext); + } + + return count($methodsData) !== 0; + } + + /** + * {@inheritdoc} + */ + public function toArray() + { + return $this->convertToArray([$this->paymentContext]); + } + + /** + * {@inheritdoc} + */ + public function compile($factoryAccessor) + { + return $this->convertToPhpCode([$this->paymentContext], $factoryAccessor); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Condition/PaymentMethodHasEnabledPaymentRules.php b/src/Marello/Bundle/PaymentBundle/Condition/PaymentMethodHasEnabledPaymentRules.php new file mode 100644 index 000000000..ae5a062a1 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Condition/PaymentMethodHasEnabledPaymentRules.php @@ -0,0 +1,35 @@ +repository = $repository; + } + + /** + * {@inheritDoc} + */ + protected function getRulesByMethod($paymentMethodIdentifier) + { + return $this->repository->getEnabledRulesByMethod($paymentMethodIdentifier); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Condition/PaymentMethodHasPaymentRules.php b/src/Marello/Bundle/PaymentBundle/Condition/PaymentMethodHasPaymentRules.php new file mode 100644 index 000000000..0e2cfedee --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Condition/PaymentMethodHasPaymentRules.php @@ -0,0 +1,35 @@ +repository = $repository; + } + + /** + * {@inheritDoc} + */ + protected function getRulesByMethod($paymentMethodIdentifier) + { + return $this->repository->getRulesByMethod($paymentMethodIdentifier); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Context/Builder/Basic/BasicPaymentContextBuilder.php b/src/Marello/Bundle/PaymentBundle/Context/Builder/Basic/BasicPaymentContextBuilder.php new file mode 100644 index 000000000..ebe533217 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Context/Builder/Basic/BasicPaymentContextBuilder.php @@ -0,0 +1,258 @@ +sourceEntity = $sourceEntity; + $this->sourceEntityIdentifier = $sourceEntityIdentifier; + $this->paymentLineItemCollectionFactory = $paymentLineItemCollectionFactory; + } + + /** + * {@inheritDoc} + */ + public function getResult() + { + $params = $this->getMandatoryParams(); + $params += $this->getOptionalParams(); + + return new PaymentContext($params); + } + + /** + * {@inheritDoc} + */ + public function setLineItems(PaymentLineItemCollectionInterface $lineItemCollection) + { + $this->lineItems = $lineItemCollection->toArray(); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function addLineItem(PaymentLineItemInterface $paymentLineItem) + { + $this->lineItems[] = $paymentLineItem; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setBillingAddress(AddressInterface $billingAddress) + { + $this->billingAddress = $billingAddress; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setShippingAddress(AddressInterface $shippingAddress) + { + $this->shippingAddress = $shippingAddress; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setShippingOrigin(AddressInterface $shippingOrigin) + { + $this->shippingOrigin = $shippingOrigin; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setPaymentMethod($paymentMethod) + { + $this->paymentMethod = $paymentMethod; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setCustomer(Customer $customer) + { + $this->customer = $customer; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setCompany(Company $company) + { + $this->company = $company; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setSubTotal(Price $subTotal) + { + $this->subTotal = $subTotal; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setCurrency($currency) + { + $this->currency = $currency; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setTotal($total) + { + $this->total = $total; + + return $this; + } + + /** + * @return array + */ + private function getMandatoryParams() + { + $lineItems = $this->paymentLineItemCollectionFactory->createPaymentLineItemCollection($this->lineItems); + $params = [ + PaymentContext::FIELD_SOURCE_ENTITY => $this->sourceEntity, + PaymentContext::FIELD_SOURCE_ENTITY_ID => $this->sourceEntityIdentifier, + PaymentContext::FIELD_LINE_ITEMS => $lineItems, + ]; + + return $params; + } + + /** + * @return array + */ + private function getOptionalParams() + { + $optionalParams = [ + PaymentContext::FIELD_CURRENCY => $this->currency, + PaymentContext::FIELD_SUBTOTAL => $this->subTotal, + PaymentContext::FIELD_BILLING_ADDRESS => $this->billingAddress, + PaymentContext::FIELD_SHIPPING_ADDRESS => $this->shippingAddress, + PaymentContext::FIELD_PAYMENT_METHOD => $this->paymentMethod, + PaymentContext::FIELD_CUSTOMER => $this->customer, + PaymentContext::FIELD_COMPANY => $this->company, + PaymentContext::FIELD_SHIPPING_ORIGIN => $this->shippingOrigin, + PaymentContext::FIELD_TOTAL => $this->total, + ]; + + // Exclude NULL elements. + $optionalParams = array_diff_key($optionalParams, array_filter($optionalParams, 'is_null')); + + return $optionalParams; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Context/Builder/Basic/Factory/BasicPaymentContextBuilderFactory.php b/src/Marello/Bundle/PaymentBundle/Context/Builder/Basic/Factory/BasicPaymentContextBuilderFactory.php new file mode 100644 index 000000000..c0325de2e --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Context/Builder/Basic/Factory/BasicPaymentContextBuilderFactory.php @@ -0,0 +1,35 @@ +collectionFactory = $collectionFactory; + } + + /** + * {@inheritDoc} + */ + public function createPaymentContextBuilder($sourceEntity, $sourceEntityId) + { + return new BasicPaymentContextBuilder( + $sourceEntity, + $sourceEntityId, + $this->collectionFactory + ); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Context/Builder/Factory/PaymentContextBuilderFactoryInterface.php b/src/Marello/Bundle/PaymentBundle/Context/Builder/Factory/PaymentContextBuilderFactoryInterface.php new file mode 100644 index 000000000..c822930e0 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Context/Builder/Factory/PaymentContextBuilderFactoryInterface.php @@ -0,0 +1,16 @@ +decoratedProductLineItemFactory = $decoratedProductLineItemFactory; + } + + /** + * @inheritDoc + */ + public function convert(PaymentContextInterface $paymentContext) + { + $lineItems = $paymentContext->getLineItems()->toArray(); + + return [ + 'lineItems' => array_map(function (PaymentLineItemInterface $lineItem) use ($lineItems) { + return $this->decoratedProductLineItemFactory + ->createLineItemWithDecoratedProductByLineItem($lineItems, $lineItem); + }, $lineItems), + 'billingAddress' => $paymentContext->getBillingAddress(), + 'shippingAddress' => $paymentContext->getShippingAddress(), + 'shippingOrigin' => $paymentContext->getShippingOrigin(), + 'paymentMethod' => $paymentContext->getPaymentMethod(), + 'currency' => $paymentContext->getCurrency(), + 'subtotal' => $paymentContext->getSubtotal(), + 'customer' => $paymentContext->getCustomer(), + 'company' => $paymentContext->getCompany(), + 'total' => $paymentContext->getTotal(), + ]; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Context/Converter/PaymentContextToRulesValueConverterInterface.php b/src/Marello/Bundle/PaymentBundle/Context/Converter/PaymentContextToRulesValueConverterInterface.php new file mode 100644 index 000000000..4cbfd8fd9 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Context/Converter/PaymentContextToRulesValueConverterInterface.php @@ -0,0 +1,15 @@ +factories = $factories; + } + + /** + * {@inheritDoc} + */ + public function create($entityClass, $entityId) + { + return $this->getFactory($entityClass, $entityId)->create($entityClass, $entityId); + } + + /** + * {@inheritDoc} + */ + public function supports($entityClass, $entityId) + { + foreach ($this->factories as $factory) { + if ($factory->supports($entityClass, $entityId)) { + return true; + } + } + + return false; + } + + /** + * @param $entityClass + * @param $entityId + * + * @return SupportsEntityPaymentContextFactoryInterface + * + * @throws UnsupportedEntityException + */ + protected function getFactory($entityClass, $entityId) + { + foreach ($this->factories as $factory) { + if ($factory->supports($entityClass, $entityId)) { + return $factory; + } + } + + $msg = sprintf( + 'Could not find payment context factory for given entity class (%s) and id (%d)', + $entityClass, + $entityId + ); + throw new UnsupportedEntityException($msg); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Context/Factory/Exception/UnsupportedEntityException.php b/src/Marello/Bundle/PaymentBundle/Context/Factory/Exception/UnsupportedEntityException.php new file mode 100644 index 000000000..5b5a26b55 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Context/Factory/Exception/UnsupportedEntityException.php @@ -0,0 +1,7 @@ +quantity = $quantity; + $this->productHolder = $productHolder; + } + + /** + * {@inheritDoc} + */ + public function getResult() + { + $params = [ + PaymentLineItem::FIELD_QUANTITY => $this->quantity, + PaymentLineItem::FIELD_PRODUCT_HOLDER => $this->productHolder, + PaymentLineItem::FIELD_ENTITY_IDENTIFIER => $this->productHolder->getId(), + ]; + + if (null !== $this->product) { + $params[PaymentLineItem::FIELD_PRODUCT] = $this->product; + } + + if (null !== $this->productSku) { + $params[PaymentLineItem::FIELD_PRODUCT_SKU] = $this->productSku; + } + + if (null !== $this->weight) { + $params[PaymentLineItem::FIELD_WEIGHT] = $this->weight; + } + + if (null !== $this->price) { + $params[PaymentLineItem::FIELD_PRICE] = $this->price; + } + + return new PaymentLineItem($params); + } + + /** + * {@inheritDoc} + */ + public function setProduct(Product $product) + { + $this->product = $product; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setProductSku($sku) + { + $this->productSku = $sku; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setWeight($weight) + { + $this->weight = $weight; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setPrice(Price $price) + { + $this->price = $price; + + return $this; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Context/LineItem/Builder/Basic/Factory/BasicPaymentLineItemBuilderFactory.php b/src/Marello/Bundle/PaymentBundle/Context/LineItem/Builder/Basic/Factory/BasicPaymentLineItemBuilderFactory.php new file mode 100644 index 000000000..564a19d0a --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Context/LineItem/Builder/Basic/Factory/BasicPaymentLineItemBuilderFactory.php @@ -0,0 +1,20 @@ +get(self::FIELD_COMPANY); + } + + /** + * {@inheritDoc} + */ + public function getCustomer() + { + return $this->get(self::FIELD_CUSTOMER); + } + + /** + * {@inheritDoc} + */ + public function getLineItems() + { + return $this->get(self::FIELD_LINE_ITEMS); + } + + /** + * {@inheritDoc} + */ + public function getBillingAddress() + { + return $this->get(self::FIELD_BILLING_ADDRESS); + } + + /** + * {@inheritDoc} + */ + public function getShippingAddress() + { + return $this->get(self::FIELD_SHIPPING_ADDRESS); + } + + /** + * {@inheritDoc} + */ + public function getShippingOrigin() + { + return $this->get(self::FIELD_SHIPPING_ORIGIN); + } + + /** + * {@inheritDoc} + */ + public function getPaymentMethod() + { + return $this->get(self::FIELD_PAYMENT_METHOD); + } + + /** + * {@inheritDoc} + */ + public function getCurrency() + { + return $this->get(self::FIELD_CURRENCY); + } + + /** + * {@inheritDoc} + */ + public function getSubtotal() + { + return $this->get(self::FIELD_SUBTOTAL); + } + + /** + * {@inheritDoc} + */ + public function getSourceEntity() + { + return $this->get(self::FIELD_SOURCE_ENTITY); + } + + /** + * {@inheritDoc} + */ + public function getSourceEntityIdentifier() + { + return $this->get(self::FIELD_SOURCE_ENTITY_ID); + } + + /** + * {@inheritDoc} + */ + public function getTotal() + { + return $this->get(self::FIELD_TOTAL); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Context/PaymentContextFactoryInterface.php b/src/Marello/Bundle/PaymentBundle/Context/PaymentContextFactoryInterface.php new file mode 100644 index 000000000..66b75f004 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Context/PaymentContextFactoryInterface.php @@ -0,0 +1,13 @@ +get(self::FIELD_PRICE); + } + + /** + * {@inheritDoc} + */ + public function getProduct() + { + return $this->get(self::FIELD_PRODUCT); + } + + /** + * {@inheritDoc} + */ + public function getProductHolder() + { + return $this->get(self::FIELD_PRODUCT_HOLDER); + } + + /** + * {@inheritDoc} + */ + public function getProductSku() + { + return $this->get(self::FIELD_PRODUCT_SKU); + } + + /** + * {@inheritDoc} + */ + public function getEntityIdentifier() + { + return $this->get(self::FIELD_ENTITY_IDENTIFIER); + } + + /** + * {@inheritDoc} + */ + public function getQuantity() + { + return $this->get(self::FIELD_QUANTITY); + } + + /** + * {@inheritDoc} + */ + public function getWeight() + { + return $this->get(self::FIELD_WEIGHT); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Context/PaymentLineItemInterface.php b/src/Marello/Bundle/PaymentBundle/Context/PaymentLineItemInterface.php new file mode 100644 index 000000000..377044bad --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Context/PaymentLineItemInterface.php @@ -0,0 +1,46 @@ + PaymentMethodsConfigsRule::class + ]; + } + + /** + * @Route("/create", name="marello_payment_methods_configs_rule_create") + * @Template("MarelloPaymentBundle:PaymentMethodsConfigsRule:update.html.twig") + * @Acl( + * id="marello_payment_methods_configs_rule_create", + * type="entity", + * permission="CREATE", + * class="MarelloPaymentBundle:PaymentMethodsConfigsRule" + * ) + * + * @param Request $request + * @return array + */ + public function createAction(Request $request) + { + return $this->update(new PaymentMethodsConfigsRule(), $request); + } + + /** + * @Route("/view/{id}", name="marello_payment_methods_configs_rule_view", requirements={"id"="\d+"}) + * @Template("MarelloPaymentBundle:PaymentMethodsConfigsRule:view.html.twig") + * @Acl( + * id="marello_payment_methods_configs_rule_view", + * type="entity", + * class="MarelloPaymentBundle:PaymentMethodsConfigsRule", + * permission="VIEW" + * ) + * + * @param PaymentMethodsConfigsRule $paymentMethodsConfigsRule + * + * @return array + */ + public function viewAction(PaymentMethodsConfigsRule $paymentMethodsConfigsRule) + { + return [ + 'entity' => $paymentMethodsConfigsRule, + ]; + } + + /** + * @Route("/update/{id}", name="marello_payment_methods_configs_rule_update", requirements={"id"="\d+"}) + * @Template("MarelloPaymentBundle:PaymentMethodsConfigsRule:update.html.twig") + * @Acl( + * id="marello_payment_methods_configs_rule_update", + * type="entity", + * permission="EDIT", + * class="MarelloPaymentBundle:PaymentMethodsConfigsRule" + * ) + * @param Request $request + * @param PaymentMethodsConfigsRule $entity + * + * @return array + */ + public function updateAction(Request $request, PaymentMethodsConfigsRule $entity) + { + return $this->update($entity, $request); + } + + /** + * @param PaymentMethodsConfigsRule $entity + * @param Request $request + * @return array|\Symfony\Component\HttpFoundation\RedirectResponse + */ + protected function update(PaymentMethodsConfigsRule $entity, Request $request) + { + $form = $this->createForm(PaymentMethodsConfigsRuleType::class); + if ($this->get('marello_payment.form.handler.payment_methods_configs_rule')->process($form, $entity)) { + $this->get('session')->getFlashBag()->add( + 'success', + $this->get('translator')->trans('marello.payment.controller.rule.saved.message') + ); + + return $this->get('oro_ui.router')->redirect($entity); + } + + if ($request->get(PaymentMethodsConfigsRuleHandler::UPDATE_FLAG, false)) { + // take different form due to JS validation should be shown even in case + // when it was not validated on backend + $form = $this->createForm(PaymentMethodsConfigsRuleType::class, $form->getData()); + } + + return [ + 'entity' => $entity, + 'form' => $form->createView() + ]; + } + + /** + * @Route("/{gridName}/massAction/{actionName}", name="marello_payment_methods_configs_massaction") + * @Acl( + * id="marello_payment_methods_configs_update", + * type="entity", + * permission="EDIT", + * class="MarelloPaymentBundle:PaymentMethodsConfigsRule" + * ) + * @CsrfProtection() + * + * @param string $gridName + * @param string $actionName + * @param Request $request + * + * @return JsonResponse + */ + public function markMassAction($gridName, $actionName, Request $request) + { + /** @var MassActionDispatcher $massActionDispatcher */ + $massActionDispatcher = $this->get('oro_datagrid.mass_action.dispatcher'); + + $response = $massActionDispatcher->dispatchByRequest($gridName, $actionName, $request); + + $data = [ + 'successful' => $response->isSuccessful(), + 'message' => $response->getMessage() + ]; + + return new JsonResponse(array_merge($data, $response->getOptions())); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/DependencyInjection/Compiler/CompositePaymentMethodProviderCompilerPass.php b/src/Marello/Bundle/PaymentBundle/DependencyInjection/Compiler/CompositePaymentMethodProviderCompilerPass.php new file mode 100644 index 000000000..a7d69982c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/DependencyInjection/Compiler/CompositePaymentMethodProviderCompilerPass.php @@ -0,0 +1,34 @@ +hasDefinition(self::COMPOSITE_SERVICE)) { + return; + } + + $taggedServices = $container->findTaggedServiceIds(self::TAG); + if (empty($taggedServices)) { + return; + } + + $compiledServiceDefinition = $container->getDefinition(self::COMPOSITE_SERVICE); + + foreach ($taggedServices as $method => $value) { + $compiledServiceDefinition->addMethodCall('addProvider', [new Reference($method)]); + } + } +} diff --git a/src/Marello/Bundle/PaymentBundle/DependencyInjection/Compiler/TwigSandboxConfigurationPass.php b/src/Marello/Bundle/PaymentBundle/DependencyInjection/Compiler/TwigSandboxConfigurationPass.php new file mode 100644 index 000000000..b5ae7f0c5 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/DependencyInjection/Compiler/TwigSandboxConfigurationPass.php @@ -0,0 +1,43 @@ +root(MarelloPaymentExtension::ALIAS); + + SettingsBuilder::append( + $rootNode, + [ + // General + self::MERCHANT_COUNTRY_KEY => [ + 'type' => 'text', + 'value' => LocaleConfiguration::DEFAULT_COUNTRY, + ], + ] + ); + + return $treeBuilder; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/DependencyInjection/MarelloPaymentExtension.php b/src/Marello/Bundle/PaymentBundle/DependencyInjection/MarelloPaymentExtension.php new file mode 100644 index 000000000..b7ce323ad --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/DependencyInjection/MarelloPaymentExtension.php @@ -0,0 +1,36 @@ +processConfiguration($configuration, $configs); + $container->prependExtensionConfig($this->getAlias(), $config); + + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.yml'); + $loader->load('mass_action.yml'); + $loader->load('form_types.yml'); + } + + /** + * {@inheritDoc} + */ + public function getAlias() + { + return self::ALIAS; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodAwareInterface.php b/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodAwareInterface.php new file mode 100644 index 000000000..25a5bc018 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodAwareInterface.php @@ -0,0 +1,17 @@ +id; + } + + /** + * @return PaymentMethodsConfigsRule + */ + public function getMethodsConfigsRule() + { + return $this->methodsConfigsRule; + } + + /** + * @param PaymentMethodsConfigsRule $methodsConfigsRule + * @return $this + */ + public function setMethodsConfigsRule($methodsConfigsRule) + { + $this->methodsConfigsRule = $methodsConfigsRule; + + return $this; + } + + /** + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * @param string $method + * @return $this + */ + public function setMethod($method) + { + $this->method = $method; + + return $this; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * @param array $options + * @return $this + */ + public function setOptions($options) + { + $this->options = $options; + + return $this; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodsConfigsRule.php b/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodsConfigsRule.php new file mode 100644 index 000000000..9ea57aa59 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodsConfigsRule.php @@ -0,0 +1,289 @@ +methodConfigs = new ArrayCollection(); + $this->destinations = new ArrayCollection(); + } + + /** + * {@inheritdoc} + */ + public function getRule() + { + return $this->rule; + } + + /** + * @param RuleInterface $rule + * + * @return $this + */ + public function setRule(RuleInterface $rule) + { + $this->rule = $rule; + + return $this; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @return Collection|PaymentMethodConfig[] + */ + public function getMethodConfigs() + { + return $this->methodConfigs; + } + + /** + * @param PaymentMethodConfig $methodConfig + * + * @return bool + */ + public function hasMethodConfig(PaymentMethodConfig $methodConfig) + { + return $this->methodConfigs->contains($methodConfig); + } + + /** + * @param PaymentMethodConfig $methodConfig + * + * @return $this + */ + public function addMethodConfig(PaymentMethodConfig $methodConfig) + { + if (!$this->hasMethodConfig($methodConfig)) { + $this->methodConfigs[] = $methodConfig; + $methodConfig->setMethodsConfigsRule($this); + } + + return $this; + } + + /** + * @param PaymentMethodConfig $methodConfig + * + * @return $this + */ + public function removeMethodConfig(PaymentMethodConfig $methodConfig) + { + if ($this->hasMethodConfig($methodConfig)) { + $this->methodConfigs->removeElement($methodConfig); + } + + return $this; + } + + /** + * @return Collection|PaymentMethodsConfigsRuleDestination[] + */ + public function getDestinations() + { + return $this->destinations; + } + + /** + * @param PaymentMethodsConfigsRuleDestination $destination + * + * @return $this + */ + public function addDestination(PaymentMethodsConfigsRuleDestination $destination) + { + if (!$this->destinations->contains($destination)) { + $this->destinations->add($destination); + $destination->setMethodsConfigsRule($this); + } + + return $this; + } + + /** + * @param PaymentMethodsConfigsRuleDestination $destination + * + * @return $this + */ + public function removeDestination(PaymentMethodsConfigsRuleDestination $destination) + { + if ($this->destinations->contains($destination)) { + $this->destinations->removeElement($destination); + } + + return $this; + } + + /** + * @return string + */ + public function getCurrency() + { + return $this->currency; + } + + /** + * @param string $currency + * + * @return $this + */ + public function setCurrency($currency) + { + $this->currency = $currency; + + return $this; + } + + /** + * @return OrganizationInterface + */ + public function getOrganization() + { + return $this->organization; + } + + /** + * @param OrganizationInterface $organization + * + * @return $this + */ + public function setOrganization(OrganizationInterface $organization) + { + $this->organization = $organization; + + return $this; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodsConfigsRuleDestination.php b/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodsConfigsRuleDestination.php new file mode 100644 index 000000000..7c2bad3f8 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodsConfigsRuleDestination.php @@ -0,0 +1,314 @@ +postalCodes = new ArrayCollection(); + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @return PaymentMethodsConfigsRule + */ + public function getMethodsConfigsRule() + { + return $this->methodsConfigsRule; + } + + /** + * @param PaymentMethodsConfigsRule $methodsConfigsRule + * @return $this + */ + public function setMethodsConfigsRule($methodsConfigsRule) + { + $this->methodsConfigsRule = $methodsConfigsRule; + + return $this; + } + + /** + * @return Collection|PaymentMethodsConfigsRuleDestinationPostalCode[] + */ + public function getPostalCodes() + { + return $this->postalCodes; + } + + /** + * @param PaymentMethodsConfigsRuleDestinationPostalCode $postalCode + * @return $this + */ + public function addPostalCode(PaymentMethodsConfigsRuleDestinationPostalCode $postalCode) + { + if (!$this->postalCodes->contains($postalCode)) { + $postalCode->setDestination($this); + $this->postalCodes->add($postalCode); + } + + return $this; + } + + /** + * @param PaymentMethodsConfigsRuleDestinationPostalCode $postalCode + * @return $this + */ + public function removePostalCode(PaymentMethodsConfigsRuleDestinationPostalCode $postalCode) + { + if ($this->postalCodes->contains($postalCode)) { + $this->postalCodes->removeElement($postalCode); + } + + return $this; + } + + /** + * @return Region + */ + public function getRegion() + { + return $this->region; + } + + /** + * @param Region $region + * @return $this + */ + public function setRegion($region) + { + $this->region = $region; + + return $this; + } + + /** + * Get name of region + * + * @return string + */ + public function getRegionName() + { + return $this->getRegion() ? $this->getRegion()->getName() : ''; + } + + /** + * Get code of region + * + * @return string + */ + public function getRegionCode() + { + return $this->getRegion() ? $this->getRegion()->getCode() : ''; + } + + /** + * @return string + */ + public function getRegionText() + { + return $this->regionText; + } + + /** + * @param string $regionText + * @return $this + */ + public function setRegionText($regionText) + { + $this->regionText = $regionText; + + return $this; + } + + /** + * @return Country + */ + public function getCountry() + { + return $this->country; + } + + /** + * Get name of country + * + * @return string + */ + public function getCountryName() + { + return $this->getCountry() ? $this->getCountry()->getName() : ''; + } + + /** + * @param Country $country + * @return $this + */ + public function setCountry($country) + { + $this->country = $country; + + return $this; + } + + /** + * Get country ISO2 code + * + * @return string + */ + public function getCountryIso2() + { + return $this->getCountry() ? $this->getCountry()->getIso2Code() : ''; + } + + /** + * Get country ISO3 code + * + * @return string + */ + public function getCountryIso3() + { + return $this->getCountry() ? $this->getCountry()->getIso3Code() : ''; + } + + /** + * Convert address to string + * + * @return string + */ + public function __toString() + { + $countryPostalStr = implode( + ' ', + array_filter([ + $this->getCountry(), + implode(', ', array_map(function (PaymentMethodsConfigsRuleDestinationPostalCode $postalCode) { + return (string)$postalCode; + }, $this->postalCodes->getValues())), + ]) + ); + + return implode(', ', array_filter([$this->getRegionName(), $countryPostalStr])); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodsConfigsRuleDestinationPostalCode.php b/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodsConfigsRuleDestinationPostalCode.php new file mode 100644 index 000000000..c5576c56a --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Entity/PaymentMethodsConfigsRuleDestinationPostalCode.php @@ -0,0 +1,125 @@ +id; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * @return PaymentMethodsConfigsRuleDestination + */ + public function getDestination() + { + return $this->destination; + } + + /** + * @param PaymentMethodsConfigsRuleDestination $destination + * @return $this + */ + public function setDestination(PaymentMethodsConfigsRuleDestination $destination) + { + $this->destination = $destination; + + return $this; + } + + /** + * @return mixed + */ + public function __toString() + { + return (string)$this->getName(); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Entity/Repository/PaymentMethodConfigRepository.php b/src/Marello/Bundle/PaymentBundle/Entity/Repository/PaymentMethodConfigRepository.php new file mode 100644 index 000000000..3ef96626d --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Entity/Repository/PaymentMethodConfigRepository.php @@ -0,0 +1,49 @@ +createQueryBuilder('methodConfig'); + + $qb->delete() + ->where( + $qb->expr()->eq('methodConfig.method', ':method') + ) + ->setParameter('method', $method); + + $qb->getQuery()->execute(); + } + + /** + * @param array $ids + */ + public function deleteByIds(array $ids) + { + $qb = $this->createQueryBuilder('methodConfig'); + $qb->delete() + ->where($qb->expr()->in('methodConfig.id', ':ids')) + ->setParameter('ids', $ids) + ->getQuery()->execute(); + } + + /** + * @param string|string[] $method + * + * @return PaymentMethodConfig[] + */ + public function findByMethod($method) + { + return $this->findBy([ + 'method' => $method + ]); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Entity/Repository/PaymentMethodsConfigsRuleRepository.php b/src/Marello/Bundle/PaymentBundle/Entity/Repository/PaymentMethodsConfigsRuleRepository.php new file mode 100644 index 000000000..8a65b1626 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Entity/Repository/PaymentMethodsConfigsRuleRepository.php @@ -0,0 +1,188 @@ +aclHelper = $aclHelper; + } + + /** + * @param AddressInterface $billingAddress + * @param string $currency + * + * @return PaymentMethodsConfigsRule[] + */ + public function getByDestinationAndCurrency( + AddressInterface $billingAddress, + string $currency + ): array { + $queryBuilder = $this->getByCurrencyQueryBuilder($currency) + ->leftJoin('methodsConfigsRule.destinations', 'destination') + ->leftJoin('methodsConfigsRule.rule', 'rule') + ->addSelect('rule', 'destination', 'postalCode') + ->leftJoin('destination.region', 'region') + ->leftJoin('destination.postalCodes', 'postalCode') + ->andWhere('destination.country = :country or destination.country is null') + ->andWhere('region.code = :regionCode or region.code is null') + ->andWhere('postalCode.name in (:postalCodes) or postalCode.name is null') + ->setParameter('country', $billingAddress->getCountryIso2()) + ->setParameter('regionCode', $billingAddress->getRegionCode()) + ->setParameter('postalCodes', explode(',', $billingAddress->getPostalCode())); + + return $this->aclHelper->apply($queryBuilder)->getResult(); + } + + /** + * @param string $currency + * + * @return PaymentMethodsConfigsRule[] + */ + public function getByCurrency(string $currency): array + { + $query = $this->getByCurrencyQueryBuilder($currency); + + return $this->aclHelper->apply($query)->getResult(); + } + + /** + * @param string $currency + * + * @return PaymentMethodsConfigsRule[] + */ + public function getByCurrencyWithoutDestination(string $currency): array + { + $query = $this->getByCurrencyQueryBuilder($currency) + ->leftJoin('methodsConfigsRule.destinations', 'destination') + ->andWhere('destination.id is null'); + + return $this->aclHelper->apply($query)->getResult(); + } + + /** + * @param string $methodId + * + * @return PaymentMethodsConfigsRule[] + */ + public function getConfigsWithEnabledRuleAndMethod($methodId) + { + $query = $this->createQueryBuilder('methodsConfigsRule') + ->innerJoin('methodsConfigsRule.methodConfigs', 'methodConfigs') + ->innerJoin('methodsConfigsRule.rule', 'rule') + ->andWhere('rule.enabled = true') + ->andWhere('methodConfigs.method = :methodId') + ->setParameter('methodId', $methodId); + + return $this->aclHelper->apply($query)->getResult(); + } + + /** + * @param bool $onlyEnabled + * + * @return mixed + */ + public function getRulesWithoutPaymentMethods($onlyEnabled = false) + { + $qb = $this->createQueryBuilder('methodsConfigsRule') + ->select('rule.id') + ->leftJoin('methodsConfigsRule.methodConfigs', 'methodConfigs') + ->leftJoin('methodsConfigsRule.rule', 'rule'); + if ($onlyEnabled) { + $qb->andWhere('rule.enabled = true'); + } + + return $qb + ->having('COUNT(methodConfigs.id) = 0') + ->groupBy('rule.id') + ->getQuery()->execute(); + } + + public function disableRulesWithoutPaymentMethods() + { + $rules = $this->getRulesWithoutPaymentMethods(true); + if (0 < count($rules)) { + $enabledRulesIds = array_column($rules, 'id'); + $qb = $this->createQueryBuilder('methodsConfigsRule'); + $qb->update('MarelloRuleBundle:Rule', 'rule') + ->set('rule.enabled', ':newValue') + ->setParameter('newValue', false) + ->where($qb->expr()->in('rule.id', ':rules')) + ->setParameter('rules', $enabledRulesIds) + ->getQuery()->execute(); + } + } + + /** + * @param string $currency + * + * @return QueryBuilder + */ + private function getByCurrencyQueryBuilder($currency): QueryBuilder + { + $queryBuilder = $this->createQueryBuilder('methodsConfigsRule'); + + return $queryBuilder + ->leftJoin('methodsConfigsRule.methodConfigs', 'methodConfigs') + ->where('methodsConfigsRule.currency = :currency') + ->orderBy($queryBuilder->expr()->asc('methodsConfigsRule.id')) + ->setParameter('currency', $currency); + } + + /** + * @param string $method + * + * @return PaymentMethodsConfigsRule[] + */ + public function getRulesByMethod($method) + { + $qb = $this->getRulesByMethodQueryBuilder($method); + + return $this->aclHelper->apply($qb)->getResult(); + } + + /** + * @param string $method + * + * @return PaymentMethodsConfigsRule[] + */ + public function getEnabledRulesByMethod($method) + { + $qb = $this->getRulesByMethodQueryBuilder($method) + ->innerJoin('methodsConfigsRule.rule', 'rule', Expr\Join::WITH, 'rule.enabled = true'); + + return $this->aclHelper->apply($qb)->getResult(); + } + + /** + * @param string $method + * + * @return QueryBuilder + */ + private function getRulesByMethodQueryBuilder($method) + { + return $this->createQueryBuilder('methodsConfigsRule') + ->innerJoin('methodsConfigsRule.methodConfigs', 'methodConfigs') + ->where('methodConfigs.method = :method') + ->setParameter('method', $method); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Event/ApplicablePaymentMethodViewEvent.php b/src/Marello/Bundle/PaymentBundle/Event/ApplicablePaymentMethodViewEvent.php new file mode 100644 index 000000000..ffeea75ba --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Event/ApplicablePaymentMethodViewEvent.php @@ -0,0 +1,121 @@ +paymentContext = $paymentContext; + $this->methodId = $methodId; + $this->methodLabel = $methodLabel; + $this->options = $options; + } + + /** + * @return string + */ + public function getMethodId() + { + return $this->methodId; + } + + /** + * @param string $methodId + * @return ApplicablePaymentMethodViewEvent + */ + public function setMethodId($methodId) + { + $this->methodId = $methodId; + + return $this; + } + + /** + * @return string + */ + public function getMethodLabel() + { + return $this->methodLabel; + } + + /** + * @param string $methodLabel + * @return ApplicablePaymentMethodViewEvent + */ + public function setMethodLabel($methodLabel) + { + $this->methodLabel = $methodLabel; + + return $this; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * @param array $options + * @return ApplicablePaymentMethodViewEvent + */ + public function setOptions($options) + { + $this->options = $options; + + return $this; + } + + /** + * @return PaymentContextInterface + */ + public function getPaymentContext() + { + return $this->paymentContext; + } + + /** + * @param PaymentContextInterface $paymentContext + * @return ApplicablePaymentMethodViewEvent + */ + public function setPaymentContext($paymentContext) + { + $this->paymentContext = $paymentContext; + + return $this; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Event/PaymentMethodConfigDataEvent.php b/src/Marello/Bundle/PaymentBundle/Event/PaymentMethodConfigDataEvent.php new file mode 100755 index 000000000..299e9673d --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Event/PaymentMethodConfigDataEvent.php @@ -0,0 +1,56 @@ +methodIdentifier = $identifier; + } + + /** + * @return int|string + */ + public function getMethodIdentifier() + { + return $this->methodIdentifier; + } + + /** + * Returns payment method config template + * + * @return string + */ + public function getTemplate() + { + return $this->template; + } + + /** + * @param string $template + * @return $this + */ + public function setTemplate($template) + { + $this->template = $template; + return $this; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/EventListener/PaymentRuleViewMethodTemplateListener.php b/src/Marello/Bundle/PaymentBundle/EventListener/PaymentRuleViewMethodTemplateListener.php new file mode 100644 index 000000000..0c3554149 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/EventListener/PaymentRuleViewMethodTemplateListener.php @@ -0,0 +1,39 @@ +template = $template; + $this->provider = $provider; + } + + /** + * @param PaymentMethodConfigDataEvent $event + */ + public function onGetConfigData(PaymentMethodConfigDataEvent $event) + { + if ($this->provider->hasPaymentMethod($event->getMethodIdentifier())) { + $event->setTemplate($this->template); + } + } +} diff --git a/src/Marello/Bundle/PaymentBundle/ExpressionLanguage/DecoratedProductLineItemFactory.php b/src/Marello/Bundle/PaymentBundle/ExpressionLanguage/DecoratedProductLineItemFactory.php new file mode 100644 index 000000000..21e1c9b99 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/ExpressionLanguage/DecoratedProductLineItemFactory.php @@ -0,0 +1,48 @@ +virtualFieldsProductDecoratorFactory = $virtualFieldsProductDecoratorFactory; + } + + /** + * @param PaymentLineItemInterface[] $lineItems + * @param PaymentLineItemInterface $lineItem + * + * @return PaymentLineItem + */ + public function createLineItemWithDecoratedProductByLineItem(array $lineItems, PaymentLineItemInterface $lineItem) + { + $product = $lineItem->getProduct(); + + $decoratedProduct = $product + ? $this->virtualFieldsProductDecoratorFactory->createDecoratedProductByProductHolders($lineItems, $product) + : null; + + return new PaymentLineItem( + [ + PaymentLineItem::FIELD_PRICE => $lineItem->getPrice(), + PaymentLineItem::FIELD_QUANTITY => $lineItem->getQuantity(), + PaymentLineItem::FIELD_PRODUCT_HOLDER => $lineItem->getProductHolder(), + PaymentLineItem::FIELD_PRODUCT_SKU => $lineItem->getProductSku(), + PaymentLineItem::FIELD_PRODUCT => $decoratedProduct, + ] + ); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/DataTransformer/DestinationPostalCodeTransformer.php b/src/Marello/Bundle/PaymentBundle/Form/DataTransformer/DestinationPostalCodeTransformer.php new file mode 100644 index 000000000..deb9449d6 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/DataTransformer/DestinationPostalCodeTransformer.php @@ -0,0 +1,52 @@ +getName() . ', '; + } + $postalCodesString = rtrim($postalCodesString, ', '); + + return $postalCodesString; + } + + /** + * @param string|null $postalCodesString + * @return ArrayCollection|PaymentMethodsConfigsRuleDestinationPostalCode[] + */ + public function reverseTransform($postalCodesString) + { + $postalCodes = new ArrayCollection(); + + if (!$postalCodesString || $postalCodesString === '') { + return $postalCodes; + } + + $postalCodeNames = explode(',', $postalCodesString); + foreach ($postalCodeNames as $postalCodeName) { + $postalCode = new PaymentMethodsConfigsRuleDestinationPostalCode(); + + $postalCode->setName(trim($postalCodeName)); + $postalCodes->add($postalCode); + } + + return $postalCodes; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/EventSubscriber/DestinationCollectionTypeSubscriber.php b/src/Marello/Bundle/PaymentBundle/Form/EventSubscriber/DestinationCollectionTypeSubscriber.php new file mode 100644 index 000000000..b6688fe17 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/EventSubscriber/DestinationCollectionTypeSubscriber.php @@ -0,0 +1,43 @@ + 'preSubmit' + ]; + } + + /** + * @param FormEvent $event + */ + public function preSubmit(FormEvent $event) + { + $data = $event->getData(); + + if (!$data || + !(is_array($data) && array_key_exists('destinations', $data)) || + !is_array($data['destinations']) + ) { + return; + } + + foreach ($data['destinations'] as $index => $destination) { + if (!array_filter($destination)) { + unset($data['destinations']); + } + } + + $event->setData($data); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/EventSubscriber/MethodConfigCollectionSubscriber.php b/src/Marello/Bundle/PaymentBundle/Form/EventSubscriber/MethodConfigCollectionSubscriber.php new file mode 100755 index 000000000..583d53d5c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/EventSubscriber/MethodConfigCollectionSubscriber.php @@ -0,0 +1,87 @@ +paymentMethodProvider = $paymentMethodProvider; + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + FormEvents::PRE_SET_DATA => 'preSet', + FormEvents::PRE_SUBMIT => 'preSubmit', + ]; + } + + /** + * @param FormEvent $event + */ + public function preSet(FormEvent $event) + { + /** @var Collection|PaymentMethodConfig[] $data */ + $data = $event->getData(); + $form = $event->getForm(); + + if (!$data) { + return; + } + + foreach ($data as $index => $methodConfig) { + $paymentMethod = $this->paymentMethodProvider->getPaymentMethod($methodConfig->getMethod()); + if (!$paymentMethod) { + $data->remove($index); + $form->remove($index); + } + } + $event->setData($data); + } + + /** + * @param FormEvent $event + */ + public function preSubmit(FormEvent $event) + { + /** @var array $data */ + $submittedData = $event->getData(); + $form = $event->getForm(); + + if (!$submittedData) { + return; + } + + $filteredSubmittedData = []; + foreach ($submittedData as $index => $itemData) { + if (array_key_exists('method', $itemData) + && $this->paymentMethodProvider->getPaymentMethod($itemData['method']) !== null + ) { + $filteredSubmittedData[$index] = $itemData; + } else { + $form->remove($index); + } + } + + $event->setData($filteredSubmittedData); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/EventSubscriber/MethodConfigSubscriber.php b/src/Marello/Bundle/PaymentBundle/Form/EventSubscriber/MethodConfigSubscriber.php new file mode 100755 index 000000000..6b491e79c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/EventSubscriber/MethodConfigSubscriber.php @@ -0,0 +1,89 @@ +factory = $factory; + $this->paymentMethodProvider = $paymentMethodProvider; + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + FormEvents::PRE_SET_DATA => 'preSet', + FormEvents::PRE_SUBMIT => 'preSubmit', + ]; + } + + /** + * @param FormEvent $event + */ + public function preSet(FormEvent $event) + { + /** @var PaymentMethodConfig $data */ + $data = $event->getData(); + if (!$data) { + return; + } + $this->recreateDynamicChildren($event->getForm(), $data->getMethod()); + } + + /** + * @param FormEvent $event + */ + public function preSubmit(FormEvent $event) + { + $submittedData = $event->getData(); + $form = $event->getForm(); + /** @var PaymentMethodConfig $data */ + $data = $form->getData(); + + if (!$data) { + $this->recreateDynamicChildren($form, $submittedData['method']); + $event->setData($submittedData); + } + } + + /** + * @param FormInterface $form + * @param string $method + */ + protected function recreateDynamicChildren(FormInterface $form, $method) + { + $paymentMethod = $this->paymentMethodProvider->getPaymentMethod($method); + + $oldOptions = $form->get('options')->getConfig()->getOptions(); + $child = $this->factory->createNamed('options', $paymentMethod->getOptionsConfigurationFormType()); + $form->add('options', $paymentMethod->getOptionsConfigurationFormType(), array_merge($oldOptions, [ + 'compound' => $child->getConfig()->getOptions()['compound'] + ])); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/Handler/PaymentMethodsConfigsRuleHandler.php b/src/Marello/Bundle/PaymentBundle/Form/Handler/PaymentMethodsConfigsRuleHandler.php new file mode 100644 index 000000000..013782148 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/Handler/PaymentMethodsConfigsRuleHandler.php @@ -0,0 +1,62 @@ +requestStack = $requestStack; + $this->em = $em; + } + + /** + * @param FormInterface $form + * @param PaymentMethodsConfigsRule $entity + * @return bool + */ + public function process(FormInterface $form, PaymentMethodsConfigsRule $entity) + { + $form->setData($entity); + + $request = $this->requestStack->getCurrentRequest(); + if (in_array($request->getMethod(), ['POST', 'PUT'], true)) { + $this->submitPostPutRequest($form, $request); + if (!$request->get(self::UPDATE_FLAG, false) && $form->isValid()) { + $this->em->persist($entity); + $this->em->flush(); + + return true; + } + } + + return false; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodConfigCollectionType.php b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodConfigCollectionType.php new file mode 100644 index 000000000..bb378dbde --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodConfigCollectionType.php @@ -0,0 +1,73 @@ +subscriber = $subscriber; + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->addEventSubscriber($this->subscriber); + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'show_form_when_empty' => false, + 'allow_add' => true, + 'entry_type' => PaymentMethodConfigType::class, + ]); + } + + /** + * {@inheritdoc} + */ + public function finishView(FormView $view, FormInterface $form, array $options) + { + $view->vars['allow_add'] = false; + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + return CollectionType::class; + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodConfigType.php b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodConfigType.php new file mode 100644 index 000000000..25c73d2b8 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodConfigType.php @@ -0,0 +1,87 @@ +subscriber = $subscriber; + $this->methodProvider = $methodProvider; + } + + /** + * {@inheritDoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('method', HiddenType::class, ['required' => true]); + $builder->add('options', HiddenType::class); + + $builder->addEventSubscriber($this->subscriber); + } + + /** + * {@inheritDoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options) + { + $methodsLabels = []; + $methodsIcons = []; + /* @var PaymentMethodInterface|PaymentMethodIconAwareInterface $method */ + foreach ($this->methodProvider->getPaymentMethods() as $method) { + $methodsLabels[$method->getIdentifier()] = $method->getLabel(); + $methodsIcons[$method->getIdentifier()] = $method->getIcon(); + } + $view->vars['methods_labels'] = $methodsLabels; + $view->vars['methods_icons'] = $methodsIcons; + } + + /** + * @param OptionsResolver $resolver + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => PaymentMethodConfig::class, + ]); + } + + /** + * {@inheritDoc} + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodSelectType.php b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodSelectType.php new file mode 100644 index 000000000..2261e9248 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodSelectType.php @@ -0,0 +1,62 @@ +paymentMethodChoicesProvider = $paymentMethodChoicesProvider; + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefaults([ + 'expanded' => true, + 'choices' => array_flip($this->getChoices()), + ]); + } + + /** + * @return array + */ + protected function getChoices() + { + return $this->paymentMethodChoicesProvider->getMethods(); + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + return ChoiceType::class; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodsConfigsRuleDestinationType.php b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodsConfigsRuleDestinationType.php new file mode 100644 index 000000000..25dfa374c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodsConfigsRuleDestinationType.php @@ -0,0 +1,89 @@ +subscriber = $subscriber; + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->addEventSubscriber($this->subscriber); + $builder->add('country', CountryType::class, [ + 'required' => true, + 'label' => 'oro.address.country.label', + //Used to show validation error for empty countries while creating rules + 'constraints' => new NotBlank(), + ]) + ->add('region', RegionType::class, ['required' => false, 'label' => 'oro.address.region.label']) + ->add( + 'region_text', + HiddenType::class, + ['required' => false, 'random_id' => true, 'label' => 'oro.address.region_text.label'] + ) + ->add('postalCodes', TextType::class, ['required' => false, 'label' => 'oro.address.postal_code.label']); + + $builder->get('postalCodes')->addModelTransformer(new DestinationPostalCodeTransformer()); + } + + /** + * @param OptionsResolver $resolver + * @throws AccessException + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => PaymentMethodsConfigsRuleDestination::class, + 'region_route' => 'oro_api_country_get_regions', + ]); + } + + /** + * {@inheritdoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options) + { + if (!empty($options['region_route'])) { + $view->vars['region_route'] = $options['region_route']; + } + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodsConfigsRuleType.php b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodsConfigsRuleType.php new file mode 100644 index 000000000..92087712d --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Form/Type/PaymentMethodsConfigsRuleType.php @@ -0,0 +1,95 @@ +provider = $provider; + $this->translator = $translator; + } + + /** + * {@inheritDoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('rule', RuleType::class, ['label' => 'marello.payment.paymentmethodsconfigsrule.rule.label']) + ->add('currency', CurrencySelectionType::class, [ + 'label' => 'marello.payment.paymentmethodsconfigsrule.currency.label', + 'placeholder' => 'oro.currency.currency.form.choose', + ]) + ->add('destinations', CollectionType::class, [ + 'required' => false, + 'entry_type' => PaymentMethodsConfigsRuleDestinationType::class, + 'label' => 'marello.payment.paymentmethodsconfigsrule.destinations.label', + 'show_form_when_empty' => false, + ]) + ->add('methodConfigs', PaymentMethodConfigCollectionType::class, [ + 'required' => false, + ]) + ->add('method', OroChoiceType::class, [ + 'mapped' => false, + 'choices' => array_flip($this->provider->getMethods()) + ]); + } + + /** + * {@inheritDoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['methods'] = $this->provider->getMethods(true); + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => PaymentMethodsConfigsRule::class + ]); + } + + /** + * {@inheritDoc} + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Formatter/PaymentMethodLabelFormatter.php b/src/Marello/Bundle/PaymentBundle/Formatter/PaymentMethodLabelFormatter.php new file mode 100755 index 000000000..4f2bbbbad --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Formatter/PaymentMethodLabelFormatter.php @@ -0,0 +1,33 @@ +paymentMethodProvider = $paymentMethodProvider; + } + + + /** + * @param string $paymentMethodName + * @return string + */ + public function formatPaymentMethodLabel($paymentMethodName) + { + $paymentMethod = $this->paymentMethodProvider->getPaymentMethod($paymentMethodName); + + return $paymentMethod->getLabel(); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/MarelloPaymentBundle.php b/src/Marello/Bundle/PaymentBundle/MarelloPaymentBundle.php new file mode 100644 index 000000000..f16f2f926 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/MarelloPaymentBundle.php @@ -0,0 +1,27 @@ +addCompilerPass(new TwigSandboxConfigurationPass()); + $container->addCompilerPass(new CompositePaymentMethodProviderCompilerPass()); + + parent::build($container); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Config/ParameterBag/AbstractParameterBagPaymentConfig.php b/src/Marello/Bundle/PaymentBundle/Method/Config/ParameterBag/AbstractParameterBagPaymentConfig.php new file mode 100644 index 000000000..62497252a --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Config/ParameterBag/AbstractParameterBagPaymentConfig.php @@ -0,0 +1,28 @@ +get(self::FIELD_LABEL); + } + + /** + * {@inheritdoc} + */ + public function getPaymentMethodIdentifier() + { + return $this->get(self::FIELD_PAYMENT_METHOD_IDENTIFIER); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Config/PaymentConfigInterface.php b/src/Marello/Bundle/PaymentBundle/Method/Config/PaymentConfigInterface.php new file mode 100644 index 000000000..7b3ce9163 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Config/PaymentConfigInterface.php @@ -0,0 +1,16 @@ +eventDispatcher = $eventDispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch($id) + { + $this->eventDispatcher->dispatch(MethodRemovalEvent::NAME, new MethodRemovalEvent($id)); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Event/BasicMethodRenamingEventDispatcher.php b/src/Marello/Bundle/PaymentBundle/Method/Event/BasicMethodRenamingEventDispatcher.php new file mode 100644 index 000000000..ddac0181a --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Event/BasicMethodRenamingEventDispatcher.php @@ -0,0 +1,29 @@ +eventDispatcher = $eventDispatcher; + } + + /** + * {@inheritDoc} + */ + public function dispatch($oldId, $newId) + { + $this->eventDispatcher->dispatch(MethodRenamingEvent::NAME, new MethodRenamingEvent($oldId, $newId)); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Event/MethodRemovalEvent.php b/src/Marello/Bundle/PaymentBundle/Method/Event/MethodRemovalEvent.php new file mode 100644 index 000000000..b0e03deb9 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Event/MethodRemovalEvent.php @@ -0,0 +1,31 @@ +methodId = $id; + } + + /** + * @return int|string + */ + public function getMethodIdentifier() + { + return $this->methodId; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Event/MethodRemovalEventDispatcherInterface.php b/src/Marello/Bundle/PaymentBundle/Method/Event/MethodRemovalEventDispatcherInterface.php new file mode 100644 index 000000000..a3023dab6 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Event/MethodRemovalEventDispatcherInterface.php @@ -0,0 +1,12 @@ +oldMethodId = $oldId; + $this->newMethodId = $newId; + } + + /** + * @return string + */ + public function getOldMethodIdentifier() + { + return $this->oldMethodId; + } + + /** + * @return string + */ + public function getNewMethodIdentifier() + { + return $this->newMethodId; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Event/MethodRenamingEventDispatcherInterface.php b/src/Marello/Bundle/PaymentBundle/Method/Event/MethodRenamingEventDispatcherInterface.php new file mode 100644 index 000000000..3c5802158 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Event/MethodRenamingEventDispatcherInterface.php @@ -0,0 +1,14 @@ +channelType = $channelType; + $this->identifierGenerator = $identifierGenerator; + $this->dispatcher = $dispatcher; + } + + /** + * @param ChannelDeleteEvent $event + */ + public function onRemove(ChannelDeleteEvent $event) + { + $channel = $event->getChannel(); + if ($this->channelType === $channel->getType()) { + $this->dispatcher->dispatch($this->identifierGenerator->generateIdentifier($channel)); + } + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/EventListener/MethodRemovalListener.php b/src/Marello/Bundle/PaymentBundle/Method/EventListener/MethodRemovalListener.php new file mode 100644 index 000000000..c6c870a5e --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/EventListener/MethodRemovalListener.php @@ -0,0 +1,77 @@ +doctrineHelper = $doctrineHelper; + $this->logger = $logger; + } + + /** + * @param MethodRemovalEvent $event + * @throws \Exception + */ + public function onMethodRemove(MethodRemovalEvent $event) + { + $methodId = $event->getMethodIdentifier(); + $connection = $this->getEntityManager()->getConnection(); + try { + $connection->beginTransaction(); + $this->getPaymentMethodConfigRepository()->deleteByMethod($methodId); + $this->getPaymentMethodsConfigsRuleRepository()->disableRulesWithoutPaymentMethods(); + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + $this->logger->critical($e->getMessage(), [ + 'payment_method_identifier' => $methodId + ]); + } + } + + /** + * @return \Doctrine\ORM\EntityManager|null + */ + private function getEntityManager() + { + return $this->doctrineHelper->getEntityManagerForClass('MarelloPaymentBundle:PaymentMethodsConfigsRule'); + } + + /** + * @return PaymentMethodConfigRepository|\Doctrine\ORM\EntityRepository + */ + private function getPaymentMethodConfigRepository() + { + return $this->doctrineHelper->getEntityRepository('MarelloPaymentBundle:PaymentMethodConfig'); + } + + /** + * @return PaymentMethodsConfigsRuleRepository|\Doctrine\ORM\EntityRepository + */ + private function getPaymentMethodsConfigsRuleRepository() + { + return $this->doctrineHelper->getEntityRepository('MarelloPaymentBundle:PaymentMethodsConfigsRule'); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/EventListener/MethodRenamingListener.php b/src/Marello/Bundle/PaymentBundle/Method/EventListener/MethodRenamingListener.php new file mode 100644 index 000000000..58c2db56a --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/EventListener/MethodRenamingListener.php @@ -0,0 +1,43 @@ +paymentMethodConfigRepository = $paymentMethodConfigRepository; + } + + /** + * @param MethodRenamingEvent $event + */ + public function onMethodRename(MethodRenamingEvent $event) + { + $this->updateRuleConfigs($event->getOldMethodIdentifier(), $event->getNewMethodIdentifier()); + } + + /** + * @param string $oldId + * @param string $newId + */ + private function updateRuleConfigs($oldId, $newId) + { + $configs = $this->paymentMethodConfigRepository->findByMethod($oldId); + foreach ($configs as $config) { + $config->setMethod($newId); + } + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/EventListener/PaymentMethodDisableIntegrationListener.php b/src/Marello/Bundle/PaymentBundle/Method/EventListener/PaymentMethodDisableIntegrationListener.php new file mode 100644 index 000000000..8d223e2f2 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/EventListener/PaymentMethodDisableIntegrationListener.php @@ -0,0 +1,48 @@ +channelType = $channelType; + $this->methodIdentifierGenerator = $methodIdentifierGenerator; + $this->paymentMethodDisableHandler = $paymentMethodDisableHandler; + } + + /** + * @param ChannelDisableEvent $event + */ + public function onIntegrationDisable(ChannelDisableEvent $event) + { + $channel = $event->getChannel(); + $channelType = $channel->getType(); + if ($channelType === $this->channelType) { + $methodId = $this->methodIdentifierGenerator->generateIdentifier($channel); + $this->paymentMethodDisableHandler->handleMethodDisable($methodId); + } + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Factory/IntegrationPaymentMethodFactoryInterface.php b/src/Marello/Bundle/PaymentBundle/Method/Factory/IntegrationPaymentMethodFactoryInterface.php new file mode 100644 index 000000000..0aa06f53c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Factory/IntegrationPaymentMethodFactoryInterface.php @@ -0,0 +1,15 @@ +handler = $handler; + $this->repository = $repository; + $this->paymentMethodProvider = $paymentMethodProvider; + } + + /** + * {@inheritDoc} + */ + public function handleMethodDisable($methodId) + { + $this->handler->handleMethodDisable($methodId); + $paymentMethodsConfigsRule = $this->repository->getEnabledRulesByMethod($methodId); + foreach ($paymentMethodsConfigsRule as $configRule) { + if (!$this->configHasEnabledMethod($configRule, $methodId)) { + $rule = $configRule->getRule(); + $rule->setEnabled(false); + } + } + } + + /** + * @param PaymentMethodsConfigsRule $configRule + * @param string $disabledMethodId + * + * @return bool + */ + private function configHasEnabledMethod(PaymentMethodsConfigsRule $configRule, $disabledMethodId) + { + $methodConfigs = $configRule->getMethodConfigs(); + foreach ($methodConfigs as $methodConfig) { + $methodId = $methodConfig->getMethod(); + if ($methodId !== $disabledMethodId) { + $method = $this->paymentMethodProvider->getPaymentMethod($methodId); + if ($method->isEnabled()) { + return true; + } + } + } + + return false; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/PaymentMethodIconAwareInterface.php b/src/Marello/Bundle/PaymentBundle/Method/PaymentMethodIconAwareInterface.php new file mode 100644 index 000000000..1b4bc3745 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/PaymentMethodIconAwareInterface.php @@ -0,0 +1,13 @@ +hasMethodView($methodId)) { + return $this; + } + + $this->methodViews[$methodId] = $methodView; + + return $this; + } + + /** + * @param string $methodId + * + * @return bool + */ + public function hasMethodView($methodId) + { + return array_key_exists($methodId, $this->methodViews); + } + + /** + * @param string $methodId + * + * @return $this + */ + public function removeMethodView($methodId) + { + if (false === $this->hasMethodView($methodId)) { + return $this; + } + + unset($this->methodViews[$methodId]); + + return $this; + } + + /** + * @param string $methodId + * + * @return array|null + */ + public function getMethodView($methodId) + { + if (false === $this->hasMethodView($methodId)) { + return null; + } + + return $this->methodViews[$methodId]; + } + + /** + * @return array + */ + public function getAllMethodsViews() + { + return $this->methodViews; + } + + /** + * @return array + */ + public function toArray() + { + $resultingFullMethodViews = []; + + foreach ($this->methodViews as $methodId => $methodView) { + if (false === array_key_exists($methodId, $resultingFullMethodViews)) { + $resultingFullMethodViews[$methodId] = $methodView; + } + } + + uasort( + $resultingFullMethodViews, + function ($methodData1, $methodData2) { + if (false === array_key_exists('sortOrder', $methodData1) + || false === array_key_exists('sortOrder', $methodData2) + ) { + throw new \Exception('Method View should contain sortOrder'); + } + + return $methodData1['sortOrder'] - $methodData2['sortOrder']; + } + ); + + return $resultingFullMethodViews; + } + + /** + * @return self + */ + public function clear() + { + $this->methodViews = []; + + return $this; + } + + /** + * @return bool + */ + public function isEmpty() + { + return count($this->methodViews) <= 0; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/PaymentMethodViewFactory.php b/src/Marello/Bundle/PaymentBundle/Method/PaymentMethodViewFactory.php new file mode 100644 index 000000000..1108662c2 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/PaymentMethodViewFactory.php @@ -0,0 +1,24 @@ + $paymentMethodId, + 'label' => $label, + 'sortOrder' => $sortOrder, + 'options' => $options + ]; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Provider/AbstractPaymentMethodProvider.php b/src/Marello/Bundle/PaymentBundle/Method/Provider/AbstractPaymentMethodProvider.php new file mode 100644 index 000000000..3f20b2b58 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Provider/AbstractPaymentMethodProvider.php @@ -0,0 +1,73 @@ +methods = new ArrayCollection(); + } + + /** + * {@inheritdoc} + */ + public function hasPaymentMethod($identifier) + { + return $this->getMethods()->containsKey($identifier); + } + + /** + * {@inheritdoc} + */ + public function getPaymentMethod($identifier) + { + if (!$this->hasPaymentMethod($identifier)) { + return null; + } + + return $this->getMethods()->get($identifier); + } + + /** + * {@inheritdoc} + */ + public function getPaymentMethods() + { + return $this->getMethods()->toArray(); + } + + /** + * @return ArrayCollection|PaymentMethodInterface[] + */ + protected function getMethods() + { + if ($this->methods->isEmpty()) { + $this->collectMethods(); + } + + return $this->methods; + } + + /** + * @param string $identifier + * @param PaymentMethodInterface $method + */ + protected function addMethod($identifier, PaymentMethodInterface $method) + { + $this->methods->set($identifier, $method); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Provider/CompositePaymentMethodProvider.php b/src/Marello/Bundle/PaymentBundle/Method/Provider/CompositePaymentMethodProvider.php new file mode 100644 index 000000000..e4dab80d6 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Provider/CompositePaymentMethodProvider.php @@ -0,0 +1,60 @@ +providers[] = $provider; + } + + /** + * {@inheritDoc} + */ + public function getPaymentMethods() + { + $result = []; + foreach ($this->providers as $provider) { + $result = array_merge($result, $provider->getPaymentMethods()); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function getPaymentMethod($identifier) + { + foreach ($this->providers as $provider) { + if ($provider->hasPaymentMethod($identifier)) { + return $provider->getPaymentMethod($identifier); + } + } + + throw new \InvalidArgumentException('There is no payment method for "' . $identifier . '" identifier'); + } + + /** + * {@inheritDoc} + */ + public function hasPaymentMethod($identifier) + { + foreach ($this->providers as $provider) { + if ($provider->hasPaymentMethod($identifier)) { + return true; + } + } + + return false; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Provider/Integration/ChannelPaymentMethodProvider.php b/src/Marello/Bundle/PaymentBundle/Method/Provider/Integration/ChannelPaymentMethodProvider.php new file mode 100644 index 000000000..12a6eda56 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Provider/Integration/ChannelPaymentMethodProvider.php @@ -0,0 +1,122 @@ +channelType = $channelType; + $this->doctrineHelper = $doctrineHelper; + $this->methodFactory = $methodFactory; + } + + /** + * We need only non dirty channels for creating methods. + * For example if entity was changed on form submit, we will have dirty channel in Unit of work. + * + * @param Channel $channel + * @param LifecycleEventArgs $event + */ + public function postLoad(Channel $channel, LifecycleEventArgs $event) + { + if ($channel->getType() === $this->channelType) { + $this->loadedChannels[] = $channel; + $this->createMethodFromChannel($channel); + } + } + + /** + * {@inheritDoc} + */ + public function getPaymentMethods() + { + $this->loadChannels(); + + return $this->methods; + } + + /** + * {@inheritDoc} + */ + public function getPaymentMethod($name) + { + if ($this->hasPaymentMethod($name)) { + return $this->getPaymentMethods()[$name]; + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function hasPaymentMethod($name) + { + return array_key_exists($name, $this->getPaymentMethods()); + } + + /** + * @param Channel $channel + */ + private function createMethodFromChannel(Channel $channel) + { + $method = $this->methodFactory->create($channel); + $this->methods[$method->getIdentifier()] = $method; + } + + /** + * @return ChannelRepository|\Doctrine\ORM\EntityRepository + */ + private function getRepository() + { + return $this->doctrineHelper->getEntityRepository('OroIntegrationBundle:Channel'); + } + + private function loadChannels() + { + /* After fetching, all entities will be saved into $loadedChannels on postLoad call */ + $this->getRepository()->findByTypeAndExclude($this->channelType, $this->loadedChannels); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Provider/PaymentMethodProvider.php b/src/Marello/Bundle/PaymentBundle/Method/Provider/PaymentMethodProvider.php new file mode 100644 index 000000000..deab3f5a8 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Provider/PaymentMethodProvider.php @@ -0,0 +1,95 @@ +paymentMethodProvider = $paymentMethodProvider; + $this->paymentMethodsConfigsRulesProvider = $paymentMethodsConfigsRulesProvider; + } + + /** + * @param PaymentContextInterface $context + * + * @return PaymentMethodInterface[] + */ + public function getApplicablePaymentMethods(PaymentContextInterface $context) + { + $paymentMethodsConfigsRules = $this->paymentMethodsConfigsRulesProvider + ->getPaymentMethodsConfigsRules($context); + + $paymentMethods = []; + + foreach ($paymentMethodsConfigsRules as $paymentMethodsConfigsRule) { + $paymentMethods = array_merge( + $paymentMethods, + $this->getPaymentMethodsForConfigsRule($paymentMethodsConfigsRule, $context) + ); + } + + return $paymentMethods; + } + + /** + * @param PaymentMethodsConfigsRule $paymentMethodsConfigsRule + * @param PaymentContextInterface $context + * @return array + */ + protected function getPaymentMethodsForConfigsRule( + PaymentMethodsConfigsRule $paymentMethodsConfigsRule, + PaymentContextInterface $context + ) { + $paymentMethods = []; + foreach ($paymentMethodsConfigsRule->getMethodConfigs() as $methodConfig) { + $paymentMethod = $this->getPaymentMethodForConfig($methodConfig, $context); + if ($paymentMethod) { + $paymentMethods[$methodConfig->getMethod()] = $paymentMethod; + } + } + + return $paymentMethods; + } + + /** + * @param PaymentMethodConfig $methodConfig + * @param PaymentContextInterface $context + * @return PaymentMethodInterface|null + */ + protected function getPaymentMethodForConfig(PaymentMethodConfig $methodConfig, PaymentContextInterface $context) + { + $identifier = $methodConfig->getMethod(); + if ($this->paymentMethodProvider->hasPaymentMethod($identifier)) { + $paymentMethod = $this->paymentMethodProvider->getPaymentMethod($identifier); + + if ($paymentMethod->isApplicable($context)) { + return $paymentMethod; + } + } + + return null; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Method/Provider/PaymentMethodProviderInterface.php b/src/Marello/Bundle/PaymentBundle/Method/Provider/PaymentMethodProviderInterface.php new file mode 100644 index 000000000..8b3f16c7c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Method/Provider/PaymentMethodProviderInterface.php @@ -0,0 +1,25 @@ +setName(self::DEFAULT_RULE_NAME) + ->setEnabled(true) + ->setSortOrder(1); + + $paymentRule = new PaymentMethodsConfigsRule(); + + $paymentRule->setRule($rule) + ->setOrganization($this->getOrganization($manager)) + ->setCurrency('USD'); + + $manager->persist($paymentRule); + $this->addReference(self::DEFAULT_RULE_REFERENCE, $paymentRule); + $manager->flush(); + } + + /** + * @param ObjectManager $manager + * + * @return Organization|object + */ + private function getOrganization(ObjectManager $manager) + { + if ($this->hasReference(LoadOrganizationAndBusinessUnitData::REFERENCE_DEFAULT_ORGANIZATION)) { + return $this->getReference(LoadOrganizationAndBusinessUnitData::REFERENCE_DEFAULT_ORGANIZATION); + } else { + return $manager + ->getRepository('OroOrganizationBundle:Organization') + ->getFirst(); + } + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Migrations/Schema/MarelloPaymentBundleInstaller.php b/src/Marello/Bundle/PaymentBundle/Migrations/Schema/MarelloPaymentBundleInstaller.php new file mode 100644 index 000000000..b82aee0bd --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Migrations/Schema/MarelloPaymentBundleInstaller.php @@ -0,0 +1,203 @@ +createMarelloPaymentMethodConfigTable($schema); + $this->createMarelloPaymentMethodsConfigsRuleTable($schema); + $this->createMarelloPaymentMethodsConfigsRuleDestinationTable($schema); + $this->createMarelloPaymentMethodsConfigsRuleDestinationPostalCodeTable($schema); + + $this->addOroPaymentMethodConfigForeignKeys($schema); + $this->addOroPaymentMethodsConfigsRuleForeignKeys($schema); + $this->addOroPaymentMethodsConfigsRuleDestinationForeignKeys($schema); + $this->addOroPaymentMethodsConfigsRuleDestinationPostalCodeForeignKeys($schema); + } + + /** + * Create marello_payment_method_config table + * + * @param Schema $schema + */ + protected function createMarelloPaymentMethodConfigTable(Schema $schema) + { + $table = $schema->createTable('marello_payment_method_config'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('configs_rule_id', 'integer', []); + $table->addColumn('method', 'string', ['length' => 255]); + $table->addColumn('options', 'array', ['notnull' => false, 'comment' => '(DC2Type:array)']); + $table->setPrimaryKey(['id']); + } + + /** + * Create marello_payment_mtds_cfgs_rl table + * + * @param Schema $schema + */ + protected function createMarelloPaymentMethodsConfigsRuleTable(Schema $schema) + { + $table = $schema->createTable('marello_payment_mtds_cfgs_rl'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('rule_id', 'integer', []); + $table->addColumn('currency', 'string', ['notnull' => true, 'length' => 3]); + $table->addColumn('organization_id', 'integer', ['notnull' => false]); + $table->setPrimaryKey(['id']); + + $this->activityExtension->addActivityAssociation( + $schema, + 'oro_note', + 'marello_payment_mtds_cfgs_rl' + ); + } + + /** + * Create marello_payment_mtds_cfgs_rl_d table + * + * @param Schema $schema + */ + protected function createMarelloPaymentMethodsConfigsRuleDestinationTable(Schema $schema) + { + $table = $schema->createTable('marello_payment_mtds_cfgs_rl_d'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('region_code', 'string', ['notnull' => false, 'length' => 16]); + $table->addColumn('configs_rule_id', 'integer', []); + $table->addColumn('country_code', 'string', ['length' => 2]); + $table->addColumn('region_text', 'string', ['notnull' => false, 'length' => 255]); + $table->setPrimaryKey(['id']); + } + + /** + * Create marello_pmnt_mtdscfgsrl_dst_pc table + * + * @param Schema $schema + */ + protected function createMarelloPaymentMethodsConfigsRuleDestinationPostalCodeTable(Schema $schema) + { + $table = $schema->createTable('marello_pmnt_mtdscfgsrl_dst_pc'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('destination_id', 'integer', []); + $table->addColumn('name', 'text', []); + $table->setPrimaryKey(['id']); + } + + /** + * Add marello_payment_method_config foreign keys. + * + * @param Schema $schema + */ + protected function addOroPaymentMethodConfigForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_payment_method_config'); + $table->addForeignKeyConstraint( + $schema->getTable('marello_payment_mtds_cfgs_rl'), + ['configs_rule_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + } + + /** + * Add marello_payment_mtds_cfgs_rl foreign keys. + * + * @param Schema $schema + */ + protected function addOroPaymentMethodsConfigsRuleForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_payment_mtds_cfgs_rl'); + $table->addForeignKeyConstraint( + $schema->getTable('marello_rule'), + ['rule_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + + $table->addForeignKeyConstraint( + $schema->getTable('oro_organization'), + ['organization_id'], + ['id'], + ['onDelete' => 'SET NULL', 'onUpdate' => null] + ); + } + + /** + * Add marello_payment_mtds_cfgs_rl_d foreign keys. + * + * @param Schema $schema + */ + protected function addOroPaymentMethodsConfigsRuleDestinationForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_payment_mtds_cfgs_rl_d'); + $table->addForeignKeyConstraint( + $schema->getTable('oro_dictionary_region'), + ['region_code'], + ['combined_code'], + ['onDelete' => null, 'onUpdate' => null] + ); + $table->addForeignKeyConstraint( + $schema->getTable('marello_payment_mtds_cfgs_rl'), + ['configs_rule_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + $table->addForeignKeyConstraint( + $schema->getTable('oro_dictionary_country'), + ['country_code'], + ['iso2_code'], + ['onDelete' => null, 'onUpdate' => null] + ); + } + + /** + * Add marello_pmnt_mtdscfgsrl_dst_pc foreign keys. + * + * @param Schema $schema + */ + protected function addOroPaymentMethodsConfigsRuleDestinationPostalCodeForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_pmnt_mtdscfgsrl_dst_pc'); + $table->addForeignKeyConstraint( + $schema->getTable('marello_payment_mtds_cfgs_rl_d'), + ['destination_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + } + + /** + * Sets the ActivityExtension + * + * @param ActivityExtension $activityExtension + */ + public function setActivityExtension(ActivityExtension $activityExtension) + { + $this->activityExtension = $activityExtension; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Migrations/Schema/v1_0/MarelloPaymentBundle.php b/src/Marello/Bundle/PaymentBundle/Migrations/Schema/v1_0/MarelloPaymentBundle.php new file mode 100644 index 000000000..4442fe6ad --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Migrations/Schema/v1_0/MarelloPaymentBundle.php @@ -0,0 +1,195 @@ +createMarelloPaymentMethodConfigTable($schema); + $this->createMarelloPaymentMethodsConfigsRuleTable($schema); + $this->createMarelloPaymentMethodsConfigsRuleDestinationTable($schema); + $this->createMarelloPaymentMethodsConfigsRuleDestinationPostalCodeTable($schema); + + $this->addOroPaymentMethodConfigForeignKeys($schema); + $this->addOroPaymentMethodsConfigsRuleForeignKeys($schema); + $this->addOroPaymentMethodsConfigsRuleDestinationForeignKeys($schema); + $this->addOroPaymentMethodsConfigsRuleDestinationPostalCodeForeignKeys($schema); + } + + /** + * Create marello_payment_method_config table + * + * @param Schema $schema + */ + protected function createMarelloPaymentMethodConfigTable(Schema $schema) + { + $table = $schema->createTable('marello_payment_method_config'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('configs_rule_id', 'integer', []); + $table->addColumn('method', 'string', ['length' => 255]); + $table->addColumn('options', 'array', ['notnull' => false, 'comment' => '(DC2Type:array)']); + $table->setPrimaryKey(['id']); + } + + /** + * Create marello_payment_mtds_cfgs_rl table + * + * @param Schema $schema + */ + protected function createMarelloPaymentMethodsConfigsRuleTable(Schema $schema) + { + $table = $schema->createTable('marello_payment_mtds_cfgs_rl'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('rule_id', 'integer', []); + $table->addColumn('currency', 'string', ['notnull' => true, 'length' => 3]); + $table->addColumn('organization_id', 'integer', ['notnull' => false]); + $table->setPrimaryKey(['id']); + + $this->activityExtension->addActivityAssociation( + $schema, + 'oro_note', + 'marello_payment_mtds_cfgs_rl' + ); + } + + /** + * Create marello_payment_mtds_cfgs_rl_d table + * + * @param Schema $schema + */ + protected function createMarelloPaymentMethodsConfigsRuleDestinationTable(Schema $schema) + { + $table = $schema->createTable('marello_payment_mtds_cfgs_rl_d'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('region_code', 'string', ['notnull' => false, 'length' => 16]); + $table->addColumn('configs_rule_id', 'integer', []); + $table->addColumn('country_code', 'string', ['length' => 2]); + $table->addColumn('region_text', 'string', ['notnull' => false, 'length' => 255]); + $table->setPrimaryKey(['id']); + } + + /** + * Create marello_pmnt_mtdscfgsrl_dst_pc table + * + * @param Schema $schema + */ + protected function createMarelloPaymentMethodsConfigsRuleDestinationPostalCodeTable(Schema $schema) + { + $table = $schema->createTable('marello_pmnt_mtdscfgsrl_dst_pc'); + $table->addColumn('id', 'integer', ['autoincrement' => true]); + $table->addColumn('destination_id', 'integer', []); + $table->addColumn('name', 'text', []); + $table->setPrimaryKey(['id']); + } + + /** + * Add marello_payment_method_config foreign keys. + * + * @param Schema $schema + */ + protected function addOroPaymentMethodConfigForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_payment_method_config'); + $table->addForeignKeyConstraint( + $schema->getTable('marello_payment_mtds_cfgs_rl'), + ['configs_rule_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + } + + /** + * Add marello_payment_mtds_cfgs_rl foreign keys. + * + * @param Schema $schema + */ + protected function addOroPaymentMethodsConfigsRuleForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_payment_mtds_cfgs_rl'); + $table->addForeignKeyConstraint( + $schema->getTable('marello_rule'), + ['rule_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + + $table->addForeignKeyConstraint( + $schema->getTable('oro_organization'), + ['organization_id'], + ['id'], + ['onDelete' => 'SET NULL', 'onUpdate' => null] + ); + } + + /** + * Add marello_payment_mtds_cfgs_rl_d foreign keys. + * + * @param Schema $schema + */ + protected function addOroPaymentMethodsConfigsRuleDestinationForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_payment_mtds_cfgs_rl_d'); + $table->addForeignKeyConstraint( + $schema->getTable('oro_dictionary_region'), + ['region_code'], + ['combined_code'], + ['onDelete' => null, 'onUpdate' => null] + ); + $table->addForeignKeyConstraint( + $schema->getTable('marello_payment_mtds_cfgs_rl'), + ['configs_rule_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + $table->addForeignKeyConstraint( + $schema->getTable('oro_dictionary_country'), + ['country_code'], + ['iso2_code'], + ['onDelete' => null, 'onUpdate' => null] + ); + } + + /** + * Add marello_pmnt_mtdscfgsrl_dst_pc foreign keys. + * + * @param Schema $schema + */ + protected function addOroPaymentMethodsConfigsRuleDestinationPostalCodeForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_pmnt_mtdscfgsrl_dst_pc'); + $table->addForeignKeyConstraint( + $schema->getTable('marello_payment_mtds_cfgs_rl_d'), + ['destination_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + } + + /** + * Sets the ActivityExtension + * + * @param ActivityExtension $activityExtension + */ + public function setActivityExtension(ActivityExtension $activityExtension) + { + $this->activityExtension = $activityExtension; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Model/ExtendPaymentMethodConfig.php b/src/Marello/Bundle/PaymentBundle/Model/ExtendPaymentMethodConfig.php new file mode 100644 index 000000000..2cd4fb95c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Model/ExtendPaymentMethodConfig.php @@ -0,0 +1,10 @@ +name; + } + + /** + * @param string $name + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + * @return $this + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * @return float + */ + public function getCost() + { + return $this->cost; + } + + /** + * @param float $cost + * @return $this + */ + public function setCost($cost) + { + $this->cost = (float)$cost; + + return $this; + } + + /** + * @return float + */ + public function getQty() + { + return $this->qty; + } + + /** + * @param float $qty + * @return $this + */ + public function setQty($qty) + { + $this->qty = $qty; + + return $this; + } + + /** + * @return string + */ + public function getCurrency() + { + return $this->currency; + } + + /** + * @param string $currency + * @return $this + */ + public function setCurrency($currency) + { + $this->currency = $currency; + + return $this; + } + + /** + * @return string + */ + public function getUnit() + { + return $this->unit; + } + + /** + * @param string $unit + * @return $this + */ + public function setUnit($unit) + { + $this->unit = $unit; + + return $this; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Model/Surcharge.php b/src/Marello/Bundle/PaymentBundle/Model/Surcharge.php new file mode 100644 index 000000000..6e719d70b --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Model/Surcharge.php @@ -0,0 +1,94 @@ +paymentAmount = (float)$amount; + + return $this; + } + + /** + * @return float + */ + public function getPaymentAmount() + { + return $this->paymentAmount; + } + + /** + * @param float $amount + * @return $this + */ + public function setHandlingAmount($amount) + { + $this->handlingAmount = (float)$amount; + + return $this; + } + + /** + * @return float + */ + public function getHandlingAmount() + { + return $this->handlingAmount; + } + + /** + * @param float $amount + * @return $this + */ + public function setDiscountAmount($amount) + { + $this->discountAmount = (float)$amount; + + return $this; + } + + /** + * @return float + */ + public function getDiscountAmount() + { + return $this->discountAmount; + } + + /** + * @param float $amount + * @return $this + */ + public function setInsuranceAmount($amount) + { + $this->insuranceAmount = (float)$amount; + + return $this; + } + + /** + * @return float + */ + public function getInsuranceAmount() + { + return $this->insuranceAmount; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Provider/BasicPaymentMethodChoicesProvider.php b/src/Marello/Bundle/PaymentBundle/Provider/BasicPaymentMethodChoicesProvider.php new file mode 100755 index 000000000..444c7b542 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Provider/BasicPaymentMethodChoicesProvider.php @@ -0,0 +1,52 @@ +paymentMethodProvider = $paymentMethodProvider; + $this->translator = $translator; + } + + /** + * {@inheritdoc} + */ + public function getMethods($translate = false) + { + return array_reduce( + $this->paymentMethodProvider->getPaymentMethods(), + function (array $result, PaymentMethodInterface $method) use ($translate) { + $label = $method->getLabel(); + if ($translate) { + $label = $this->translator->trans($label); + } + $result[$method->getIdentifier()] = $label; + + return $result; + }, + [] + ); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Provider/BasicPaymentMethodsViewsProvider.php b/src/Marello/Bundle/PaymentBundle/Provider/BasicPaymentMethodsViewsProvider.php new file mode 100644 index 000000000..781c1d7b4 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Provider/BasicPaymentMethodsViewsProvider.php @@ -0,0 +1,81 @@ +paymentRulesProvider = $paymentRulesProvider; + $this->paymentMethodProvider = $paymentMethodProvider; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * @inheritDoc + */ + public function getApplicableMethodsViews(PaymentContextInterface $context) + { + $methodCollection = new PaymentMethodViewCollection(); + + $rules = $this->paymentRulesProvider->getPaymentMethodsConfigsRules($context); + foreach ($rules as $rule) { + foreach ($rule->getMethodConfigs() as $methodConfig) { + $methodId = $methodConfig->getMethod(); + $method = $this->paymentMethodProvider->getPaymentMethod($methodId); + + if (!$method) { + continue; + } + $event = new ApplicablePaymentMethodViewEvent( + $context, + $methodId, + $method->getLabel(), + $methodConfig->getOptions() + ); + $this->eventDispatcher->dispatch(ApplicablePaymentMethodViewEvent::NAME, $event); + $methodView = PaymentMethodViewFactory::createMethodView( + $event->getMethodId(), + $event->getMethodLabel(), + $method->getSortOrder(), + $event->getOptions() + ); + + $methodCollection->addMethodView($methodId, $methodView); + } + } + + return $methodCollection; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Provider/EnabledPaymentMethodChoicesProviderDecorator.php b/src/Marello/Bundle/PaymentBundle/Provider/EnabledPaymentMethodChoicesProviderDecorator.php new file mode 100755 index 000000000..00dbe6b08 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Provider/EnabledPaymentMethodChoicesProviderDecorator.php @@ -0,0 +1,51 @@ +paymentMethodProvider = $paymentMethodProvider; + $this->provider = $provider; + } + + /** + * {@inheritdoc} + */ + public function getMethods($translate = false) + { + $methods = $this->provider->getMethods($translate); + $enabledMethods = []; + foreach ($methods as $methodId => $label) { + $method = $this->paymentMethodProvider->getPaymentMethod($methodId); + if (!$method) { + continue; + } + + if ($method->isEnabled()) { + $enabledMethods[$methodId] = $label; + } + } + + return $enabledMethods; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Provider/MethodsConfigsRule/Context/Basic/BasicMethodsConfigsRulesByContextProvider.php b/src/Marello/Bundle/PaymentBundle/Provider/MethodsConfigsRule/Context/Basic/BasicMethodsConfigsRulesByContextProvider.php new file mode 100644 index 000000000..709102bef --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Provider/MethodsConfigsRule/Context/Basic/BasicMethodsConfigsRulesByContextProvider.php @@ -0,0 +1,55 @@ +filtrationService = $filtrationService; + $this->repository = $repository; + } + + /** + * {@inheritDoc} + */ + public function getPaymentMethodsConfigsRules(PaymentContextInterface $context) + { + if ($context->getBillingAddress()) { + $methodsConfigsRules = $this->repository->getByDestinationAndCurrency( + $context->getBillingAddress(), + $context->getCurrency() + ); + } else { + $methodsConfigsRules = $this->repository->getByCurrencyWithoutDestination( + $context->getCurrency() + ); + } + + return $this->filtrationService->getFilteredPaymentMethodsConfigsRules( + $methodsConfigsRules, + $context + ); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Provider/MethodsConfigsRule/Context/MethodsConfigsRulesByContextProviderInterface.php b/src/Marello/Bundle/PaymentBundle/Provider/MethodsConfigsRule/Context/MethodsConfigsRulesByContextProviderInterface.php new file mode 100644 index 000000000..aaf245e78 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Provider/MethodsConfigsRule/Context/MethodsConfigsRulesByContextProviderInterface.php @@ -0,0 +1,15 @@ +filtrationService = $filtrationService; + $this->repository = $repository; + } + + /** + * {@inheritDoc} + */ + public function getPaymentMethodsConfigsRules(PaymentContextInterface $context) + { + if ($context->getBillingAddress()) { + $methodsConfigsRules = $this->repository->getByDestinationAndCurrency( + $context->getBillingAddress(), + $context->getCurrency() + ); + } else { + $methodsConfigsRules = $this->repository->getByCurrency( + $context->getCurrency() + ); + } + + return $this->filtrationService->getFilteredPaymentMethodsConfigsRules( + $methodsConfigsRules, + $context + ); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Provider/PaymentMethodChoicesProviderInterface.php b/src/Marello/Bundle/PaymentBundle/Provider/PaymentMethodChoicesProviderInterface.php new file mode 100755 index 000000000..6329556a0 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Provider/PaymentMethodChoicesProviderInterface.php @@ -0,0 +1,13 @@ +div { + position: relative; + padding: 15px 15px 0 0; + margin: 0 60% 20px 0; + border: 1px solid #e6e6e6; + background: #f8f8f8; + border-radius: 4px; + min-width: 500px; + } + div[data-validation-optional-group=""] { + .oro-multiselect-holder { + padding-right: 50px !important; + } + } + .add-list-item { + margin-left: 183px; + } + .removeRow { + position: absolute; + width: 28px; + top: 4px; + right: 4px; + text-decoration: none; + &:after { + color: #bbb; + } + } + input[type="text"] { + width: 290px; + } + [data-name*="field__region-text"] { + margin-top:0 !important; + } + input.method-options-surcharge { + width: 110px; + } + .small-row { + .control-group-choice { + float: left; + } + } + .selector.input-widget-select { + margin-left: 0; + } +} + +.marello-payment-rule-add-method-select { + display: inline-block; +} diff --git a/src/Marello/Bundle/PaymentBundle/Resources/public/js/app/views/payment-rule-method-view.js b/src/Marello/Bundle/PaymentBundle/Resources/public/js/app/views/payment-rule-method-view.js new file mode 100644 index 000000000..9bf6f53ca --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/public/js/app/views/payment-rule-method-view.js @@ -0,0 +1,263 @@ +define(function(require) { + 'use strict'; + + const _ = require('underscore'); + const $ = require('jquery'); + const BaseView = require('oroui/js/app/views/base/view'); + const Popover = require('bootstrap-popover'); + const mediator = require('oroui/js/mediator'); + const layout = require('oroui/js/layout'); + + const PaymentRuleMethodsView = BaseView.extend({ + options: { + addSelector: '.add-method', + addAllSelector: '.add-all-methods', + gridSelector: '.payment-methods-grid', + methodSelectSelector: '[name="marello_payment_methods_configs_rule[method]"]', + methodSelector: 'input[data-name="field__method"]', + methodViewSelector: '[data-role="method-view"]', + methodPreviewSelector: '[data-role="method-preview"]', + focusFieldsSelector: 'input:text[name]', + previewFieldsSelector: 'input:text[name],select[name],textarea[name]', + enabledFieldSelector: '[data-name="field__enabled"]', + additionalOptionSelector: '.control-group-marello_payment_method_type_config', + focus: false, + updateFlag: null + }, + + events: { + 'click .add-all-methods': '_onAddAllClick', + 'click .add-method': '_onAddClick', + 'change [name="marello_payment_methods_configs_rule[method]"]': '_onMethodChange', + 'content:remove': '_onMethodRemove', + 'change textarea': '_onInputsChange' + }, + + listen: { + 'page:afterChange mediator': '_onPageAfterChange' + }, + + methods: null, + + $form: null, + + $methodSelect: null, + + $methodSelectClone: null, + + $add: null, + + $addAll: null, + + initialize: function(options) { + this.options = _.defaults(options || {}, this.options); + + this.$form = $(this.el).closest('form'); + this.$methodSelect = this.$(this.options.methodSelectSelector); + this.$methodSelectClone = this.$methodSelect.find('option[value!=""]').clone(); + this.$add = this.$(this.options.addSelector); + this.$addAll = this.$(this.options.addAllSelector); + + this.updateMethodsList(); + this.updateMethodSelect(); + this.updateMethodPreview(); + }, + + _onMethodChange: function() { + this.toggleAddButton(); + }, + + _onAddAllClick: function() { + this.createAddRequest(true); + }, + + _onAddClick: function() { + this.createAddRequest(false); + }, + + _onInputsChange: function(e) { + var $method = $(e.target).closest(this.options.methodViewSelector); + if ($method.length) { + this.updateMethodPreview($method); + } + }, + + _onMethodRemove: function(e) { + var removedMethod = $(e.target).find(this.options.methodSelector).val(); + this.updateMethodsList(removedMethod); + this.updateMethodSelect(); + + if (this.methods.length === 0) { + this.$(this.options.gridSelector).remove(); + } + }, + + _onPageAfterChange: function() { + this.focus(); + }, + + focus: function() { + if (!this.options.focus) { + return; + } + + var focusFieldsSelector = this.options.focusFieldsSelector; + this.$(this.options.methodViewSelector).each(function() { + var $fields = $(this).find(focusFieldsSelector); + var notEmptyFieldsCount = $fields.filter(function() { + return this.value.length > 0; + }).length; + if (notEmptyFieldsCount === 0) { + $fields.eq(0).attr('autofocus', true).focus(); + return false; + } + }); + }, + + toggleAddButton: function() { + this.$add.toggleClass('disabled', _.isEmpty(this.$methodSelect.val())); + }, + + createAddRequest: function(addAll) { + var methodCount = this.methods.length - 1; + var addMethod = function(option) { + methodCount++; + return { + name: 'marello_payment_methods_configs_rule[methodConfigs][' + methodCount + '][method]', + value: option.value + }; + }; + + var data = this.$form.serializeArray(); + if (addAll) { + data.push.apply(data, this.$methodSelect.find('option[value][value!=""]').get().map(addMethod)); + } else { + data.push(addMethod(this.$methodSelect.get(0))); + } + data.push({ + name: this.options.updateFlag, + value: true + }); + + mediator.execute('submitPage', { + url: this.$form.attr('action'), + type: this.$form.attr('method'), + data: $.param(data) + }); + }, + + updateMethodsList: function(removedMethod) { + this.methods = []; + _.each(this.$(this.options.methodSelector), function(option) { + if (!removedMethod || option.value !== removedMethod) { + this.methods.push(option.value); + } + }, this); + }, + + updateMethodSelect: function() { + this.$methodSelect.empty(); + _.each(this.$methodSelectClone, function(option) { + if (_.indexOf(this.methods, option.value) === -1) { + this.$methodSelect.append($(option).clone()); + } + }, this); + + var length = this.$methodSelect.find('option').length; + this.$methodSelect.prop('disabled', length === 0); + this.$methodSelect.inputWidget('refresh'); + this.$methodSelect.inputWidget('val', ''); + + this.$addAll.toggle(length > 1); + this.toggleAddButton(); + }, + + updateMethodPreview: function($method) { + if (_.isUndefined($method)) { + _.each(this.$(this.options.methodViewSelector), function(method) { + this.updateMethodPreview($(method)); + }, this); + return; + } + + var disabled = []; + $method.find(this.options.enabledFieldSelector).each(function() { + if ($(this).is(':checkbox') && !this.checked) { + disabled.push(this.name.replace(/\[enabled\]$/, '')); + } + }); + + var $preview = $method.find(this.options.methodPreviewSelector); + var preview = []; + _.each($method.find(this.options.previewFieldsSelector), function(field) { + var $field = $(field); + var value = _.trim(field.value); + if (value.length === 0) { + return; + } + + var isDisabled = _.filter(disabled, function(name) { + return field.name.indexOf(name) !== -1; + }).length > 0; + if (isDisabled) { + return; + } + + if ($field.is('select')) { + value = $field.find('option:selected').text(); + } + + var $label = this.$('label[for="' + field.id + '"]'); + var label = $label.contents().eq(0).text() + ': '; + + preview.push(label + value); + }, this); + + //replace whitespaces by  , for tooltip overflow calculate + $preview.html(preview.join(', ').replace(/ /g, ' ')); + + this.updatePreviewTooltip($preview, preview); + }, + + updatePreviewTooltip: function($preview, preview) { + if ($preview.data(Popover.DATA_KEY) === void 0) { + layout.initPopoverForElements($preview, { + placement: 'bottom', + trigger: 'hover', + close: false + }, true); + } + + var height = $preview.height(); + $preview.css({ + 'word-wrap': 'break-word', + 'overflow': 'auto', + 'white-space': 'normal' + }); + var isOverflow = $preview.height() > height; + + $preview.attr('style', '').data(Popover.DATA_KEY).updateContent(isOverflow ? preview.join('
') : ''); + }, + + dispose: function() { + if (this.disposed) { + return; + } + + this.$form.off(this.eventNamespace()); + + delete this.methods; + delete this.options; + + delete this.$form; + delete this.$methodSelect; + delete this.$methodSelectClone; + delete this.$add; + delete this.$addAll; + + PaymentRuleMethodsView.__super__.dispose.apply(this, arguments); + } + }); + + return PaymentRuleMethodsView; +}); diff --git a/src/Marello/Bundle/PaymentBundle/Resources/translations/jsmessages.en.yml b/src/Marello/Bundle/PaymentBundle/Resources/translations/jsmessages.en.yml new file mode 100644 index 000000000..29dcb0d51 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/translations/jsmessages.en.yml @@ -0,0 +1,16 @@ +marello: + payment: + validation: + expiration_date: "Invalid Expiration date." + month: "Please, choose correct month." + credit_card: "Invalid card number." + credit_card_type: "Credit card type not accepted." + integration: + deactivate: + message: Some payment methods in the following payment rules depend on the integration that is being disabled. The affected payment methods will be disabled. Payment rule will also be disabled if none of its payment methods remain enabled. + title: Deactivate integration + button.okText: Deactivate + delete: + message: Some payment methods in the following payment rules depend on the integration that is being deleted. The affected payment methods will be disabled. Payment rule will also be disabled if none of its payment methods remain enabled. + title: Delete integration + button.okText: Delete \ No newline at end of file diff --git a/src/Marello/Bundle/PaymentBundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/PaymentBundle/Resources/translations/messages.en.yml new file mode 100644 index 000000000..f43599e9f --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/translations/messages.en.yml @@ -0,0 +1,154 @@ +marello: + payment: + menu: + payments.label: Payments + paymentmethodsconfigsrule.description: Payment Rules + paymentmethodsconfigsrule.label: Payment Rules + shortcut_paymentmethodsconfigsrule.description: List of payment rules + datagrid: + action.enable: Enable + action.disable: Disable + status: + success_message: "{0} No entities status were changed|{1} One entity status was changed|]1,Inf[ %count% entities status were changed" + + payment_methods_table: + method.label: Method + options.label: Options + additional_options.label: Additional Options + option.label: Option + status.label: Status + active.label: Active + + methods: + label: "Payment Method" + select_method: 'Select a Payment Method' + no_method: 'No payment methods are available, please contact us to complete the order submission.' + required_field: 'Required Field' + + notification: + channel: + enabled: Payment rule has been enabled successfully + disabled: Payment rule has been disabled successfully + + system_configuration: + groups: + payment.title: 'Payment' + general.title: 'General' + display_options.title: 'Display Options' + merchant_location.title: 'Merchant Location' + fields: + merchant_country.label: 'Merchant Country' + merchant_country.placeholder: 'Please select country...' + enabled.label: 'Enabled' + label.label: 'Checkout Label' + label.tooltip: 'This label is used on the checkout screens' + short_label.label: 'Short Label' + short_label.tooltip: 'This label is used in order history' + sort_order.label: 'Sort Order' + allowed_countries: + label: 'Allowed Countries' + all: 'All' + selected: 'Selected' + selected_countries.label: 'Select Allowed Countries' + + result: + error: Payment failed + error_single_method: 'We were unable to process your payment. Please verify your payment information and try again.' + error_multiple_methods: 'We were unable to process your payment. Please verify your payment information and try again, or try another payment method.' + + security.transaction: Work with payments + capture: Capture + + status: + label: Payment Status + full: Paid in full + authorized: Payment authorized + pending: Pending payment + declined: Payment declined + partially: Paid partially + invoiced: Invoiced + + message: + error: 'General error. We were unable to process action.' + + paymentmethodconfig: + entity_label: Payment Method Config + entity_plural_label: Payment Method Configs + id.label: Id + methods_configs_rule.label: Payment Methods Configs Rule + method.label: Method + options.label: Options + + paymentmethodsconfigsrule: + entity_short_label: Payment Rule + entity_short_plural_label: Payment Rules + entity_label: Payment Rule + entity_plural_label: Payment Rules + id.label: Id + method_configs.label: Payment Methods Configs + rule.label: Rule + destinations.label: Destinations + currency.label: Currency + organization.label: Organization + websites.label: Websites + + notification: + enabled: Payment rule has been enabled successfully + disabled: Payment rule has been disabled successfully + + paymentmethodsconfigsruledestination: + entity_label: Payment Methods Configs Rule Destination + entity_plural_label: Payment Methods Configs Rule Destinations + id.label: Id + methods_configs_rule.label: Payment Methods Configs Rule + postal_codes.label: Postal Codes + region.label: Region + region_text.label: Region Text + country.label: Country + + paymentmethodsconfigsruledestinationpostalcode: + entity_label: Payment Methods Configs Rule Destination Postal Code + entity_plural_label: Payment Methods Configs Rule Destination Postal Codes + id.label: Id + name.label: Name + destination.label: Destination + + paymenttransaction: + entity_plural_label: Payment Transactions + entity_label: Payment Transaction + id.label: Id + paymentMethod.label: Payment Method + action.label: Type + amount.label: Amount + active.label: Active + currency.label: Currency + payment_method.label: Payment Method + reference.label: Reference + frontend_owner.label: Frontend Owner + source_payment_transaction.label: Source Payment Transaction + successful.label: Successful + access_identifier.label: Access Identifier + entity_class.label: Entity Class + entity_identifier.label: Entity Identifier + organization.label: Organization + owner.label: Owner + related_payment_transactions.label: Related Payment Transactions + types: + authorize.label: Authorize + charge.label: Charge + validate.label: Validate + capture.label: Capture + purchase.label: Purchase + invoice.label: Invoice + pending.label: Pending + + sections: + general: 'General Information' + destination: 'Destinations' + paymentrule_configurations: + no_methods.message: 'Please enable at least one payment integration.' + label: 'Payment Method Configurations' + placeholder.label: 'Choose a Method...' + + controller: + rule.saved.message: 'Payment rule has been saved' diff --git a/src/Marello/Bundle/PaymentBundle/Resources/translations/validators.en.yml b/src/Marello/Bundle/PaymentBundle/Resources/translations/validators.en.yml new file mode 100644 index 000000000..4a433c817 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/translations/validators.en.yml @@ -0,0 +1,6 @@ +marello: + payment: + number.error: This value should be a number. + paymentrule: + type.config.count.message: 'You must select at least {{ limit }} payment method type|You must select at least {{ limit }} payment method types' + method.config.count.message: 'You must select at least {{ limit }} payment method|You must select at least {{ limit }} payment methods' diff --git a/src/Marello/Bundle/PaymentBundle/Resources/views/Form/fields.html.twig b/src/Marello/Bundle/PaymentBundle/Resources/views/Form/fields.html.twig new file mode 100644 index 000000000..8d88d60e7 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/views/Form/fields.html.twig @@ -0,0 +1,65 @@ +{% block marello_payment_method_config_widget %} + {% import 'MarelloPaymentBundle:PaymentMethodsConfigsRule:macros.html.twig' as PayRuleMacro %} + + {% set collapseView = { + group: 'payment-method', + open: true + } %} + +
+ {% if form.method.vars.value %} +
+
+ + {% set icon = methods_icons[form.method.vars.value]|default('') %} + {% set label = methods_labels[form.method.vars.value]|trans %} + {% if icon %}{{ label }}{% endif %} + {{ label }} + {{ PayRuleMacro.renderPaymentMethodDisabledFlag(form.method.vars.value) }} +
+
+
+ {% endif %} +
+
+ {% set index = (form.method.vars.id|replace({'marello_payment_methods_configs_rule_methodConfigs_': ''})|split('_'))[0] %} + + {{ form_widget(form.options) }} +
+
+
+{% endblock %} + +{% block marello_payment_methods_configs_rule_destination_widget %} + {{ form_row(form.country) }} + {{ form_row(form.region) }} + {{ form_row(form.postalCodes) }} + {{ form_rest(form) }} +{% endblock %} + +{% block marello_payment_method_config_collection_widget %} + {% if form|length != 0 %} +
+
+ {% set collapseView = { + widgetModule: 'oroui/js/widget/collapse-group-widget', + group: 'payment-method' + } %} +
+ + {{ 'marello.payment.payment_methods_table.method.label'|trans }} +
+
{{ 'marello.payment.payment_methods_table.options.label'|trans }}
+
+
+ {{ form_widget(form, {'attr': {'class': 'marello-payment-rule-method-configs-collection row-oro'}}) }} + {{ form_errors(form) }} +
+
+ {% endif %} +{% endblock %} diff --git a/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/Datagrid/configurations.html.twig b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/Datagrid/configurations.html.twig new file mode 100644 index 000000000..75f5a4d81 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/Datagrid/configurations.html.twig @@ -0,0 +1,2 @@ +{% import 'MarelloPaymentBundle:PaymentMethodsConfigsRule:macros.html.twig' as PayRuleMacro %} +{{ PayRuleMacro.renderPaymentMethodsConfigs(record.getValue('methodConfigs').toArray(), record.getValue('currency'))}} diff --git a/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/Datagrid/destinations.html.twig b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/Datagrid/destinations.html.twig new file mode 100644 index 000000000..e3fec9a0e --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/Datagrid/destinations.html.twig @@ -0,0 +1,10 @@ +{% spaceless %} + {% set destinations = record.getValue('destinations').toArray() %} + + {% set escapedDestinations={} %} + {% for key, destination in destinations %} + {% set escapedDestinations = escapedDestinations|merge({(key): destination|e}) %} + {% endfor %} + + {{ escapedDestinations|join('
')|raw }} +{% endspaceless %} diff --git a/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/index.html.twig b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/index.html.twig new file mode 100644 index 000000000..a7c956e62 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/index.html.twig @@ -0,0 +1,17 @@ +{% extends 'OroUIBundle:actions:index.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} +{% set gridName = 'marello-payment-methods-configs-rule-grid' %} +{% set pageTitle = 'marello.payment.paymentmethodsconfigsrule.entity_short_plural_label'|trans %} + +{% block navButtons %} + {% import 'OroUIBundle::macros.html.twig' as UI %} + + {% if is_granted('marello_payment_methods_configs_rule_create') %} +
+ {{ UI.addButton({ + 'path': path('marello_payment_methods_configs_rule_create'), + 'entity_label': 'marello.payment.paymentmethodsconfigsrule.entity_short_label'|trans + }) }} +
+ {% endif %} +{% endblock %} diff --git a/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/macros.html.twig b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/macros.html.twig new file mode 100644 index 000000000..ae9c00480 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/macros.html.twig @@ -0,0 +1,24 @@ +{% macro renderPaymentMethodsConfigs(methodConfigs, currency) %} + {%- spaceless -%} +
    + {%- for methodConfig in methodConfigs -%} +
  1. + {%- set paymentMethodData = { + 'identifier': methodConfig.method, + 'options': methodConfig.options, + } -%} + {%- include marello_payment_method_config_template(methodConfig.method) with { + 'currency': currency, + 'methodData': paymentMethodData + } -%} +
  2. + {%- endfor -%} +
+ {%- endspaceless -%} +{% endmacro %} + +{% macro renderPaymentMethodDisabledFlag(identifier) %} + {% if marello_payment_method_enabled(identifier) == false %} + Disabled + {% endif %} +{% endmacro %} \ No newline at end of file diff --git a/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/paymentMethodWithOptions.html.twig b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/paymentMethodWithOptions.html.twig new file mode 100644 index 000000000..e15451d76 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/paymentMethodWithOptions.html.twig @@ -0,0 +1,6 @@ +{% spaceless %} + {{ marello_get_payment_method_label(methodData.identifier)|trans }}
+ {% for name, value in methodData.options %} + {{ name ~ ': ' ~ value }}
+ {% endfor %} +{% endspaceless %} diff --git a/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/update.html.twig b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/update.html.twig new file mode 100644 index 000000000..3dc814106 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/update.html.twig @@ -0,0 +1,132 @@ +{% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} +{% oro_title_set({params : {"%id%": entity.id|default('N/A'|trans)} }) %} + +{% set formAction = entity.id ? path('marello_payment_methods_configs_rule_update', { 'id': entity.id }) : path('marello_payment_methods_configs_rule_create') %} + +{% block pageHeader %} + {% if entity.id %} + {% set breadcrumbs = { + 'entity': entity, + 'indexPath': path('marello_payment_methods_configs_rule_index'), + 'indexLabel': 'marello.payment.paymentmethodsconfigsrule.entity_short_plural_label'|trans, + 'entityTitle': entity.rule.name|slice(0, 50) + } %} + {{ parent() }} + {% else %} + {% set breadcrumbs = { + 'indexLabel': 'Create', + 'entityTitle': 'Payment Rule', + 'indexPath': path('marello_payment_methods_configs_rule_create') + } %} + {% set title = 'oro.ui.create_entity'|trans({'%entityName%': 'marello.payment.paymentmethodsconfigsrule.entity_short_label'|trans}) %} + {% include 'OroUIBundle::page_title_block.html.twig' with { title: title } %} + {% endif %} +{% endblock pageHeader %} + +{% block navButtons %} + {{ parent() }} + + {{ UI.cancelButton(path('marello_payment_methods_configs_rule_index')) }} + {% if entity.id and is_granted('marello_payment_methods_configs_rule_update') or is_granted('omarello_payment_methods_configs_rule_create') %} + {% set html = '' %} + {% if is_granted('marello_payment_methods_configs_rule_view') %} + {% set html = UI.saveAndCloseButton({ + 'route': 'marello_payment_methods_configs_rule_view', + 'params': {'id': '$id'} + }) %} + {% endif %} + {% set html = html ~ UI.saveAndStayButton({ + 'route': 'marello_payment_methods_configs_rule_update', + 'params': {'id': '$id'} + }) %} + + {{ UI.dropdownSaveButton({'html': html}) }} + {% endif %} +{% endblock navButtons %} + +{% block marello_payment_rule_methods %} + {% set updateFlag = constant('Marello\\Bundle\\ShippingBundle\\Form\\Handler\\ShippingMethodsConfigsRuleHandler::UPDATE_FLAG') %} +
+ {{ block('marello_payment_rule_add_method_widget') }} + + {% if form.method.vars.choices|length == 0 %} +
+ + {{ 'marello.payment.sections.paymentrule_configurations.no_methods.message'|trans }} + +
+ {% endif %} + + {{ form_widget(form.methodConfigs) }} +
+{% endblock %} + +{% block marello_payment_rule_add_method_widget %} +
+
+ {{ form_row(form.method, {'attr': {'class': ' no-uniform '}}) }} +
+ + {{ form.vars.add_label|default('oro.form.collection.add')|trans }} + + + {{ form.vars.add_label|default('oro.form.collection.add_all')|trans }} + +
+{% endblock %} + +{% block content_data %} + {% set id = 'payment-methods-configs-rule-edit' %} + + {% set dataBlocks = [ + { + 'title': 'marello.payment.sections.general'|trans, + 'class': 'active', + 'subblocks': [ + { + 'data': [ + form_row(form.rule.enabled), + form_row(form.rule.name), + form_row(form.rule.sortOrder), + form_row(form.currency), + form_row(form.rule.stopProcessing), + ] + }, + ] + }, + { + 'title': 'marello.payment.sections.destination'|trans, + 'subblocks': [ + { + 'data': [ + form_widget(form.destinations, {'attr': {'class': 'marello-payment-rule-collection marello-payment-rule-destinations-collection row-oro'}}), + ] + } + ] + }, + { + 'title': 'marello.payment.sections.paymentrule_configurations.label'|trans, + 'content_attr': { + 'class': 'payment-rule-methods-wrapper' + }, + 'subblocks': [{ + 'data': [ + block('marello_payment_rule_methods') + ] + }] + }] %} + + {% set data = { + 'formErrors': form_errors(form)|nl2br, + 'dataBlocks': dataBlocks + } %} + + {{ parent() }} +{% endblock %} diff --git a/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/view.html.twig b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/view.html.twig new file mode 100644 index 000000000..af8fbab53 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Resources/views/PaymentMethodsConfigsRule/view.html.twig @@ -0,0 +1,69 @@ +{% extends 'OroUIBundle:actions:view.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} +{% import 'OroEntityConfigBundle::macros.html.twig' as entityConfig %} +{% import 'MarelloPaymentBundle:PaymentMethodsConfigsRule:macros.html.twig' as PayRuleMacro %} + +{% oro_title_set({params : {"%label%": entity.rule.name|default('N/A') }}) %} + +{% block pageHeader %} + {% set breadcrumbs = { + 'entity': entity, + 'indexPath': path('marello_payment_methods_configs_rule_index'), + 'indexLabel': 'marello.payment.paymentmethodsconfigsrule.entity_short_plural_label'|trans, + 'entityTitle': entity.rule.name|default('N/A'|trans) + } %} + {{ parent() }} +{% endblock pageHeader %} + +{% block content_data %} + {% import 'OroUIBundle::macros.html.twig' as UI %} + + {%- set paymentRuleInformation -%} +
+
+ {{ UI.renderProperty('marello.rule.name.label'|trans, entity.rule.name) }} + {{ UI.renderProperty('marello.rule.enabled.label'|trans, entity.rule.enabled ? + 'marello.rule.enabled.yes.label'|trans + : + 'marello.rule.enabled.no.label'|trans + ) }} + {{ UI.renderProperty('marello.rule.sort_order.label'|trans, entity.rule.sortOrder) }} + {{ UI.renderProperty('marello.payment.paymentmethodsconfigsrule.currency.label'|trans, entity.currency) }} + + {% if entity.destinations.count %} + {{ UI.renderHtmlProperty( + 'marello.payment.paymentmethodsconfigsrule.destinations.label'|trans, + UI.renderList(entity.destinations)) }} + {% endif %} + + {% if entity.methodConfigs.count %} + {{ UI.renderHtmlProperty( + 'marello.payment.paymentmethodsconfigsrule.method_configs.label'|trans, + PayRuleMacro.renderPaymentMethodsConfigs(entity.methodConfigs, entity.currency)) }} + {% endif %} +
+
+ {{ entityConfig.renderDynamicFields(entity) }} +
+
+ {%- endset -%} + + {% set dataBlocks = [ + { + 'title': 'oro.entity_config.block_titles.general.label'|trans, + 'class': 'active', + 'subblocks': [ + {'data' : [paymentRuleInformation]} + ] + } + ] %} + + {% set id = 'marello-payment-rule-view' %} + {% set data = {'dataBlocks': dataBlocks} %} + + {{ parent() }} +{% endblock content_data %} + +{% block stats %} + {# Must be empty, because parent view uses this block to render dates, which are not implemented in this entity #} +{% endblock stats %} diff --git a/src/Marello/Bundle/PaymentBundle/RuleFiltration/Basic/BasicMethodsConfigsRulesFiltrationService.php b/src/Marello/Bundle/PaymentBundle/RuleFiltration/Basic/BasicMethodsConfigsRulesFiltrationService.php new file mode 100644 index 000000000..45504128b --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/RuleFiltration/Basic/BasicMethodsConfigsRulesFiltrationService.php @@ -0,0 +1,48 @@ +filtrationService = $filtrationService; + $this->paymentContextToRulesValueConverter = $converter; + } + + /** + * {@inheritDoc} + */ + public function getFilteredPaymentMethodsConfigsRules( + array $paymentMethodsConfigsRules, + PaymentContextInterface $context + ) { + $arrayContext = $this->paymentContextToRulesValueConverter->convert($context); + + return $this->filtrationService->getFilteredRuleOwners( + $paymentMethodsConfigsRules, + $arrayContext + ); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/RuleFiltration/MethodsConfigsRulesFiltrationServiceInterface.php b/src/Marello/Bundle/PaymentBundle/RuleFiltration/MethodsConfigsRulesFiltrationServiceInterface.php new file mode 100644 index 000000000..662e214bd --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/RuleFiltration/MethodsConfigsRulesFiltrationServiceInterface.php @@ -0,0 +1,20 @@ +initClient(); + $this->client->useHashNavigation(true); + $this->loadFixtures( + [ + LoadPaymentMethodsConfigsRulesWithConfigs::class, + LoadUserData::class + ] + ); + $this->paymentMethodProvider = static::getContainer()->get('marello_payment.payment_method.composite_provider'); + $this->translator = static::getContainer()->get('translator'); + } + + public function testIndexWithoutCreate() + { + $this->initClient([], static::generateBasicAuthHeader(LoadUserData::USER_VIEWER, LoadUserData::USER_VIEWER)); + $crawler = $this->client->request('GET', $this->getUrl('marello_payment_methods_configs_rule_index')); + $result = $this->client->getResponse(); + static::assertHtmlResponseStatusCodeEquals($result, 200); + static::assertEquals(0, $crawler->selectLink('Create Payment Rule')->count()); + } + + /** + * @return string + */ + public function testCreate() + { + $this->initClient( + [], + static::generateBasicAuthHeader(LoadUserData::USER_VIEWER_CREATOR, LoadUserData::USER_VIEWER_CREATOR) + ); + $crawler = $this->client->request('GET', $this->getUrl('marello_payment_methods_configs_rule_create')); + + /** @var Form $form */ + $form = $crawler + ->selectButton('Save and Close') + ->form(); + + $name = 'New Rule'; + + $formValues = $form->getPhpValues(); + $formValues['marello_payment_methods_configs_rule']['rule']['name'] = $name; + $formValues['marello_payment_methods_configs_rule']['rule']['enabled'] = false; + $formValues['marello_payment_methods_configs_rule']['currency'] = 'USD'; + $formValues['marello_payment_methods_configs_rule']['rule']['sortOrder'] = 1; + $formValues['marello_payment_methods_configs_rule']['destinations'] = + [ + [ + 'postalCodes' => '54321', + 'country' => 'FR', + 'region' => 'FR-75' + ] + ]; + $formValues['marello_payment_methods_configs_rule']['methodConfigs'] = + [ + [ + 'method' => $this->getPaymentTermIdentifier(), + 'options' => '', + ] + ]; + + $this->client->followRedirects(true); + $crawler = $this->client->request($form->getMethod(), $form->getUri(), $formValues); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + + $html = $crawler->html(); + + $this->assertContains('Payment rule has been saved', $html); + $this->assertContains('No', $html); + + return $name; + } + + /** + * @depends testCreate + * + * @param string $name + */ + public function testIndex($name) + { + $auth = static::generateBasicAuthHeader(LoadUserData::USER_VIEWER_CREATOR, LoadUserData::USER_VIEWER_CREATOR); + $this->initClient([], $auth); + $crawler = $this->client->request('GET', $this->getUrl('marello_payment_methods_configs_rule_index')); + $result = $this->client->getResponse(); + static::assertHtmlResponseStatusCodeEquals($result, 200); + static::assertContains('marello-payment-methods-configs-rule-grid', $crawler->html()); + $href = $crawler->selectLink('Create Payment Rule')->attr('href'); + static::assertEquals($this->getUrl('marello_payment_methods_configs_rule_create'), $href); + + $response = $this->client->requestGrid( + [ + 'gridName' => 'marello-payment-methods-configs-rule-grid', + 'marello-payment-methods-configs-rule-grid[_sort_by][id]' => 'ASC', + ] + ); + + static::getJsonResponseContent($response, 200); + } + + /** + * @depends testCreate + * + * @param string $name + */ + public function testView($name) + { + $this->initClient([], static::generateBasicAuthHeader()); + $paymentRule = $this->getPaymentMethodsConfigsRuleByName($name); + + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_payment_methods_configs_rule_view', ['id' => $paymentRule->getId()]) + ); + + $result = $this->client->getResponse(); + static::assertHtmlResponseStatusCodeEquals($result, 200); + + $html = $crawler->html(); + + $this->assertContains($paymentRule->getRule()->getName(), $html); + $destination = $paymentRule->getDestinations(); + $this->assertContains((string)$destination[0], $html); + $methodConfigs = $paymentRule->getMethodConfigs(); + $label = $this->paymentMethodProvider + ->getPaymentMethod($methodConfigs[0]->getMethod()) + ->getLabel(); + $this->assertContains($this->translator->trans($label), $html); + } + + /** + * @depends testCreate + * + * @param string $name + * + * @return PaymentMethodsConfigsRule|object|null + */ + public function testUpdate($name) + { + $paymentRule = $this->getPaymentMethodsConfigsRuleByName($name); + + $this->assertNotEmpty($paymentRule); + + $id = $paymentRule->getId(); + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_payment_methods_configs_rule_update', ['id' => $id]) + ); + + /** @var Form $form */ + $form = $crawler->selectButton('Save and Close')->form(); + + $newName = 'New name for new rule'; + $formValues = $form->getPhpValues(); + $formValues['marello_payment_methods_configs_rule']['rule']['name'] = $newName; + $formValues['marello_payment_methods_configs_rule']['rule']['enabled'] = false; + $formValues['marello_payment_methods_configs_rule']['currency'] = 'USD'; + $formValues['marello_payment_methods_configs_rule']['rule']['sortOrder'] = 1; + $formValues['marello_payment_methods_configs_rule']['destinations'] = + [ + [ + 'postalCodes' => '54321', + 'country' => 'TH', + 'region' => 'TH-83' + ] + ]; + $formValues['marello_payment_methods_configs_rule']['methodConfigs'] = + [ + [ + 'method' => $this->getPaymentTermIdentifier(), + 'options' => '', + ] + ]; + + $this->client->followRedirects(true); + $crawler = $this->client->request($form->getMethod(), $form->getUri(), $formValues); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + $html = $crawler->html(); + static::assertContains('Payment rule has been saved', $html); + + $paymentRule = $this->getPaymentMethodsConfigsRuleByName($newName); + static::assertEquals($id, $paymentRule->getId()); + + $destination = $paymentRule->getDestinations(); + static::assertEquals('TH', $destination[0]->getCountry()->getIso2Code()); + static::assertEquals('TH-83', $destination[0]->getRegion()->getCombinedCode()); + static::assertEquals('54321', $destination[0]->getPostalCodes()->current()->getName()); + $methodConfigs = $paymentRule->getMethodConfigs(); + static::assertEquals($this->getPaymentTermIdentifier(), $methodConfigs[0]->getMethod()); + static::assertFalse($paymentRule->getRule()->isEnabled()); + + return $paymentRule; + } + + /** + * @depends testUpdate + * + * @param PaymentMethodsConfigsRule $paymentRule + */ + public function testCancel(PaymentMethodsConfigsRule $paymentRule) + { + $paymentRule = $this->getPaymentMethodsConfigsRuleByName($paymentRule->getRule()->getName()); + + $this->assertNotEmpty($paymentRule); + + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_payment_methods_configs_rule_update', ['id' => $paymentRule->getId()]) + ); + + $link = $crawler->selectLink('Cancel')->link(); + $this->client->click($link); + $response = $this->client->getResponse(); + + static::assertHtmlResponseStatusCodeEquals($response, 200); + + $html = $response->getContent(); + + static::assertContains($paymentRule->getRule()->getName(), $html); + $destination = $paymentRule->getDestinations(); + static::assertContains((string)$destination[0], $html); + $methodConfigs = $paymentRule->getMethodConfigs(); + $label = $this->paymentMethodProvider + ->getPaymentMethod($methodConfigs[0]->getMethod()) + ->getLabel(); + static::assertContains($this->translator->trans($label), $html); + } + + /** + * @depends testUpdate + * + * @param PaymentMethodsConfigsRule $paymentRule + * + * @return PaymentMethodsConfigsRule + */ + public function testUpdateRemoveDestination(PaymentMethodsConfigsRule $paymentRule) + { + $this->assertNotEmpty($paymentRule); + + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_payment_methods_configs_rule_update', ['id' => $paymentRule->getId()]) + ); + + /** @var Form $form */ + $form = $crawler->selectButton('Save and Close')->form(); + + $formValues = $form->getPhpValues(); + $formValues['marello_payment_methods_configs_rule']['destinations'] = []; + + $this->client->followRedirects(true); + $this->client->request($form->getMethod(), $form->getUri(), $formValues); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + $paymentRule = $this->getEntityManager()->find( + 'MarelloPaymentBundle:PaymentMethodsConfigsRule', + $paymentRule->getId() + ); + static::assertCount(0, $paymentRule->getDestinations()); + + return $paymentRule; + } + + public function testStatusDisableMass() + { + $this->initClient([], static::generateBasicAuthHeader()); + /** @var PaymentMethodsConfigsRule $paymentRule1 */ + $paymentRule1 = $this->getReference('payment_rule.1'); + /** @var PaymentMethodsConfigsRule $paymentRule2 */ + $paymentRule2 = $this->getReference('payment_rule.2'); + $url = $this->getUrl( + 'marello_payment_methods_configs_massaction', + [ + 'gridName' => 'marello-payment-methods-configs-rule-grid', + 'actionName' => 'disable', + 'inset' => 1, + 'values' => sprintf( + '%s,%s', + $paymentRule1->getId(), + $paymentRule2->getId() + ) + ] + ); + $this->ajaxRequest('GET', $url); + $result = $this->client->getResponse(); + $data = json_decode($result->getContent(), true); + $this->assertTrue($data['successful']); + $this->assertSame(2, $data['count']); + $this->assertFalse( + $this + ->getPaymentMethodsConfigsRuleById($paymentRule1->getId()) + ->getRule() + ->isEnabled() + ); + $this->assertFalse( + $this + ->getPaymentMethodsConfigsRuleById($paymentRule2->getId()) + ->getRule() + ->isEnabled() + ); + } + + /** + * @depends testStatusDisableMass + */ + public function testStatusEnableMass() + { + $this->initClient([], static::generateBasicAuthHeader()); + /** @var PaymentMethodsConfigsRule $paymentRule1 */ + $paymentRule1 = $this->getReference('payment_rule.1'); + /** @var PaymentMethodsConfigsRule $paymentRule2 */ + $paymentRule2 = $this->getReference('payment_rule.2'); + $url = $this->getUrl( + 'marello_payment_methods_configs_massaction', + [ + 'gridName' => 'marello-payment-methods-configs-rule-grid', + 'actionName' => 'enable', + 'inset' => 1, + 'values' => sprintf( + '%s,%s', + $paymentRule1->getId(), + $paymentRule2->getId() + ) + ] + ); + $this->ajaxRequest('GET', $url); + $result = $this->client->getResponse(); + $data = json_decode($result->getContent(), true); + $this->assertTrue($data['successful']); + $this->assertSame(2, $data['count']); + $this->assertTrue( + $this + ->getPaymentMethodsConfigsRuleById($paymentRule1->getId()) + ->getRule() + ->isEnabled() + ); + $this->assertTrue( + $this + ->getPaymentMethodsConfigsRuleById($paymentRule2->getId()) + ->getRule() + ->isEnabled() + ); + } + + public function testPaymentMethodsConfigsRuleEditWOPermission() + { + $authParams = static::generateBasicAuthHeader(LoadUserData::USER_VIEWER, LoadUserData::USER_VIEWER); + $this->initClient([], $authParams); + + /** @var PaymentMethodsConfigsRule $paymentRule */ + $paymentRule = $this->getReference('payment_rule.1'); + + $this->client->request( + 'GET', + $this->getUrl('marello_payment_methods_configs_rule_update', ['id' => $paymentRule->getId()]) + ); + + static::assertJsonResponseStatusCodeEquals($this->client->getResponse(), 403); + } + + public function testPaymentMethodsConfigsRuleEdit() + { + $authParams = static::generateBasicAuthHeader(LoadUserData::USER_EDITOR, LoadUserData::USER_EDITOR); + $this->initClient([], $authParams); + + /** @var PaymentMethodsConfigsRule $paymentRule */ + $paymentRule = $this->getReference('payment_rule.1'); + + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_payment_methods_configs_rule_update', ['id' => $paymentRule->getId()]) + ); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + + /** @var Form $form */ + $form = $crawler->selectButton('Save')->form(); + + $rule = $paymentRule->getRule(); + $form['marello_payment_methods_configs_rule[rule][enabled]'] = !$rule->isEnabled(); + $form['marello_payment_methods_configs_rule[rule][name]'] = $rule->getName() . ' new name'; + $form['marello_payment_methods_configs_rule[rule][sortOrder]'] = $rule->getSortOrder() + 1; + $form['marello_payment_methods_configs_rule[currency]'] = $paymentRule->getCurrency() === 'USD' ? 'EUR' : 'USD'; + $form['marello_payment_methods_configs_rule[rule][stopProcessing]'] = !$rule->isStopProcessing(); + $form['marello_payment_methods_configs_rule[destinations][0][postalCodes]'] = '11111'; + + $this->client->followRedirects(true); + $crawler = $this->client->submit($form); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + static::assertContains('Payment rule has been saved', $crawler->html()); + } + + public function testDeleteButtonNotVisible() + { + $authParams = static::generateBasicAuthHeader(LoadUserData::USER_VIEWER, LoadUserData::USER_VIEWER); + $this->initClient([], $authParams); + + $response = $this->client->requestGrid( + ['gridName' => 'marello-payment-methods-configs-rule-grid'], + [], + true + ); + + $result = static::getJsonResponseContent($response, 200); + + $this->assertEquals(false, isset($result['metadata']['massActions']['delete'])); + } + + /** + * @return ObjectManager|null + */ + protected function getEntityManager() + { + return static::getContainer() + ->get('doctrine') + ->getManagerForClass('MarelloPaymentBundle:PaymentMethodsConfigsRule'); + } + + /** + * @param string $name + * + * @return PaymentMethodsConfigsRule|null + */ + protected function getPaymentMethodsConfigsRuleByName($name) + { + /** @var RuleInterface $rule */ + $rule = $this + ->getEntityManager() + ->getRepository('MarelloRuleBundle:Rule') + ->findOneBy(['name' => $name]); + + return $this + ->getEntityManager() + ->getRepository('MarelloPaymentBundle:PaymentMethodsConfigsRule') + ->findOneBy(['rule' => $rule]); + } + + /** + * @param int $id + * + * @return PaymentMethodsConfigsRule|null + */ + protected function getPaymentMethodsConfigsRuleById($id) + { + return $this->getEntityManager() + ->getRepository('MarelloPaymentBundle:PaymentMethodsConfigsRule') + ->find($id); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadPaymentMethodConfigsWithFakeMethods.php b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadPaymentMethodConfigsWithFakeMethods.php new file mode 100644 index 000000000..1db26c407 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadPaymentMethodConfigsWithFakeMethods.php @@ -0,0 +1,83 @@ +getPaymentMethodConfigsData() as $reference => $data) { + $this->loadPaymentMethodConfig($reference, $data, $manager); + } + + $manager->flush(); + } + + /** + * @return array + */ + protected function getPaymentMethodConfigsData() + { + return Yaml::parse(file_get_contents(__DIR__.'/data/payment_method_configs_with_fake_methods.yml')); + } + + /** + * @param string $reference + * @param array $data + * @param ObjectManager $manager + */ + private function loadPaymentMethodConfig($reference, $data, ObjectManager $manager) + { + $methodsConfigsRule = $this->getPaymentMethodsConfigsRule($data['methods_configs_rule']); + + $methodConfig = $this->createMethodConfig($methodsConfigsRule, $data['method']); + + $manager->persist($methodConfig); + + $this->setReference($reference, $methodConfig); + } + + /** + * @param PaymentMethodsConfigsRule $configsRule + * @param string $method + * + * @return PaymentMethodConfig + */ + private function createMethodConfig(PaymentMethodsConfigsRule $configsRule, $method) + { + $methodConfig = new PaymentMethodConfig(); + + return $methodConfig->setMethodsConfigsRule($configsRule) + ->setMethod($method); + } + + /** + * @param string $reference + * + * @return PaymentMethodsConfigsRule|object + */ + private function getPaymentMethodsConfigsRule($reference) + { + return $this->getReference($reference); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadPaymentMethodsConfigsRules.php b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadPaymentMethodsConfigsRules.php new file mode 100644 index 000000000..75b39894b --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadPaymentMethodsConfigsRules.php @@ -0,0 +1,79 @@ +getPaymentMethodsConfigsRulesData() as $reference => $data) { + $this->loadPaymentMethodsConfigsRule($reference, $data, $manager); + } + + $manager->flush(); + } + + /** + * @return array + */ + protected function getPaymentMethodsConfigsRulesData() + { + return Yaml::parse(file_get_contents(__DIR__.'/data/payment_methods_configs_rules.yml')); + } + + /** + * @param string $reference + * @param array $data + * @param ObjectManager $manager + */ + private function loadPaymentMethodsConfigsRule($reference, $data, ObjectManager $manager) + { + $rule = $this->buildRule($reference, $data['rule']); + + $configRule = $this->createMethodsConfigsRule($rule, $data['currency']); + + $manager->persist($configRule); + + $this->setReference($reference, $configRule); + } + + /** + * @param RuleInterface $rule + * @param $currency + * + * @return PaymentMethodsConfigsRule + */ + private function createMethodsConfigsRule(RuleInterface $rule, $currency) + { + $configRule = new PaymentMethodsConfigsRule(); + + return $configRule->setRule($rule) + ->setCurrency($currency); + } + + /** + * @param string $reference + * @param array $ruleData + * + * @return RuleInterface + */ + private function buildRule($reference, $ruleData) + { + $rule = new Rule(); + + return $rule->setName($reference) + ->setEnabled($ruleData['enabled']) + ->setSortOrder($ruleData['sortOrder']) + ->setExpression($ruleData['expression']); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadPaymentMethodsConfigsRulesWithConfigs.php b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadPaymentMethodsConfigsRulesWithConfigs.php new file mode 100644 index 000000000..d15b56083 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadPaymentMethodsConfigsRulesWithConfigs.php @@ -0,0 +1,206 @@ +getPaymentRuleData() as $reference => $data) { + $this->loadPaymentRule($reference, $data, $manager); + } + + $manager->flush(); + } + + /** + * @return array + */ + protected function getPaymentRuleData(): array + { + return Yaml::parse(file_get_contents(__DIR__.'/data/payment_methods_configs_rules_with_configs.yml')); + } + + /** + * @param string $reference + * @param array $data + * @param ObjectManager $manager + */ + private function loadPaymentRule($reference, $data, ObjectManager $manager) + { + $rule = $this->buildRule($reference, $data); + $configRule = $this->buildMethodsConfigsRule($reference, $data, $rule, $manager); + + $this->setReference($reference, $configRule); + + $manager->persist($configRule); + } + + /** + * @param string $reference + * @param array $data + * @param RuleInterface $rule + * @param ObjectManager $manager + * + * @return PaymentMethodsConfigsRule + */ + protected function buildMethodsConfigsRule( + string $reference, + array $data, + RuleInterface $rule, + ObjectManager $manager + ) { + $configRule = new PaymentMethodsConfigsRule(); + + $configRule + ->setRule($rule) + ->setCurrency($data['currency']) + ->setOrganization($this->getOrganization()); + + $this->setDestinations($configRule, $manager, $data); + $this->setMethodConfigs($configRule, $manager, $data); + + return $configRule; + } + + /** + * @param string $reference + * @param array $data + * + * @return Rule + */ + protected function buildRule(string $reference, array $data) + { + $rule = new Rule(); + + $rule->setName($reference) + ->setEnabled($data['rule']['enabled']) + ->setSortOrder($data['rule']['sortOrder']) + ->setExpression($data['rule']['expression']); + + return $rule; + } + + /** + * @param PaymentMethodsConfigsRule $configRule + * @param ObjectManager $manager + * @param array $data + */ + private function setDestinations(PaymentMethodsConfigsRule $configRule, ObjectManager $manager, $data) + { + if (!array_key_exists('destinations', $data)) { + return; + } + + foreach ($data['destinations'] as $destination) { + /** @var Country $country */ + $country = $manager + ->getRepository('OroAddressBundle:Country') + ->findOneBy(['iso2Code' => $destination['country']]); + + $paymentRuleDestination = new PaymentMethodsConfigsRuleDestination(); + $paymentRuleDestination + ->setMethodsConfigsRule($configRule) + ->setCountry($country); + + if (array_key_exists('region', $destination)) { + /** @var Region $region */ + $region = $manager + ->getRepository('OroAddressBundle:Region') + ->findOneBy(['combinedCode' => $destination['country'].'-'.$destination['region']]); + $paymentRuleDestination->setRegion($region); + } + + if (array_key_exists('postalCodes', $destination)) { + foreach ($destination['postalCodes'] as $postalCode) { + $destinationPostalCode = new PaymentMethodsConfigsRuleDestinationPostalCode(); + $destinationPostalCode->setName($postalCode['name']) + ->setDestination($paymentRuleDestination); + + $paymentRuleDestination->addPostalCode($destinationPostalCode); + } + } + + $manager->persist($paymentRuleDestination); + $configRule->addDestination($paymentRuleDestination); + } + } + + /** + * @param PaymentMethodsConfigsRule $configRule + * @param ObjectManager $manager + * @param array $data + */ + private function setMethodConfigs(PaymentMethodsConfigsRule $configRule, ObjectManager $manager, $data) + { + if (!array_key_exists('methodConfigs', $data)) { + return; + } + + $methodConfig = $this->buildMethodConfig($configRule); + $configRule->addMethodConfig($methodConfig); + + $manager->persist($methodConfig); + } + + /** + * @param PaymentMethodsConfigsRule $configRule + * + * @return PaymentMethodConfig + */ + private function buildMethodConfig(PaymentMethodsConfigsRule $configRule) + { + $methodConfig = new PaymentMethodConfig(); + + $methodConfig + ->setMethodsConfigsRule($configRule) + ->setMethod($this->getPaymentTermIdentifier()); + + return $methodConfig; + } + + /** + * @return Organization + */ + private function getOrganization() + { + return $this->container->get('doctrine') + ->getRepository('OroOrganizationBundle:Organization') + ->getFirst(); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadUserData.php b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadUserData.php new file mode 100644 index 000000000..1c3d14c35 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/LoadUserData.php @@ -0,0 +1,113 @@ + [ + [ + 'class' => PaymentMethodsConfigsRule::class, + 'acls' => [ + [ + AbstractLoadMultipleUserData::ACL_PERMISSION => 'VIEW', + AbstractLoadMultipleUserData::ACL_LEVEL => 'SYSTEM', + ], + ], + ], + [ + 'class' => Channel::class, + 'acls' => [ + [ + AbstractLoadMultipleUserData::ACL_PERMISSION => 'VIEW', + AbstractLoadMultipleUserData::ACL_LEVEL => 'SYSTEM', + ], + ], + ], + ], + self::ROLE_EDIT => [ + [ + 'class' => PaymentMethodsConfigsRule::class, + 'acls' => [ + [ + AbstractLoadMultipleUserData::ACL_PERMISSION => 'EDIT', + AbstractLoadMultipleUserData::ACL_LEVEL => 'SYSTEM', + ], + ], + ], + ], + self::ROLE_CREATE => [ + [ + 'class' => PaymentMethodsConfigsRule::class, + 'acls' => [ + [ + AbstractLoadMultipleUserData::ACL_PERMISSION => 'CREATE', + AbstractLoadMultipleUserData::ACL_LEVEL => 'SYSTEM', + ], + ], + ], + ], + ]; + + /** + * @var array + */ + protected $users = [ + [ + 'email' => 'payment-user-viewer@example.com', + 'username' => self::USER_VIEWER, + 'password' => self::USER_VIEWER, + 'firstname' => 'PaymentUser1FN', + 'lastname' => 'PaymentUser1LN', + 'roles' => [self::ROLE_VIEW], + ], + [ + 'email' => 'payment-user-editor@example.com', + 'username' => self::USER_EDITOR, + 'password' => self::USER_EDITOR, + 'firstname' => 'PaymentUser2FN', + 'lastname' => 'PaymentUser2LN', + 'roles' => [self::ROLE_VIEW, self::ROLE_EDIT], + ], + [ + 'email' => 'payment-user-viewer-creator@example.com', + 'username' => self::USER_VIEWER_CREATOR, + 'password' => self::USER_VIEWER_CREATOR, + 'firstname' => 'PaymentUser2FN', + 'lastname' => 'PaymentUser2LN', + 'roles' => [self::ROLE_VIEW, self::ROLE_CREATE], + ] + ]; + + /** + * {@inheritdoc} + */ + protected function getRolesData() + { + return $this->roles; + } + + /** + * {@inheritdoc} + */ + protected function getUsersData() + { + return $this->users; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/data/payment_method_configs_with_fake_methods.yml b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/data/payment_method_configs_with_fake_methods.yml new file mode 100644 index 000000000..e6dc6fc28 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/data/payment_method_configs_with_fake_methods.yml @@ -0,0 +1,15 @@ +payment_rule.1.method_config.1: + methods_configs_rule: payment_rule.1 + method: ups + +payment_rule.2.method_config.1: + methods_configs_rule: payment_rule.2 + method: flat_rate + +payment_rule.3.method_config.1: + methods_configs_rule: payment_rule.3 + method: dpd + +payment_rule.3.method_config_without_type_configs: + methods_configs_rule: payment_rule.3 + method: ups diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/data/payment_methods_configs_rules.yml b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/data/payment_methods_configs_rules.yml new file mode 100644 index 000000000..778939764 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/data/payment_methods_configs_rules.yml @@ -0,0 +1,27 @@ +payment_rule.1: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'EUR' + +payment_rule.2: + rule: + enabled: true + sortOrder: 1 + expression: 'true' + currency: 'EUR' + +payment_rule.3: + rule: + enabled: false + sortOrder: 2 + expression: 'true' + currency: 'EUR' + +payment_rule.4: + rule: + enabled: true + sortOrder: 3 + expression: 'true' + currency: 'EUR' diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/data/payment_methods_configs_rules_with_configs.yml b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/data/payment_methods_configs_rules_with_configs.yml new file mode 100644 index 000000000..64b6d3be0 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/DataFixtures/data/payment_methods_configs_rules_with_configs.yml @@ -0,0 +1,205 @@ +payment_rule.1: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_order' + price: 10 + destinations: + - + postalCodes: + - + name: '12345' + country: 'US' + region: 'NY' +payment_rule.2: + rule: + enabled: true + sortOrder: 1 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + country: 'US' + - + country: 'FR' + region: 'FR-75' +payment_rule.3: + rule: + enabled: false + sortOrder: 2 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + country: 'US' + region: 'NY' + - + country: 'FR' +payment_rule.4: + rule: + enabled: true + sortOrder: 2 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + postalCodes: + - + name: '12345' + - + name: '12346' + country: 'US' + region: 'NY' +payment_rule.5: + rule: + enabled: true + sortOrder: 2 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 +payment_rule.6: + rule: + enabled: true + sortOrder: 2 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + country: 'FR' +payment_rule.7: + rule: + enabled: true + sortOrder: 3 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + postalCodes: + - + name: '12346' + country: 'US' + region: 'NY' +payment_rule.8: + rule: + enabled: true + sortOrder: 3 + expression: 'true' + currency: 'EUR' + destinations: + - + country: 'US' + region: 'AL' +payment_rule.9: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'USD' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_order' + price: 10 +payment_rule.10: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'UAH' +payment_rule.11: + rule: + enabled: false + sortOrder: 0 + expression: 'true' + currency: 'UAH' +payment_rule.12: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'UAH' + destinations: + - + country: 'US' + region: 'NY' +payment_rule_without_type_configs: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'USD' + methodConfigs: + - + typeConfigs: [] +payment_rule_with_disabled_type_configs: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'USD' + methodConfigs: + - + typeConfigs: + - + enabled: false + options: + type: 'per_order' + price: 10 diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/Entity/Repository/PaymentMethodConfigRepositoryTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Functional/Entity/Repository/PaymentMethodConfigRepositoryTest.php new file mode 100644 index 000000000..11ad631e0 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/Entity/Repository/PaymentMethodConfigRepositoryTest.php @@ -0,0 +1,51 @@ +initClient([], static::generateBasicAuthHeader()); + $this->loadFixtures([ + LoadPaymentMethodConfigsWithFakeMethods::class, + ]); + + $this->repository = static::getContainer()->get('doctrine') + ->getRepository('MarelloPaymentBundle:PaymentMethodConfig'); + } + + public function testDeleteByMethod() + { + $method = 'ups'; + + static::assertNotEmpty($this->repository->findByMethod($method)); + + $this->repository->deleteByMethod($method); + + static::assertEmpty($this->repository->findByMethod($method)); + } + + public function testDeleteMethodConfigByIds() + { + $ids = [ + $this->getReference('payment_rule.3.method_config_without_type_configs')->getId(), + ]; + + $this->repository->deleteByIds($ids); + + static::assertEmpty($this->repository->findBy(['id' => $ids])); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/Entity/Repository/PaymentMethodsConfigsRuleRepositoryTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Functional/Entity/Repository/PaymentMethodsConfigsRuleRepositoryTest.php new file mode 100644 index 000000000..cf1a50636 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/Entity/Repository/PaymentMethodsConfigsRuleRepositoryTest.php @@ -0,0 +1,248 @@ +initClient([], static::generateBasicAuthHeader()); + $this->client->useHashNavigation(true); + + $this->loadFixtures([ + LoadPaymentMethodsConfigsRulesWithConfigs::class, + ]); + + $this->em = static::getContainer()->get('doctrine') + ->getManagerForClass('MarelloPaymentBundle:PaymentMethodsConfigsRule'); + $this->repository = $this->em->getRepository('MarelloPaymentBundle:PaymentMethodsConfigsRule'); + } + + /** + * @param array $entities + * + * @return array + */ + private function getEntitiesIds(array $entities) + { + return array_map(function ($entity) { + return $entity->getId(); + }, $entities); + } + + /** + * @dataProvider getByDestinationAndCurrencyDataProvider + * + * @param array $shippingAddressData + * @param string $currency + * @param PaymentMethodsConfigsRule[] $expectedRules + */ + public function testGetByDestinationAndCurrency(array $shippingAddressData, $currency, array $expectedRules) + { + $expectedRulesIds = $this->getEntitiesIds($this->getEntitiesByReferences($expectedRules)); + $actualRules = $this->repository->getByDestinationAndCurrency( + $this->createShippingAddress($shippingAddressData), + $currency + ); + + $this->assertEquals($expectedRulesIds, $this->getEntitiesIds($actualRules)); + } + + /** + * @return array + */ + public function getByDestinationAndCurrencyDataProvider() + { + return [ + [ + 'shippingAddress' => [ + 'country' => 'US', + 'region' => [ + 'combinedCode' => 'US-NY', + 'code' => 'NY', + ], + 'postalCode' => '12345', + ], + 'currency' => 'EUR', + 'expectedRulesIds' => [ + 'payment_rule.1', + 'payment_rule.2', + 'payment_rule.3', + 'payment_rule.4', + 'payment_rule.5', + ] + ], + ]; + } + + public function testGetByCurrencyWithoutDestination() + { + $currency = 'UAH'; + $expectedRules = $this->getEntitiesByReferences([ + 'payment_rule.10', + 'payment_rule.11' + ]); + + $actualRules = $this->repository->getByCurrencyWithoutDestination($currency); + + $this->assertEquals($this->getEntitiesIds($expectedRules), $this->getEntitiesIds($actualRules)); + } + + public function testGetRulesWithoutPaymentMethods() + { + $rulesWithoutPaymentMethods = $this->repository->getRulesWithoutPaymentMethods(); + $enabledRulesWithoutPaymentMethods = $this->repository->getRulesWithoutPaymentMethods(true); + + static::assertCount(4, $rulesWithoutPaymentMethods); + static::assertCount(3, $enabledRulesWithoutPaymentMethods); + } + + public function testDisableRulesWithoutPaymentMethods() + { + $this->repository->disableRulesWithoutPaymentMethods(); + + $rulesWithoutPaymentMethods = $this->repository->getRulesWithoutPaymentMethods(); + $enabledRulesWithoutPaymentMethods = $this->repository->getRulesWithoutPaymentMethods(true); + + static::assertCount(4, $rulesWithoutPaymentMethods); + static::assertCount(0, $enabledRulesWithoutPaymentMethods); + } + + public function testGetRulesByMethod() + { + $rulesByExistingMethod = $this->repository->getRulesByMethod($this->getPaymentTermIdentifier()); + + $expectedRuleReferences = [ + 'payment_rule.1', + 'payment_rule.2', + 'payment_rule.3', + 'payment_rule.4', + 'payment_rule.5', + 'payment_rule.6', + 'payment_rule.7', + 'payment_rule.9', + 'payment_rule_without_type_configs', + 'payment_rule_with_disabled_type_configs', + ]; + foreach ($expectedRuleReferences as $expectedRuleReference) { + static::assertContains($this->getReference($expectedRuleReference), $rulesByExistingMethod); + } + + $rulesByNotExistingMethod = $this->repository->getRulesByMethod('not_existing_method'); + static::assertCount(0, $rulesByNotExistingMethod); + } + + /** + * @dataProvider getEnabledRulesByMethodDataProvider + * + * @param string[] $expectedRuleReferences + */ + public function testGetEnabledRulesByMethod(array $expectedRuleReferences) + { + $actualRules = $this->repository->getEnabledRulesByMethod($this->getPaymentTermIdentifier()); + + foreach ($expectedRuleReferences as $expectedRuleReference) { + static::assertContains($this->getReference($expectedRuleReference), $actualRules); + } + } + + /** + * @return array + */ + public function getEnabledRulesByMethodDataProvider() + { + return [ + [ + 'expectedRuleReferences' => [ + 'payment_rule.1', + 'payment_rule.2', + 'payment_rule.4', + 'payment_rule.5', + 'payment_rule.6', + 'payment_rule.7', + 'payment_rule.9', + 'payment_rule_without_type_configs', + 'payment_rule_with_disabled_type_configs', + ] + ] + ]; + } + + /** + * @param array $rules + * + * @return array + */ + protected function getEntitiesByReferences(array $rules) + { + return array_map(function ($ruleReference) { + return $this->getReference($ruleReference); + }, $rules); + } + + /** + * @param array $data + * + * @return AddressInterface|object + */ + protected function createShippingAddress(array $data) + { + return $this->getEntity(ShippingAddressStub::class, [ + 'country' => new Country($data['country']), + 'region' => $this->getEntity( + Region::class, + [ + 'code' => $data['region']['code'], + ], + [ + 'combinedCode' => $data['region']['combinedCode'], + ] + ), + 'postalCode' => $data['postalCode'], + ]); + } + + public function testGetByCurrency() + { + $expectedRules = $this->getEntitiesByReferences([ + 'payment_rule.10', + 'payment_rule.11', + 'payment_rule.12' + ]); + + $this->assertEquals( + $this->getEntitiesIds($expectedRules), + $this->getEntitiesIds($this->repository->getByCurrency('UAH')) + ); + } + + public function testGetByCurrencyWhenCurrencyNotExists() + { + $this->assertEmpty($this->repository->getByCurrency('WON')); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Functional/Helper/PaymentTermIntegrationTrait.php b/src/Marello/Bundle/PaymentBundle/Tests/Functional/Helper/PaymentTermIntegrationTrait.php new file mode 100644 index 000000000..56986a17c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Functional/Helper/PaymentTermIntegrationTrait.php @@ -0,0 +1,34 @@ +getChannelReference(); + + return sprintf('payment_term_%s', $channel->getId()); + } + + /** + * @return Channel + */ + protected function getChannelReference() + { + return $this->getReference(LoadPaymentTermIntegration::REFERENCE_PAYMENT_TERM); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Checker/PaymentMethodEnabledByIdentifierCheckerTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Checker/PaymentMethodEnabledByIdentifierCheckerTest.php new file mode 100644 index 000000000..15b062faf --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Checker/PaymentMethodEnabledByIdentifierCheckerTest.php @@ -0,0 +1,85 @@ +method = $this->createMock(PaymentMethodInterface::class); + + $this->shippingMethodProvider = $this->createMock(PaymentMethodProviderInterface::class); + + $this->shippingMethodEnabledByIdentifierChecker = new PaymentMethodEnabledByIdentifierChecker( + $this->shippingMethodProvider + ); + } + + public function testIsEnabledForEnabledMethod() + { + $identifier = 'shipping_method_1'; + + $this->method + ->expects(static::once()) + ->method('isEnabled') + ->willReturn(true); + + $this->shippingMethodProvider + ->expects(static::any()) + ->method('getPaymentMethod') + ->with($identifier) + ->willReturn($this->method); + + $this->assertTrue($this->shippingMethodEnabledByIdentifierChecker->isEnabled($identifier)); + } + + public function testIsEnabledForDisabledMethod() + { + $identifier = 'shipping_method_1'; + + $this->method + ->expects(static::once()) + ->method('isEnabled') + ->willReturn(false); + + $this->shippingMethodProvider + ->expects(static::any()) + ->method('getPaymentMethod') + ->with($identifier) + ->willReturn($this->method); + + $this->assertFalse($this->shippingMethodEnabledByIdentifierChecker->isEnabled($identifier)); + } + + public function testIsEnabledForNotExistingMethod() + { + $identifier = 'shipping_method_1'; + + $this->shippingMethodProvider + ->expects(static::any()) + ->method('getPaymentMethod') + ->with($identifier) + ->willReturn(null); + + $this->assertFalse($this->shippingMethodEnabledByIdentifierChecker->isEnabled($identifier)); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Checker/PaymentRuleEnabledCheckerTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Checker/PaymentRuleEnabledCheckerTest.php new file mode 100644 index 000000000..b569312f4 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Checker/PaymentRuleEnabledCheckerTest.php @@ -0,0 +1,63 @@ +methodEnabledChecker = $this->createMock( + PaymentMethodEnabledByIdentifierCheckerInterface::class + ); + + $this->ruleChecker = new PaymentRuleEnabledChecker($this->methodEnabledChecker); + } + + public function testCanBeEnabledForOneEnabledMethod() + { + $this->methodEnabledChecker->expects(static::at(1)) + ->method('isEnabled') + ->willReturn(true); + + $rule = $this->getRuleMock(); + + static::assertTrue($this->ruleChecker->canBeEnabled($rule)); + } + + public function testCanBeEnabledForNoEnabledMethods() + { + $rule = $this->getRuleMock(); + + static::assertFalse($this->ruleChecker->canBeEnabled($rule)); + } + + /** + * @return PaymentMethodsConfigsRule|\PHPUnit\Framework\MockObject\MockObject + */ + private function getRuleMock() + { + $rule = $this->createMock(PaymentMethodsConfigsRule::class); + $rule->expects(static::any()) + ->method('getMethodConfigs') + ->willReturn([ + new PaymentMethodConfig(), new PaymentMethodConfig() + ]); + + return $rule; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Condition/HasApplicablePaymentMethodsTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Condition/HasApplicablePaymentMethodsTest.php new file mode 100644 index 000000000..9baec11c3 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Condition/HasApplicablePaymentMethodsTest.php @@ -0,0 +1,144 @@ +paymentMethodProvider = $this->createMock(PaymentMethodProviderInterface::class); + + $this->paymentMethodsViewsProvider = $this + ->getMockBuilder(PaymentMethodsViewsProviderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->condition = new HasApplicablePaymentMethods( + $this->paymentMethodProvider, + $this->paymentMethodsViewsProvider + ); + } + + protected function tearDown() + { + unset($this->condition, $this->paymentMethodProvider); + } + + public function testGetName() + { + $this->assertEquals(HasApplicablePaymentMethods::NAME, $this->condition->getName()); + } + + /** + * @expectedException \Oro\Component\ConfigExpression\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "paymentContext" option + */ + public function testInitializeInvalid() + { + $this->assertInstanceOf( + 'Oro\Component\ConfigExpression\Condition\AbstractCondition', + $this->condition->initialize([]) + ); + } + + public function testInitialize() + { + $this->assertInstanceOf( + 'Oro\Component\ConfigExpression\Condition\AbstractCondition', + $this->condition->initialize([self::METHOD, new \stdClass()]) + ); + } + + /** + * @dataProvider evaluateProvider + * @param array $methods + * @param bool $expected + */ + public function testEvaluate($methods, $expected) + { + $method = $this->createMock('Marello\Bundle\PaymentBundle\Method\PaymentMethodInterface'); + $this->paymentMethodProvider->expects($this->any())->method('getPaymentMethod')->willReturn($method); + + $this->paymentMethodsViewsProvider->expects($this->once()) + ->method('getApplicableMethodsViews') + ->willReturn($methods); + + $this->condition->initialize(['paymentContext' => new PaymentContext([])]); + $this->assertEquals($expected, $this->condition->evaluate([])); + } + + /** + * @return array + */ + public function evaluateProvider() + { + return [ + 'no_rules_no_methods' => [ + 'methods' => [], + 'expected' => false, + ], + 'with_rules_no_methods' => [ + 'methods' => [], + 'expected' => false, + ], + 'with_rules_and_methods' => [ + 'methods' => ['flat_rate'], + 'expected' => true, + ], + ]; + } + + public function testToArray() + { + $stdClass = new \stdClass(); + $this->condition->initialize(['paymentContext' => $stdClass]); + $result = $this->condition->toArray(); + + $key = '@' . HasApplicablePaymentMethods::NAME; + + $this->assertInternalType('array', $result); + $this->assertArrayHasKey($key, $result); + + $resultSection = $result[$key]; + $this->assertInternalType('array', $resultSection); + $this->assertArrayHasKey('parameters', $resultSection); + $this->assertContains($stdClass, $resultSection['parameters']); + } + + public function testCompile() + { + $toStringStub = new ToStringStub(); + $options = ['paymentContext' => $toStringStub]; + + $this->condition->initialize($options); + $result = $this->condition->compile('$factory'); + $this->assertEquals( + sprintf( + '$factory->create(\'%s\', [%s])', + HasApplicablePaymentMethods::NAME, + $toStringStub + ), + $result + ); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Condition/PaymentMethodHasPaymentRulesTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Condition/PaymentMethodHasPaymentRulesTest.php new file mode 100644 index 000000000..6603f4031 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Condition/PaymentMethodHasPaymentRulesTest.php @@ -0,0 +1,148 @@ +repository = $this->getMockBuilder(PaymentMethodsConfigsRuleRepository::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->propertyPath = $this->createMock(PropertyPathInterface::class); + $this->propertyPath->expects($this->any()) + ->method('__toString') + ->will($this->returnValue(self::PROPERTY_PATH_NAME)); + $this->propertyPath->expects($this->any()) + ->method('getElements') + ->will($this->returnValue([self::PROPERTY_PATH_NAME])); + + $this->paymentMethodHasPaymentRulesCondition = new PaymentMethodHasPaymentRules($this->repository); + } + + public function testGetName() + { + $this->assertEquals( + PaymentMethodHasPaymentRules::NAME, + $this->paymentMethodHasPaymentRulesCondition->getName() + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Missing "method_identifier" option + */ + public function testInitializeInvalid() + { + $this->assertInstanceOf( + PaymentMethodHasPaymentRules::class, + $this->paymentMethodHasPaymentRulesCondition->initialize([]) + ); + } + + public function testInitialize() + { + $this->assertInstanceOf( + PaymentMethodHasPaymentRules::class, + $this->paymentMethodHasPaymentRulesCondition->initialize(['method_identifier']) + ); + } + + /** + * @dataProvider evaluateProvider + * + * @param PaymentMethodsConfigsRule[] $rules + * @param bool $expected + */ + public function testEvaluate($rules, $expected) + { + $this->repository->expects(static::once()) + ->method('getRulesByMethod') + ->willReturn($rules); + + $this->paymentMethodHasPaymentRulesCondition->initialize(['method_identifier']); + $this->assertEquals($expected, $this->paymentMethodHasPaymentRulesCondition->evaluate([])); + } + + /** + * @return array + */ + public function evaluateProvider() + { + return [ + 'no_rules' => [ + 'rules' => [], + 'expected' => false, + ], + 'with_rules' => [ + 'rules' => [ + new PaymentMethodsConfigsRule(), + new PaymentMethodsConfigsRule(), + ], + 'expected' => true, + ], + ]; + } + + public function testToArray() + { + $result = $this->paymentMethodHasPaymentRulesCondition->initialize([$this->propertyPath])->toArray(); + + $this->assertEquals( + sprintf('$%s', self::PROPERTY_PATH_NAME), + $result['@marello_payment_method_has_payment_rules']['parameters'][0] + ); + } + + public function testCompile() + { + $result = $this->paymentMethodHasPaymentRulesCondition->compile('$factoryAccessor'); + + $this->assertContains('$factoryAccessor->create(\'marello_payment_method_has_payment_rules\'', $result); + } + + public function testSetContextAccessor() + { + /** @var ContextAccessorInterface|\PHPUnit\Framework\MockObject\MockObject $contextAccessor * */ + $contextAccessor = $this->getMockBuilder(ContextAccessorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->paymentMethodHasPaymentRulesCondition->setContextAccessor($contextAccessor); + + $reflection = new \ReflectionProperty( + get_class($this->paymentMethodHasPaymentRulesCondition), + 'contextAccessor' + ); + $reflection->setAccessible(true); + + $this->assertInstanceOf( + get_class($contextAccessor), + $reflection->getValue($this->paymentMethodHasPaymentRulesCondition) + ); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Condition/ToStringStub.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Condition/ToStringStub.php new file mode 100644 index 000000000..d680a605b --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Condition/ToStringStub.php @@ -0,0 +1,12 @@ +priceMock = $this->createMock(Price::class); + + $this->productHolderMock = $this->createMock(OrderItem::class); + + $this->productHolderMock->method('getId')->willReturn(static::TEST_ENTITY_ID); + + $this->productMock = $this->createMock(Product::class); + + $this->productMock->method('getSku')->willReturn(static::TEST_PRODUCT_SKU); + $this->productMock->method('getId')->willReturn(static::TEST_PRODUCT_ID); + } + + /** + * @return array + */ + protected function getPaymentLineItemParams() + { + return [ + PaymentLineItem::FIELD_PRICE => $this->priceMock, + PaymentLineItem::FIELD_QUANTITY => self::TEST_QUANTITY, + PaymentLineItem::FIELD_PRODUCT_HOLDER => $this->productHolderMock, + PaymentLineItem::FIELD_PRODUCT => $this->productMock, + PaymentLineItem::FIELD_PRODUCT_SKU => self::TEST_PRODUCT_SKU, + PaymentLineItem::FIELD_WEIGHT => self::TEST_WEIGHT, + PaymentLineItem::FIELD_ENTITY_IDENTIFIER => self::TEST_ENTITY_ID, + ]; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/Builder/Basic/BasicPaymentContextBuilderTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/Builder/Basic/BasicPaymentContextBuilderTest.php new file mode 100644 index 000000000..dfaed1b70 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/Builder/Basic/BasicPaymentContextBuilderTest.php @@ -0,0 +1,208 @@ +customerMock = $this->createMock(Customer::class); + $this->lineItemsCollectionMock = $this->createMock(PaymentLineItemCollectionInterface::class); + $this->lineItemsCollectionMock + ->expects(static::any()) + ->method('toArray') + ->willReturn([$this->createMock(PaymentLineItemInterface::class)]); + $this->billingAddressMock = $this->createMock(AddressInterface::class); + $this->shippingAddressMock = $this->createMock(AddressInterface::class); + $this->shippingOriginMock = $this->createMock(AddressInterface::class); + $this->subtotalMock = $this->createMock(Price::class); + $this->sourceEntityMock = $this->createMock(Order::class); + $this->shippingOriginMock = $this->createMock(AddressInterface::class); + $this->paymentLineItemCollectionFactory = $this->createMock(PaymentLineItemCollectionFactoryInterface::class); + $this->paymentLineItemCollectionFactory + ->expects(static::any()) + ->method('createPaymentLineItemCollection') + ->willReturn($this->lineItemsCollectionMock); + } + + public function testFullContextBuilding() + { + $paymentMethod = 'paymentMethod'; + $currency = 'usd'; + $entityId = '12'; + + $builder = new BasicPaymentContextBuilder( + $this->sourceEntityMock, + $entityId, + $this->paymentLineItemCollectionFactory + ); + + $builder + ->setCurrency($currency) + ->setSubTotal($this->subtotalMock) + ->setLineItems($this->lineItemsCollectionMock) + ->setShippingAddress($this->shippingAddressMock) + ->setBillingAddress($this->billingAddressMock) + ->setCustomer($this->customerMock) + ->setPaymentMethod($paymentMethod) + ->setShippingOrigin($this->shippingOriginMock); + + $expectedContext = $this->getExpectedFullContext( + $paymentMethod, + $currency, + $entityId, + $this->shippingOriginMock + ); + $context = $builder->getResult(); + + $this->assertEquals($expectedContext, $context); + } + + public function testOptionalFields() + { + $entityId = '12'; + + $builder = new BasicPaymentContextBuilder( + $this->sourceEntityMock, + $entityId, + $this->paymentLineItemCollectionFactory + ); + $builder->setShippingOrigin($this->shippingOriginMock); + + $expectedContext = $this->getExpectedContextWithoutOptionalFields( + $entityId, + $this->shippingOriginMock + ); + + $context = $builder->getResult(); + + $this->assertEquals($expectedContext, $context); + } + + public function testWithoutOrigin() + { + $paymentMethod = 'paymentMethod'; + $currency = 'usd'; + $entityId = '12'; + + $builder = new BasicPaymentContextBuilder( + $this->sourceEntityMock, + $entityId, + $this->paymentLineItemCollectionFactory + ); + + $builder + ->setCurrency($currency) + ->setSubTotal($this->subtotalMock) + ->setLineItems($this->lineItemsCollectionMock) + ->setShippingAddress($this->shippingAddressMock) + ->setBillingAddress($this->billingAddressMock) + ->setCustomer($this->customerMock) + ->setPaymentMethod($paymentMethod); + + $expectedContext = $this->getExpectedFullContext( + $paymentMethod, + $currency, + $entityId, + null + ); + $context = $builder->getResult(); + + $this->assertEquals($expectedContext, $context); + } + + /** + * @param string $paymentMethod + * @param string $currency + * @param int $entityId + * @param AddressInterface|null $shippingOrigin + * + * @return PaymentContext + */ + private function getExpectedFullContext($paymentMethod, $currency, $entityId, AddressInterface $shippingOrigin = null) + { + $params = [ + PaymentContext::FIELD_CUSTOMER => $this->customerMock, + PaymentContext::FIELD_LINE_ITEMS => $this->lineItemsCollectionMock, + PaymentContext::FIELD_BILLING_ADDRESS => $this->billingAddressMock, + PaymentContext::FIELD_SHIPPING_ADDRESS => $this->shippingAddressMock, + PaymentContext::FIELD_SHIPPING_ORIGIN => $shippingOrigin, + PaymentContext::FIELD_PAYMENT_METHOD => $paymentMethod, + PaymentContext::FIELD_CURRENCY => $currency, + PaymentContext::FIELD_SUBTOTAL => $this->subtotalMock, + PaymentContext::FIELD_SOURCE_ENTITY => $this->sourceEntityMock, + PaymentContext::FIELD_SOURCE_ENTITY_ID => $entityId, + ]; + $params = array_diff_key($params, array_filter($params, 'is_null')); + + return new PaymentContext($params); + } + + /** + * @param int $entityId + * @param AddressInterface $shippingOrigin + * + * @return PaymentContext + */ + private function getExpectedContextWithoutOptionalFields($entityId, AddressInterface $shippingOrigin) + { + $params = [ + PaymentContext::FIELD_LINE_ITEMS => $this->lineItemsCollectionMock, + PaymentContext::FIELD_SHIPPING_ORIGIN => $shippingOrigin, + PaymentContext::FIELD_SOURCE_ENTITY => $this->sourceEntityMock, + PaymentContext::FIELD_SOURCE_ENTITY_ID => $entityId, + ]; + $params = array_diff_key($params, array_filter($params, 'is_null')); + + return new PaymentContext($params); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/Builder/Basic/Factory/BasicPaymentContextBuilderFactoryTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/Builder/Basic/Factory/BasicPaymentContextBuilderFactoryTest.php new file mode 100644 index 000000000..c5d56eb5f --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/Builder/Basic/Factory/BasicPaymentContextBuilderFactoryTest.php @@ -0,0 +1,46 @@ +lineItemsCollectionFactoryMock = $this->createMock(PaymentLineItemCollectionFactoryInterface::class); + $this->sourceEntityMock = $this->createMock(Order::class); + } + + public function testCreateBuilder() + { + $entityId = '12'; + + $builderFactory = new BasicPaymentContextBuilderFactory($this->lineItemsCollectionFactoryMock); + + $builder = $builderFactory->createPaymentContextBuilder( + $this->sourceEntityMock, + $entityId + ); + + $expectedBuilder = new BasicPaymentContextBuilder( + $this->sourceEntityMock, + $entityId, + $this->lineItemsCollectionFactoryMock + ); + + $this->assertEquals($expectedBuilder, $builder); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Builder/Basic/BasicPaymentLineItemBuilderTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Builder/Basic/BasicPaymentLineItemBuilderTest.php new file mode 100644 index 000000000..74ba06c27 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Builder/Basic/BasicPaymentLineItemBuilderTest.php @@ -0,0 +1,48 @@ +productHolderMock + ); + + $builder + ->setProduct($this->productMock) + ->setPrice($this->priceMock) + ->setProductSku(self::TEST_PRODUCT_SKU) + ->setWeight(self::TEST_WEIGHT); + + $shippingLineItem = $builder->getResult(); + + $expectedPaymentLineItem = new PaymentLineItem($this->getPaymentLineItemParams()); + + $this->assertEquals($expectedPaymentLineItem, $shippingLineItem); + } + + public function testOptionalBuild() + { + $builder = new BasicPaymentLineItemBuilder( + self::TEST_QUANTITY, + $this->productHolderMock + ); + + $shippingLineItem = $builder->getResult(); + + $expectedPaymentLineItem = new PaymentLineItem([ + PaymentLineItem::FIELD_QUANTITY => self::TEST_QUANTITY, + PaymentLineItem::FIELD_PRODUCT_HOLDER => $this->productHolderMock, + PaymentLineItem::FIELD_ENTITY_IDENTIFIER => self::TEST_ENTITY_ID + ]); + + $this->assertEquals($expectedPaymentLineItem, $shippingLineItem); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Builder/Basic/Factory/BasicPaymentLineItemBuilderFactoryTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Builder/Basic/Factory/BasicPaymentLineItemBuilderFactoryTest.php new file mode 100644 index 000000000..60ab2d9dc --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Builder/Basic/Factory/BasicPaymentLineItemBuilderFactoryTest.php @@ -0,0 +1,39 @@ +productHolderMock = $this->createMock(OrderItem::class); + } + + public function testCreate() + { + $quantity = 15; + + $builderFactory = new BasicPaymentLineItemBuilderFactory(); + + $builder = $builderFactory->createBuilder( + $quantity, + $this->productHolderMock + ); + + $expectedBuilder = new BasicPaymentLineItemBuilder( + $quantity, + $this->productHolderMock + ); + + $this->assertEquals($expectedBuilder, $builder); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrinePaymentLineItemCollectionFactoryTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrinePaymentLineItemCollectionFactoryTest.php new file mode 100644 index 000000000..b1e075300 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrinePaymentLineItemCollectionFactoryTest.php @@ -0,0 +1,42 @@ +createPaymentLineItemCollection($paymentLineItems); + + $this->assertEquals($paymentLineItems, $collection->toArray()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Expected: Marello\Bundle\PaymentBundle\Context\PaymentLineItemInterface + */ + public function testFactoryWithException() + { + $paymentLineItems = [ + new OrderItem(), + new OrderItem(), + new OrderItem(), + new OrderItem(), + ]; + + $collectionFactory = new DoctrinePaymentLineItemCollectionFactory(); + $collectionFactory->createPaymentLineItemCollection($paymentLineItems); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrinePaymentLineItemCollectionTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrinePaymentLineItemCollectionTest.php new file mode 100644 index 000000000..9366966fa --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrinePaymentLineItemCollectionTest.php @@ -0,0 +1,23 @@ +assertEquals($paymentLineItems, $collection->toArray()); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/PaymentContextMockTrait.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/PaymentContextMockTrait.php new file mode 100644 index 000000000..0fabb40c3 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/PaymentContextMockTrait.php @@ -0,0 +1,19 @@ +createMock(PaymentContextInterface::class); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/PaymentContextTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/PaymentContextTest.php new file mode 100644 index 000000000..ae09e8f85 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/PaymentContextTest.php @@ -0,0 +1,102 @@ +customerMock = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $this->lineItemsCollectionMock = $this->createMock(PaymentLineItemCollectionInterface::class); + $this->billingAddressMock = $this->createMock(AddressInterface::class); + $this->shippingAddressMock = $this->createMock(AddressInterface::class); + $this->shippingOriginMock = $this->createMock(AddressInterface::class); + $this->subtotalMock = $this->getMockBuilder(Price::class) + ->disableOriginalConstructor() + ->getMock(); + $this->sourceEntityMock = $this->getMockBuilder(Order::class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testConstructionAndGetters() + { + $paymentMethod = 'paymentMethod'; + $currency = 'usd'; + $entityId = '12'; + + $params = [ + PaymentContext::FIELD_CUSTOMER => $this->customerMock, + PaymentContext::FIELD_LINE_ITEMS => $this->lineItemsCollectionMock, + PaymentContext::FIELD_BILLING_ADDRESS => $this->billingAddressMock, + PaymentContext::FIELD_SHIPPING_ADDRESS => $this->shippingAddressMock, + PaymentContext::FIELD_SHIPPING_ORIGIN => $this->shippingOriginMock, + PaymentContext::FIELD_PAYMENT_METHOD => $paymentMethod, + PaymentContext::FIELD_CURRENCY => $currency, + PaymentContext::FIELD_SUBTOTAL => $this->subtotalMock, + PaymentContext::FIELD_SOURCE_ENTITY => $this->sourceEntityMock, + PaymentContext::FIELD_SOURCE_ENTITY_ID => $entityId, + ]; + + $shippingContext = new PaymentContext($params); + + $getterValues = [ + PaymentContext::FIELD_CUSTOMER => $shippingContext->getCustomer(), + PaymentContext::FIELD_LINE_ITEMS => $shippingContext->getLineItems(), + PaymentContext::FIELD_BILLING_ADDRESS => $shippingContext->getBillingAddress(), + PaymentContext::FIELD_SHIPPING_ADDRESS => $shippingContext->getShippingAddress(), + PaymentContext::FIELD_SHIPPING_ORIGIN => $shippingContext->getShippingOrigin(), + PaymentContext::FIELD_PAYMENT_METHOD => $shippingContext->getPaymentMethod(), + PaymentContext::FIELD_CURRENCY => $shippingContext->getCurrency(), + PaymentContext::FIELD_SUBTOTAL => $shippingContext->getSubtotal(), + PaymentContext::FIELD_SOURCE_ENTITY => $shippingContext->getSourceEntity(), + PaymentContext::FIELD_SOURCE_ENTITY_ID => $shippingContext->getSourceEntityIdentifier(), + ]; + + $this->assertEquals($params, $getterValues); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/PaymentLineItemTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/PaymentLineItemTest.php new file mode 100644 index 000000000..aa841e6f1 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Context/PaymentLineItemTest.php @@ -0,0 +1,35 @@ +getPaymentLineItemParams(); + + $shippingLineItem = new PaymentLineItem($shippingLineItemParams); + + $this->assertEquals($shippingLineItemParams[PaymentLineItem::FIELD_PRICE], $shippingLineItem->getPrice()); + $this->assertEquals( + $shippingLineItemParams[PaymentLineItem::FIELD_QUANTITY], + $shippingLineItem->getQuantity() + ); + $this->assertEquals( + $shippingLineItemParams[PaymentLineItem::FIELD_PRODUCT_HOLDER], + $shippingLineItem->getProductHolder() + ); + $this->assertEquals($shippingLineItemParams[PaymentLineItem::FIELD_PRODUCT], $shippingLineItem->getProduct()); + $this->assertEquals( + $shippingLineItemParams[PaymentLineItem::FIELD_PRODUCT_SKU], + $shippingLineItem->getProductSku() + ); + $this->assertEquals($shippingLineItemParams[PaymentLineItem::FIELD_WEIGHT], $shippingLineItem->getWeight()); + $this->assertEquals( + $shippingLineItemParams[PaymentLineItem::FIELD_ENTITY_IDENTIFIER], + $shippingLineItem->getEntityIdentifier() + ); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Converter/Basic/PaymentContextToRuleValuesConverterTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Converter/Basic/PaymentContextToRuleValuesConverterTest.php new file mode 100644 index 000000000..e6b7b3ebf --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Converter/Basic/PaymentContextToRuleValuesConverterTest.php @@ -0,0 +1,107 @@ +factory = new DecoratedProductLineItemFactory( + $this->createMock(VirtualFieldsProductDecoratorFactory::class) + ); + + $this->shippingContextToRuleValuesConverter = new BasicPaymentContextToRulesValueConverter( + $this->factory + ); + } + + /** + * @dataProvider convertDataProvider + * @param PaymentContext $context + */ + public function testConvert(PaymentContext $context) + { + $expectedValues = [ + 'lineItems' => array_map(function (PaymentLineItem $lineItem) use ($context) { + return $this->factory + ->createLineItemWithDecoratedProductByLineItem($context->getLineItems()->toArray(), $lineItem); + }, $context->getLineItems()->toArray()), + 'shippingOrigin' => $context->getShippingOrigin(), + 'billingAddress' => $context->getBillingAddress(), + 'shippingAddress' => $context->getShippingAddress(), + 'paymentMethod' => $context->getPaymentMethod(), + 'currency' => $context->getCurrency(), + 'subtotal' => $context->getSubtotal(), + 'customer' => $context->getCustomer(), + 'company' => $context->getCompany(), + 'total' => $context->getTotal() + ]; + $this->assertEquals($expectedValues, $this->shippingContextToRuleValuesConverter->convert($context)); + } + + /** + * @return array + */ + public function convertDataProvider() + { + return [ + [ + 'context' => new PaymentContext([ + PaymentContext::FIELD_LINE_ITEMS => new DoctrinePaymentLineItemCollection([ + new PaymentLineItem([ + PaymentLineItem::FIELD_PRODUCT => $this->getEntity(Product::class, ['id' => 1]), + ]), + ]), + PaymentContext::FIELD_SHIPPING_ORIGIN => $this->getEntity(ShippingAddressStub::class, [ + 'region' => $this->getEntity(Region::class, [ + 'code' => 'CA', + ], ['US-CA']), + ]), + PaymentContext::FIELD_BILLING_ADDRESS => $this->getEntity(ShippingAddressStub::class, [ + 'country' => new Country('US'), + ]), + PaymentContext::FIELD_SHIPPING_ADDRESS => $this->getEntity(ShippingAddressStub::class, [ + 'country' => new Country('US'), + 'region' => $this->getEntity(Region::class, [ + 'code' => 'CA', + ], ['US-CA']), + 'postalCode' => '90401', + ]), + PaymentContext::FIELD_PAYMENT_METHOD => 'integration_payment_method', + PaymentContext::FIELD_CURRENCY => 'USD', + PaymentContext::FIELD_SUBTOTAL => Price::create(10.0, 'USD'), + PaymentContext::FIELD_CUSTOMER => (new Customer())->setFirstName('Customer Name'), + ]), + ], + ]; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodConfigTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodConfigTest.php new file mode 100644 index 000000000..0b92579ea --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodConfigTest.php @@ -0,0 +1,26 @@ + 'test']], + ['methodsConfigsRule', new PaymentMethodsConfigsRule()], + ]; + + $entity = new PaymentMethodConfig(); + + $this->assertPropertyAccessors($entity, $properties); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodsConfigsRuleDestinationPostalCodeTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodsConfigsRuleDestinationPostalCodeTest.php new file mode 100644 index 000000000..49771e1ce --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodsConfigsRuleDestinationPostalCodeTest.php @@ -0,0 +1,26 @@ +country = $this->createMockCountry(); + $this->region = $this->createMockRegion(); + + $this->shippingRuleDestination = $this->getEntity( + 'Marello\Bundle\PaymentBundle\Entity\PaymentMethodsConfigsRuleDestination', + [ + 'region' => $this->region, + 'country' => $this->country, + 'postalCodes' => new ArrayCollection([$this->createPostalCode('123')]), + ] + ); + } + + public function testProperties() + { + $properties = [ + ['id', 1], + ['region', new Region('code')], + ['regionText', 'text'], + ['country', new Country('UA')], + ['methodsConfigsRule', new PaymentMethodsConfigsRule()], + ]; + + $destination = new PaymentMethodsConfigsRuleDestination(); + static::assertPropertyAccessors($destination, $properties); + static::assertPropertyCollection( + $destination, + 'postalCodes', + $this->createPostalCode('123') + ); + } + + public function testGetRegionName() + { + $this->assertEquals('RegionName', $this->shippingRuleDestination->getRegionName()); + $this->shippingRuleDestination->setRegion(null); + $this->assertEquals('', $this->shippingRuleDestination->getRegionName()); + } + + public function testGetRegionCode() + { + $this->assertEquals('RegionCode', $this->shippingRuleDestination->getRegionCode()); + $this->shippingRuleDestination->setRegion(null); + $this->assertEquals('', $this->shippingRuleDestination->getRegionCode()); + } + + public function testGetCountryName() + { + $this->assertEquals('CountryName', $this->shippingRuleDestination->getCountryName()); + $this->shippingRuleDestination->setCountry(null); + $this->assertEquals('', $this->shippingRuleDestination->getCountryName()); + } + + public function testGetCountryIso2() + { + $this->assertEquals('CountryIso2', $this->shippingRuleDestination->getCountryIso2()); + $this->shippingRuleDestination->setCountry(null); + $this->assertEquals('', $this->shippingRuleDestination->getCountryIso2()); + } + + public function testGetCountryIso3() + { + $this->assertEquals('CountryIso3', $this->shippingRuleDestination->getCountryIso3()); + $this->shippingRuleDestination->setCountry(null); + $this->assertEquals('', $this->shippingRuleDestination->getCountryIso3()); + } + + /** + * @dataProvider toStringDataProvider + * + * @param array $data + * @param string $expectedString + */ + public function testToString(array $data, $expectedString) + { + $entity = (string) $this->getEntity( + 'Marello\Bundle\PaymentBundle\Entity\PaymentMethodsConfigsRuleDestination', + $data + ); + $this->assertEquals($expectedString, $entity); + } + + /** + * @return array + */ + public function toStringDataProvider() + { + return [ + 'all' => [ + 'data' => [ + 'country' => $this->createMockCountry(), + 'region' => $this->createMockRegion(), + 'postalCodes' => new ArrayCollection([$this->createPostalCode('12345')]), + ], + 'expectedString' => 'RegionName, CountryName 12345' + ], + 'country and postal code' => [ + 'data' => [ + 'country' => $this->createMockCountry(), + 'region' => null, + 'postalCodes' => new ArrayCollection([ + $this->createPostalCode('12345'), + $this->createPostalCode('54321'), + ]), + ], + 'expectedString' => 'CountryName 12345, 54321' + ], + 'country and region' => [ + 'data' => [ + 'country' => $this->createMockCountry('SecondCountryName'), + 'region' => $this->createMockRegion('SecondRegionName'), + 'postalCodes' => new ArrayCollection(), + ], + 'expectedString' => 'SecondRegionName, SecondCountryName' + ], + 'only country' => [ + 'data' => [ + 'country' => $this->createMockCountry(), + 'region' => null, + 'postalCodes' => new ArrayCollection(), + ], + 'expectedString' => 'CountryName' + ] + ]; + } + + /** + * @param string $name + * @param string $iso2 + * @param string $iso3 + * @return \PHPUnit\Framework\MockObject\MockObject + */ + protected function createMockCountry($name = 'CountryName', $iso2 = 'CountryIso2', $iso3 = 'CountryIso3') + { + $result = $this->getMockBuilder('Oro\Bundle\AddressBundle\Entity\Country') + ->disableOriginalConstructor() + ->getMock(); + $result->expects($this->any()) + ->method('__toString') + ->will($this->returnValue($name)); + $result->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)); + $result->expects($this->any()) + ->method('getIso2Code') + ->will($this->returnValue($iso2)); + $result->expects($this->any()) + ->method('getIso3Code') + ->will($this->returnValue($iso3)); + + return $result; + } + + /** + * @param string $name + * @param string $code + * @return \PHPUnit\Framework\MockObject\MockObject + */ + protected function createMockRegion($name = 'RegionName', $code = 'RegionCode') + { + $result = $this->createMock('Oro\Bundle\AddressBundle\Entity\Region', [], ['combinedCode']); + $result->expects($this->any()) + ->method('__toString') + ->will($this->returnValue($name)); + $result->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)); + $result->expects($this->any()) + ->method('getCode') + ->will($this->returnValue($code)); + return $result; + } + + /** + * @param string $name + * @return PaymentMethodsConfigsRuleDestinationPostalCode + */ + protected function createPostalCode($name) + { + return (new PaymentMethodsConfigsRuleDestinationPostalCode())->setName($name); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodsConfigsRuleMockTrait.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodsConfigsRuleMockTrait.php new file mode 100644 index 000000000..0f4d3552a --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodsConfigsRuleMockTrait.php @@ -0,0 +1,19 @@ +createMock(PaymentMethodsConfigsRule::class); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodsConfigsRuleTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodsConfigsRuleTest.php new file mode 100644 index 000000000..01aaa8dc1 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Entity/PaymentMethodsConfigsRuleTest.php @@ -0,0 +1,32 @@ +paymentMethodProvider = new CompositePaymentMethodProvider(); + + $this->provider = $this->getMockBuilder(PaymentMethodProviderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testGetMethods() + { + $paymentMethods = $this->paymentMethodProvider->getPaymentMethods(); + $this->assertInternalType('array', $paymentMethods); + $this->assertEmpty($paymentMethods); + } + + public function testRegistry() + { + $method = $this->createMock(PaymentMethodInterface::class); + + $this->provider->expects($this->once()) + ->method('getPaymentMethods') + ->willReturn(['test_name' => $method]); + + $this->provider->expects($this->once()) + ->method('getPaymentMethod') + ->with('test_name') + ->willReturn($method); + + $this->provider->expects($this->once()) + ->method('hasPaymentMethod') + ->with('test_name') + ->willReturn(true); + + $this->paymentMethodProvider->addProvider($this->provider); + $this->assertEquals($method, $this->paymentMethodProvider->getPaymentMethod('test_name')); + $this->assertEquals(['test_name' => $method], $this->paymentMethodProvider->getPaymentMethods()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage There is no payment method for "wrong_name" identifier + */ + public function testRegistryWrongMethod() + { + $this->paymentMethodProvider->getPaymentMethod('wrong_name'); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/BasicMethodRemovalEventDispatcherTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/BasicMethodRemovalEventDispatcherTest.php new file mode 100644 index 000000000..e98c800e8 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/BasicMethodRemovalEventDispatcherTest.php @@ -0,0 +1,37 @@ +eventDispatcher = $this->createMock(EventDispatcherInterface::class); + + $this->dispatcher = new BasicMethodRemovalEventDispatcher($this->eventDispatcher); + } + + public function testDispatch() + { + $methodId = 'method'; + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with(MethodRemovalEvent::NAME, new MethodRemovalEvent($methodId)); + + $this->dispatcher->dispatch($methodId); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/BasicMethodRenamingEventDispatcherTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/BasicMethodRenamingEventDispatcherTest.php new file mode 100644 index 000000000..d6b926e61 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/BasicMethodRenamingEventDispatcherTest.php @@ -0,0 +1,38 @@ +eventDispatcher = $this->createMock(EventDispatcherInterface::class); + + $this->dispatcher = new BasicMethodRenamingEventDispatcher($this->eventDispatcher); + } + + public function testDispatch() + { + $oldId = 'old_id'; + $newId = 'new_id'; + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with(MethodRenamingEvent::NAME, new MethodRenamingEvent($oldId, $newId)); + + $this->dispatcher->dispatch($oldId, $newId); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/MethodRemovalEventTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/MethodRemovalEventTest.php new file mode 100644 index 000000000..135a5c21c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/MethodRemovalEventTest.php @@ -0,0 +1,17 @@ +assertSame($methodId, $event->getMethodIdentifier()); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/MethodRenamingEventTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/MethodRenamingEventTest.php new file mode 100644 index 000000000..ec365e1eb --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Event/MethodRenamingEventTest.php @@ -0,0 +1,19 @@ +assertSame($oldId, $event->getOldMethodIdentifier()); + $this->assertSame($newId, $event->getNewMethodIdentifier()); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/EventListener/IntegrationRemovalListenerTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/EventListener/IntegrationRemovalListenerTest.php new file mode 100644 index 000000000..f03ff07da --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/EventListener/IntegrationRemovalListenerTest.php @@ -0,0 +1,96 @@ +channelType = 'payment_method'; + $this->identifierGenerator = $this->createMock(IntegrationIdentifierGeneratorInterface::class); + $this->dispatcher = $this->createMock(MethodRemovalEventDispatcherInterface::class); + + $this->listener = new IntegrationRemovalListener( + $this->channelType, + $this->identifierGenerator, + $this->dispatcher + ); + } + + public function testPreRemove() + { + /** @var Channel|\PHPUnit\Framework\MockObject\MockObject $channel */ + $channel = $this->createMock(Channel::class); + $channel->expects(static::once()) + ->method('getType') + ->willReturn($this->channelType); + + /** @var ChannelDeleteEvent|\PHPUnit\Framework\MockObject\MockObject $event */ + $event = $this->createMock(ChannelDeleteEvent::class); + $event->expects(static::any()) + ->method('getChannel') + ->willReturn($channel); + + $identifier = 'method'; + + $this->identifierGenerator->expects(static::once()) + ->method('generateIdentifier') + ->with($channel) + ->willReturn($identifier); + + $this->dispatcher->expects(static::once()) + ->method('dispatch') + ->with($identifier); + + $this->listener->onRemove($event); + } + + public function testPreRemoveOtherType() + { + /** @var Channel|\PHPUnit\Framework\MockObject\MockObject $channel */ + $channel = $this->createMock(Channel::class); + $channel->expects(static::once()) + ->method('getType') + ->willReturn('other_type'); + + /** @var ChannelDeleteEvent|\PHPUnit\Framework\MockObject\MockObject $event */ + $event = $this->createMock(ChannelDeleteEvent::class); + $event->expects(static::any()) + ->method('getChannel') + ->willReturn($channel); + + $this->identifierGenerator->expects(static::never()) + ->method('generateIdentifier'); + + $this->dispatcher->expects(static::never()) + ->method('dispatch'); + + $this->listener->onRemove($event); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/EventListener/MethodRenamingListenerTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/EventListener/MethodRenamingListenerTest.php new file mode 100644 index 000000000..2deeb0088 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/EventListener/MethodRenamingListenerTest.php @@ -0,0 +1,59 @@ +paymentMethodConfigRepository = $this->createMock(PaymentMethodConfigRepository::class); + $this->listener = new MethodRenamingListener($this->paymentMethodConfigRepository); + } + + public function testOnMethodRename() + { + $oldId = 'old_name'; + $newId = 'new_name'; + + /** @var MethodRenamingEvent|\PHPUnit\Framework\MockObject\MockObject $event */ + $event = $this->createMock(MethodRenamingEvent::class); + $event->expects(static::any()) + ->method('getOldMethodIdentifier') + ->willReturn($oldId); + + $event->expects(static::any()) + ->method('getNewMethodIdentifier') + ->willReturn($newId); + + $config1 = $this->createMock(PaymentMethodConfig::class); + $config1->expects(static::once()) + ->method('setMethod') + ->with($newId); + $config2 = $this->createMock(PaymentMethodConfig::class); + $config2->expects(static::once()) + ->method('setMethod') + ->with($newId); + + $this->paymentMethodConfigRepository->expects(static::once()) + ->method('findByMethod') + ->with($oldId) + ->willReturn([$config1, $config2]); + + $this->listener->onMethodRename($event); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/EventListener/PaymentMethodDisableIntegrationListenerTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/EventListener/PaymentMethodDisableIntegrationListenerTest.php new file mode 100644 index 000000000..81f42964c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/EventListener/PaymentMethodDisableIntegrationListenerTest.php @@ -0,0 +1,111 @@ +channelType = 'integration_payment_method'; + + $this->methodIdentifierGenerator = $this->createMock( + IntegrationIdentifierGeneratorInterface::class + ); + $this->handler = $this->createMock( + PaymentMethodDisableHandlerInterface::class + ); + $this->event = $this->createMock( + ChannelDisableEvent::class + ); + $this->listener = new PaymentMethodDisableIntegrationListener( + $this->channelType, + $this->methodIdentifierGenerator, + $this->handler + ); + } + + public function testOnIntegrationDisable() + { + $methodIdentifier = 'method_1'; + $channel = $this->createMock(Channel::class); + + $this->event + ->expects(static::once()) + ->method('getChannel') + ->willReturn($channel); + + $channel + ->expects(static::once()) + ->method('getType') + ->willReturn($this->channelType); + + $this->methodIdentifierGenerator + ->expects(static::once()) + ->method('generateIdentifier') + ->with($channel) + ->willReturn($methodIdentifier); + + $this->handler + ->expects(static::once()) + ->method('handleMethodDisable') + ->with($methodIdentifier); + + $this->listener->onIntegrationDisable($this->event); + } + + public function testOnIntegrationDisableWithAnotherType() + { + $channel = $this->createMock(Channel::class); + + $this->event + ->expects(static::once()) + ->method('getChannel') + ->willReturn($channel); + + $channel + ->expects(static::once()) + ->method('getType') + ->willReturn('another_type'); + + $this->methodIdentifierGenerator + ->expects(static::never()) + ->method('generateIdentifier'); + + $this->handler + ->expects(static::never()) + ->method('handleMethodDisable'); + + $this->listener->onIntegrationDisable($this->event); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Handler/RulesPaymentMethodDisableHandlerDecoratorTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Handler/RulesPaymentMethodDisableHandlerDecoratorTest.php new file mode 100644 index 000000000..1f790f4a1 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Handler/RulesPaymentMethodDisableHandlerDecoratorTest.php @@ -0,0 +1,151 @@ +handler = $this->createMock(PaymentMethodDisableHandlerInterface::class); + $this->repository = $this->createMock(PaymentMethodsConfigsRuleRepository::class); + $this->paymentMethodProvider = $this->createMock(PaymentMethodProviderInterface::class); + + $this->decorator = new RulesPaymentMethodDisableHandlerDecorator( + $this->handler, + $this->repository, + $this->paymentMethodProvider + ); + } + + /** + * @param string $disabledMethodId + * @param array $configs + * @param array $registryMap + * + * @dataProvider testHandleMethodDisableProvider + */ + public function testHandleMethodDisable($disabledMethodId, $configs, $registryMap) + { + $this->handler->expects(self::once())->method('handleMethodDisable')->with($disabledMethodId); + + $configMocks = []; + $registryMapValues = []; + $methods = []; + foreach ($registryMap as $methodId => $enabled) { + $methods[$methodId] = $this->createMock(PaymentMethodInterface::class); + $methods[$methodId]->expects(self::any())->method('isEnabled')->willReturn($enabled); + $registryMapValues[] = [$methodId, $methods[$methodId]]; + } + + $rules = []; + foreach ($configs as $configName => $config) { + $methodConfigs = []; + foreach ($config['methods'] as $methodId) { + $methodConfig = $this->createMock(PaymentMethodConfig::class); + $methodConfig->expects(self::once())->method('getMethod')->willReturn($methodId); + $methodConfigs[] = $methodConfig; + } + $rules[$configName] = $this->createMock(Rule::class); + $rules[$configName]->expects(self::exactly($config['rule_disabled']))->method('setEnabled')->with(false); + + $configMock = $this->createMock(PaymentMethodsConfigsRule::class); + $configMock->expects(self::once()) + ->method('getMethodConfigs') + ->willReturn($methodConfigs); + $configMock->expects(self::any()) + ->method('getRule') + ->willReturn($rules[$configName]); + $configMocks[] = $configMock; + } + + $this->paymentMethodProvider + ->method('getPaymentMethod') + ->will($this->returnValueMap($registryMapValues)); + + $this->repository->expects(self::once()) + ->method('getEnabledRulesByMethod') + ->willReturn($configMocks); + + $this->decorator->handleMethodDisable($disabledMethodId); + } + + /** + * @return array + */ + public function testHandleMethodDisableProvider() + { + return [ + 'a_few_methods' => + [ + 'methodId' => 'method1', + 'configs' => + [ + 'config1' => + [ + 'methods' => ['method1', 'method2'], + 'rule_disabled' => 1, + ], + 'config2' => + [ + 'methods' => ['method1', 'method3'], + 'rule_disabled' => 0, + ] + ], + 'registry_map' => + [ + 'method1' => true, + 'method2' => false, + 'method3' => true, + ], + ], + 'only_method' => + [ + 'methodId' => 'method1', + 'configs' => + [ + 'config1' => + [ + 'methods' => ['method1'], + 'rule_disabled' => 1, + ], + ], + 'registry_map' => + [ + 'method1' => true, + ], + ], + ]; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/PaymentMethodViewCollectionTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/PaymentMethodViewCollectionTest.php new file mode 100644 index 000000000..e8150352d --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/PaymentMethodViewCollectionTest.php @@ -0,0 +1,212 @@ +createCollection(); + + $methodId = 'someMethodId'; + + $view = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $addResult = $collection->addMethodView($methodId, $view); + + $actualView = $collection->getMethodView($methodId); + + $this->assertEquals($collection, $addResult); + $this->assertNotNull($actualView); + $this->assertEquals($view, $actualView); + } + + public function testGetMethodViewWhenNotExists() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + + $actualView = $collection->getMethodView($methodId); + + $this->assertNull($actualView); + } + + public function testAddMethodViewWhenAlreadyExists() + { + $methodId = 'someMethodId'; + + $collection = $this->createCollection(); + + $view = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $view); + + $view2 = [ + 'someField3' => 'someValue4', + 'someField4' => 'someValue4', + 'sortOrder' => 1 + ]; + + $addMethodViewResult = $collection->addMethodView($methodId, $view2); + + $actualView = $collection->getMethodView($methodId); + + $this->assertNotNull($actualView); + $this->assertEquals($view, $actualView); + $this->assertEquals($collection, $addMethodViewResult); + } + + public function testHasMethodView() + { + $methodId = 'someMethodId'; + $collection = $this->createCollection(); + + $collection->addMethodView($methodId, []); + + $this->assertTrue($collection->hasMethodView($methodId)); + } + + public function testHasMethodViewNotExists() + { + $collection = $this->createCollection(); + + $this->assertFalse($collection->hasMethodView('someMethodId')); + } + + public function testRemoveMethodView() + { + $methodId = 'someMethodId'; + $collection = $this->createCollection(); + + $collection->addMethodView($methodId, []); + + $this->assertTrue($collection->hasMethodView($methodId)); + + $removeResult = $collection->removeMethodView($methodId); + + $this->assertEquals($collection, $removeResult); + $this->assertFalse($collection->hasMethodView($methodId)); + } + + public function testRemoveMethodViewWhenNotExists() + { + $methodId = 'someMethodId'; + $collection = $this->createCollection(); + + $removeResult = $collection->removeMethodView($methodId); + + $this->assertEquals($collection, $removeResult); + $this->assertFalse($collection->hasMethodView($methodId)); + } + + public function testGetAllMethodsViews() + { + $collection = $this->createCollection(); + + $this->assertEquals([], $collection->getAllMethodsViews()); + + $methodId = 'someMethodId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $methodView); + + $this->assertEquals([$methodId => $methodView], $collection->getAllMethodsViews()); + + $methodId2 = 'someOtherMethodId'; + + $methodView2 = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId2, $methodView2); + + $this->assertEquals([$methodId => $methodView, $methodId2 => $methodView2], $collection->getAllMethodsViews()); + } + + public function testToArray() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $methodView); + + $methodId2 = 'someOtherMethodId'; + + $methodView2 = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId2, $methodView2); + + $this->assertEquals( + [ + $methodId => $methodView, + $methodId2 => $methodView2 + ], + $collection->toArray() + ); + } + + public function testIsEmpty() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $this->assertTrue($collection->isEmpty()); + + $collection->addMethodView($methodId, $methodView); + + $this->assertFalse($collection->isEmpty()); + + $collection->clear(); + + $this->assertTrue($collection->isEmpty()); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/PaymentMethodViewFactoryTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/PaymentMethodViewFactoryTest.php new file mode 100644 index 000000000..20f85b362 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/PaymentMethodViewFactoryTest.php @@ -0,0 +1,53 @@ +paymentMethodProviderMock = $this + ->getMockBuilder(PaymentMethodProviderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->paymentMethodViewFactory = new PaymentMethodViewFactory($this->paymentMethodProviderMock); + } + + public function testCreateMethodView() + { + $methodId = 'someId'; + $label = 'someLabel'; + $sortOrder = 5; + $options = [ + 'option1', + 'option2' + ]; + + $expected = [ + 'identifier' => $methodId, + 'label' => $label, + 'sortOrder' => $sortOrder, + 'options' => $options + ]; + + $actual = $this->paymentMethodViewFactory->createMethodView($methodId, $label, $sortOrder, $options); + + $this->assertEquals($expected, $actual); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Provider/ChannelPaymentMethodProviderTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Provider/ChannelPaymentMethodProviderTest.php new file mode 100644 index 000000000..81c3f9c5f --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Method/Provider/ChannelPaymentMethodProviderTest.php @@ -0,0 +1,136 @@ +doctrineHelper = $this->createMock(DoctrineHelper::class); + $repository = $this->createMock(ChannelRepository::class); + + $this->doctrineHelper + ->method('getEntityRepository') + ->with('OroIntegrationBundle:Channel') + ->willReturn($repository); + + $loadedChannel = $this->createChannel('ch_enabled'); + $fetchedChannel = $this->createChannel('ch_disabled'); + + $this->enabledMethod = $this->createMock(PaymentMethodInterface::class); + $this->enabledMethod + ->method('getIdentifier') + ->willReturn('ups_10'); + + $this->disabledMethod = $this->createMock(PaymentMethodInterface::class); + $this->disabledMethod + ->method('getIdentifier') + ->willReturn('ups_20'); + + $this->methodFactory = $this->createMock(IntegrationPaymentMethodFactoryInterface::class); + $this->methodFactory + ->method('create') + ->will($this->returnValueMap([ + [$loadedChannel, $this->enabledMethod], + [$fetchedChannel, $this->disabledMethod], + ])); + + $this->provider = new ChannelPaymentMethodProvider(static::TYPE, $this->doctrineHelper, $this->methodFactory); + + $doctrineEvent = $this->createLifecycleEventArgsMock(); + $this->provider->postLoad($loadedChannel, $doctrineEvent); + + $repository + ->method('findByTypeAndExclude') + ->will(static::returnCallback(function () use ($fetchedChannel, $doctrineEvent) { + $this->provider->postLoad($fetchedChannel, $doctrineEvent); + return [$fetchedChannel]; + })); + } + + public function testGetPaymentMethods() + { + $methods = $this->provider->getPaymentMethods(); + static::assertCount(2, $methods); + $actualMethod = reset($methods); + static::assertSame($this->enabledMethod, $actualMethod); + } + + public function testGetPaymentMethod() + { + $method = $this->provider->getPaymentMethod($this->enabledMethod->getIdentifier()); + static::assertInstanceOf(PaymentMethodInterface::class, $method); + } + + public function testHasPaymentMethod() + { + static::assertTrue($this->provider->hasPaymentMethod($this->enabledMethod->getIdentifier())); + } + + public function testHasPaymentMethodFalse() + { + static::assertFalse($this->provider->hasPaymentMethod('wrong')); + } + + /** + * @param string $name + * + * @return Channel + */ + private function createChannel($name) + { + return $this->getEntity( + Channel::class, + ['id' => 20, 'name' => $name, 'type' => static::TYPE] + ); + } + + /** + * @return LifecycleEventArgs|\PHPUnit\Framework\MockObject\MockObject + */ + private function createLifecycleEventArgsMock() + { + return $this->createMock(LifecycleEventArgs::class); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/BasicPaymentMethodChoicesProviderTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/BasicPaymentMethodChoicesProviderTest.php new file mode 100644 index 000000000..8e4979a52 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/BasicPaymentMethodChoicesProviderTest.php @@ -0,0 +1,135 @@ +paymentMethodProvider = $this->createMock(PaymentMethodProviderInterface::class); + $this->translator = $this->createMock(TranslatorInterface::class); + $this->choicesProvider = new BasicPaymentMethodChoicesProvider( + $this->paymentMethodProvider, + $this->translator + ); + } + + /** + * @param array $methods + * @param array $result + * @param bool $translate + * + * @dataProvider methodsProvider + */ + public function testGetMethods($methods, $result, $translate = false) + { + $translation = [ + ['bank transfer', [], null, null, 'bank transfer translated'], + ['payment term', [], null, null, 'payment term translated'], + ]; + + $this->paymentMethodProvider->expects($this->once()) + ->method('getPaymentMethods') + ->willReturn($methods); + + $this->translator->expects($this->any()) + ->method('trans') + ->will($this->returnValueMap($translation)); + + $this->assertEquals($result, $this->choicesProvider->getMethods($translate)); + } + + /** + * @return array + */ + public function methodsProvider() + { + return + [ + 'some_methods' => + [ + 'methods' => + [ + 'payment_term' => $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'payment_term', + 'sortOrder' => 1, + 'label' => 'payment term', + 'isEnabled' => false, + 'options' => [], + ] + ), + 'bank_transfer' => $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'bank_transfer', + 'sortOrder' => 1, + 'label' => 'bank transfer', + 'isEnabled' => true, + 'options' => [], + ] + ), + ], + 'result' => ['payment_term' => 'payment term', 'bank_transfer' => 'bank transfer'], + 'translate' => false, + ], + 'some_methods_with_translation' => + [ + 'methods' => + [ + 'bank_transfer' => $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'bank_transfer', + 'sortOrder' => 1, + 'label' => 'bank transfer', + 'isEnabled' => true, + 'options' => [], + ] + ), + 'payment_term' => $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'payment_term', + 'sortOrder' => 1, + 'label' => 'payment term', + 'isEnabled' => false, + 'options' => [], + ] + ), + ], + 'result' => ['bank_transfer' => 'bank transfer translated', 'payment_term' => 'payment term translated'], + 'translate' => true, + ], + 'no_methods' => + [ + 'methods' => [], + 'result' => [], + ], + ]; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/EnabledPaymentMethodChoicesProviderDecoratorTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/EnabledPaymentMethodChoicesProviderDecoratorTest.php new file mode 100644 index 000000000..6e995f9b3 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/EnabledPaymentMethodChoicesProviderDecoratorTest.php @@ -0,0 +1,178 @@ +paymentMethodProvider = $this->createMock(PaymentMethodProviderInterface::class); + $this->choicesProvider = $this->createMock(PaymentMethodChoicesProviderInterface::class); + $this->enabledChoicesProvider = new EnabledPaymentMethodChoicesProviderDecorator( + $this->paymentMethodProvider, + $this->choicesProvider + ); + } + + /** + * @param array $registryMap + * @param array $choices + * @param array $result + * + * @dataProvider methodsProvider + */ + public function testGetMethods($registryMap, $choices, $result) + { + $this->paymentMethodProvider->expects($this->any()) + ->method('getPaymentMethod') + ->will($this->returnValueMap($registryMap)); + + $this->choicesProvider->expects($this->once()) + ->method('getMethods') + ->willReturn($choices); + + $this->assertEquals($result, $this->enabledChoicesProvider->getMethods()); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function methodsProvider() + { + return + [ + 'all_methods_enabled' => + [ + 'methods_map' => + [ + [ + 'bank_transfer', + $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'bank_transfer', + 'sortOrder' => 1, + 'label' => 'bank transfer', + 'isEnabled' => true, + 'options' => ['instructions'], + ] + ), + ], + [ + 'payment_term', + $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'payment_term', + 'sortOrder' => 1, + 'label' => 'payment term', + 'isEnabled' => true, + 'options' => [], + ] + ), + ], + ], + 'choices' => ['payment_term' => 'payment term', 'bank_transfer' => 'bank transfer'], + 'result' => ['payment_term' => 'payment term', 'bank_transfer' => 'bank transfer'], + ], + 'some_methods_disabled' => + [ + 'methods_map' => + [ + [ + 'bank_transfer', + $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'bank_transfer', + 'sortOrder' => 1, + 'label' => 'bank transfer', + 'isEnabled' => true, + 'options' => ['instructions'], + ] + ), + ], + [ + 'payment_term', + $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'payment_term', + 'sortOrder' => 1, + 'label' => 'payment term', + 'isEnabled' => false, + 'options' => [], + ] + ), + ], + ], + 'choices' => ['bank_transfer' => 'bank transfer', 'payment_term' => 'payment term'], + 'result' => ['bank_transfer' => 'bank transfer'], + ], + 'all_disabled_methods' => + [ + 'methods_map' => + [ + [ + 'bank_transfer', + $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'bank_transfer', + 'sortOrder' => 1, + 'label' => 'bank transfer', + 'isEnabled' => false, + 'options' => [], + ] + ), + ], + [ + 'payment_term', + $this->getEntity( + PaymentMethodStub::class, + [ + 'identifier' => 'payment_term', + 'sortOrder' => 1, + 'label' => 'payment term', + 'isEnabled' => false, + 'options' => [], + ] + ), + ], + ], + 'choices' => ['flat rate' => 'flat_rate', 'ups' => 'ups'], + 'result' => [], + ], + 'no_methods' => + [ + 'methods' => [], + 'choices' => [], + 'result' => [], + ], + ]; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/Basic/BasicMethodsConfigsRulesByContextProviderTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/Basic/BasicMethodsConfigsRulesByContextProviderTest.php new file mode 100644 index 000000000..fb37624f5 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/Basic/BasicMethodsConfigsRulesByContextProviderTest.php @@ -0,0 +1,114 @@ +repository = $this->createMock(PaymentMethodsConfigsRuleRepository::class); + + $this->filtrationService = $this->createMock(MethodsConfigsRulesFiltrationServiceInterface::class); + + $this->provider = new BasicMethodsConfigsRulesByContextProvider( + $this->filtrationService, + $this->repository + ); + } + + public function testGetAllFilteredPaymentMethodsConfigsWithBillingAddress() + { + $currency = 'USD'; + $address = $this->createAddressMock(); + $rulesFromDb = [ + $this->createPaymentMethodsConfigsRuleMock(), + $this->createPaymentMethodsConfigsRuleMock(), + ]; + + $this->repository->expects(static::once()) + ->method('getByDestinationAndCurrency') + ->with($address, $currency) + ->willReturn($rulesFromDb); + + $this->repository->expects(static::never()) + ->method('getByCurrencyWithoutDestination'); + + $context = $this->createPaymentContextMock(); + $context->method('getCurrency') + ->willReturn($currency); + $context->method('getBillingAddress') + ->willReturn($address); + + $expectedRules = [$this->createPaymentMethodsConfigsRuleMock()]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredPaymentMethodsConfigsRules') + ->with($rulesFromDb) + ->willReturn($expectedRules); + + static::assertSame( + $expectedRules, + $this->provider->getPaymentMethodsConfigsRules($context) + ); + } + + public function testGetAllFilteredPaymentMethodsConfigsWithoutShippingAddress() + { + $currency = 'USD'; + $rulesFromDb = [$this->createPaymentMethodsConfigsRuleMock()]; + + $this->repository->expects(static::once()) + ->method('getByCurrencyWithoutDestination') + ->with($currency) + ->willReturn($rulesFromDb); + + $context = $this->createPaymentContextMock(); + $context->method('getCurrency') + ->willReturn($currency); + + $expectedRules = [$this->createPaymentMethodsConfigsRuleMock()]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredPaymentMethodsConfigsRules') + ->with($rulesFromDb) + ->willReturn($expectedRules); + + static::assertSame( + $expectedRules, + $this->provider->getPaymentMethodsConfigsRules($context) + ); + } + + /** + * @return AddressInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function createAddressMock() + { + return $this->createMock(AddressInterface::class); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/RegardlessDestination/RegardlessDestinationMethodsConfigsRulesByContextProviderTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/RegardlessDestination/RegardlessDestinationMethodsConfigsRulesByContextProviderTest.php new file mode 100644 index 000000000..16b011b02 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/RegardlessDestination/RegardlessDestinationMethodsConfigsRulesByContextProviderTest.php @@ -0,0 +1,114 @@ +repository = $this->createMock(PaymentMethodsConfigsRuleRepository::class); + + $this->filtrationService = $this->createMock(MethodsConfigsRulesFiltrationServiceInterface::class); + + $this->provider = new RegardlessDestination\RegardlessDestinationMethodsConfigsRulesByContextProvider( + $this->filtrationService, + $this->repository + ); + } + + public function testGetAllFilteredPaymentMethodsConfigsWithBillingAddress() + { + $currency = 'USD'; + $address = $this->createAddressMock(); + $rulesFromDb = [$this->createPaymentMethodsConfigsRuleMock()]; + + $this->repository->expects(static::once()) + ->method('getByDestinationAndCurrency') + ->with($address, $currency) + ->willReturn($rulesFromDb); + + $this->repository->expects(static::never()) + ->method('getByCurrency'); + + $context = $this->createPaymentContextMock(); + $context->method('getCurrency') + ->willReturn($currency); + $context->method('getBillingAddress') + ->willReturn($address); + + $expectedRules = [ + $this->createPaymentMethodsConfigsRuleMock(), + $this->createPaymentMethodsConfigsRuleMock(), + ]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredPaymentMethodsConfigsRules') + ->with($rulesFromDb) + ->willReturn($expectedRules); + + static::assertSame( + $expectedRules, + $this->provider->getPaymentMethodsConfigsRules($context) + ); + } + + public function testGetAllFilteredPaymentMethodsConfigsWithoutBillingAddress() + { + $currency = 'USD'; + $rulesFromDb = [$this->createPaymentMethodsConfigsRuleMock()]; + + $this->repository->expects(static::once()) + ->method('getByCurrency') + ->with($currency) + ->willReturn($rulesFromDb); + + $context = $this->createPaymentContextMock(); + $context->method('getCurrency') + ->willReturn($currency); + + $expectedRules = [$this->createPaymentMethodsConfigsRuleMock()]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredPaymentMethodsConfigsRules') + ->with($rulesFromDb) + ->willReturn($expectedRules); + + static::assertSame( + $expectedRules, + $this->provider->getPaymentMethodsConfigsRules($context) + ); + } + + /** + * @return AddressInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function createAddressMock() + { + return $this->createMock(AddressInterface::class); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/Stub/PaymentMethodProviderStub.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/Stub/PaymentMethodProviderStub.php new file mode 100644 index 000000000..94a3f3889 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/Stub/PaymentMethodProviderStub.php @@ -0,0 +1,48 @@ +setIdentifier(self::METHOD_IDENTIFIER); + + $this->method = $method; + } + + /** + * {@inheritdoc} + */ + public function getPaymentMethods() + { + return [$this->method->getIdentifier() => $this->method]; + } + + /** + * {@inheritdoc} + */ + public function getPaymentMethod($name) + { + if ($name === $this->method->getIdentifier()) { + return $this->method; + } + return null; + } + + /** + * {@inheritdoc} + */ + public function hasPaymentMethod($name) + { + return $name === $this->method->getIdentifier(); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/Stub/PaymentMethodStub.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/Stub/PaymentMethodStub.php new file mode 100644 index 000000000..ad649e6b6 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/Provider/Stub/PaymentMethodStub.php @@ -0,0 +1,150 @@ +identifier; + } + + /** + * @param string $identifier + * @return $this + */ + public function setIdentifier($identifier) + { + $this->identifier = $identifier; + + return $this; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label ?: $this->identifier . '.label'; + } + + /** + * @param string $label + * @return $this + */ + public function setLabel($label) + { + $this->label = $label; + + return $this; + } + + /** + * @return int + */ + public function getSortOrder() + { + return $this->sortOrder; + } + + /** + * @param int $sortOrder + * @return $this + */ + public function setSortOrder($sortOrder) + { + $this->sortOrder = $sortOrder; + + return $this; + } + + /** + * @return string + */ + public function getOptionsConfigurationFormType() + { + return $this->optionsConfigurationFormType; + } + + /** + * @param string $optionsConfigurationFormType + * @return $this + */ + public function setOptionsConfigurationFormType($optionsConfigurationFormType) + { + $this->optionsConfigurationFormType = $optionsConfigurationFormType; + + return $this; + } + + /** + * @return boolean + */ + public function isEnabled() + { + return $this->isEnabled; + } + + /** + * @param boolean $isEnabled + * @return PaymentMethodStub + */ + public function setEnabled($isEnabled) + { + $this->isEnabled = $isEnabled; + return $this; + } + /** + * @inheritDoc + */ + public function getOptions() + { + return $this->options; + } + + /** + * @param boolean $options + * @return PaymentMethodStub + */ + public function setOptions($options) + { + $this->options = $options; + return $this; + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Tests/Unit/RuleFiltration/Basic/BasicMethodsConfigsRulesFiltrationServiceTest.php b/src/Marello/Bundle/PaymentBundle/Tests/Unit/RuleFiltration/Basic/BasicMethodsConfigsRulesFiltrationServiceTest.php new file mode 100644 index 000000000..fb36cf77c --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Tests/Unit/RuleFiltration/Basic/BasicMethodsConfigsRulesFiltrationServiceTest.php @@ -0,0 +1,96 @@ +filtrationService = $this->createMock(RuleFiltrationServiceInterface::class); + $this->paymentContextToRuleValuesConverter = $this + ->createMock(PaymentContextToRulesValueConverterInterface::class); + + $this->basicMethodsConfigsRulesFiltrationService = new BasicMethodsConfigsRulesFiltrationService( + $this->filtrationService, + $this->paymentContextToRuleValuesConverter + ); + } + + /** + * {@inheritDoc} + */ + public function testGetFilteredPaymentMethodsConfigsRules() + { + $configRules = [ + $this->createPaymentMethodsConfigsRule(), + $this->createPaymentMethodsConfigsRule(), + ]; + $context = $this->createContextMock(); + $values = [ + 'currency' => 'USD', + ]; + + $this->paymentContextToRuleValuesConverter->expects(static::once()) + ->method('convert') + ->with($context) + ->willReturn($values); + + $expectedConfigRules = [ + $this->createPaymentMethodsConfigsRule(), + $this->createPaymentMethodsConfigsRule(), + ]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredRuleOwners') + ->with($configRules, $values) + ->willReturn($expectedConfigRules); + + static::assertEquals( + $expectedConfigRules, + $this->basicMethodsConfigsRulesFiltrationService->getFilteredPaymentMethodsConfigsRules( + $configRules, + $context + ) + ); + } + + /** + * @return PaymentContextInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function createContextMock() + { + return $this->createMock(PaymentContextInterface::class); + } + + /** + * @return PaymentMethodsConfigsRule|\PHPUnit\Framework\MockObject\MockObject + */ + private function createPaymentMethodsConfigsRule() + { + return $this->createMock(PaymentMethodsConfigsRule::class); + } +} diff --git a/src/Marello/Bundle/PaymentBundle/Twig/PaymentMethodExtension.php b/src/Marello/Bundle/PaymentBundle/Twig/PaymentMethodExtension.php new file mode 100755 index 000000000..cea343cb2 --- /dev/null +++ b/src/Marello/Bundle/PaymentBundle/Twig/PaymentMethodExtension.php @@ -0,0 +1,111 @@ +paymentMethodLabelFormatter = $paymentMethodLabelFormatter; + $this->dispatcher = $dispatcher; + $this->checker = $checker; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return static::PAYMENT_METHOD_EXTENSION_NAME; + } + + /** + * @param string $paymentMethodName + * + * @return string Payment Method config template path + */ + public function getPaymentMethodConfigRenderData($paymentMethodName) + { + $event = new PaymentMethodConfigDataEvent($paymentMethodName); + if (!array_key_exists($paymentMethodName, $this->configCache)) { + $this->dispatcher->dispatch(PaymentMethodConfigDataEvent::NAME, $event); + $template = $event->getTemplate(); + if (!$template) { + $template = static::DEFAULT_METHOD_CONFIG_TEMPLATE; + } + $this->configCache[$paymentMethodName] = $template; + } + + return $this->configCache[$paymentMethodName]; + } + + /** + * @param string $methodIdentifier + * + * @return bool + */ + public function isPaymentMethodEnabled($methodIdentifier) + { + return $this->checker->isEnabled($methodIdentifier); + } + + /** + * @return array + */ + public function getFunctions() + { + return [ + new TwigFunction( + 'marello_get_payment_method_label', + [$this->paymentMethodLabelFormatter, 'formatPaymentMethodLabel'] + ), + new TwigFunction( + 'marello_payment_method_config_template', + [$this, 'getPaymentMethodConfigRenderData'] + ), + new TwigFunction( + 'marello_payment_method_enabled', + [$this, 'isPaymentMethodEnabled'] + ) + ]; + } +} diff --git a/src/Marello/Bundle/PaymentTermBundle/Controller/PaymentTermController.php b/src/Marello/Bundle/PaymentTermBundle/Controller/PaymentTermController.php index c0dc0ef48..5af2532b6 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Controller/PaymentTermController.php +++ b/src/Marello/Bundle/PaymentTermBundle/Controller/PaymentTermController.php @@ -4,20 +4,21 @@ use Marello\Bundle\PaymentTermBundle\Entity\PaymentTerm; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Symfony\Component\HttpFoundation\Request; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; -class PaymentTermController extends Controller +class PaymentTermController extends AbstractController { /** - * @param Request $request * @return array * - * @Route("/", name="marello_paymentterm_paymentterm_index") - * @Template + * @Route( + * path="/", + * name="marello_paymentterm_paymentterm_index" + * ) + * @Template("MarelloPaymentTermBundle:PaymentTerm:index.html.twig") */ - public function indexAction(Request $request) + public function indexAction() { return [ 'entityClass' => PaymentTerm::class, @@ -25,13 +26,15 @@ public function indexAction(Request $request) } /** - * @param Request $request * @return array|\Symfony\Component\HttpFoundation\RedirectResponse * - * @Route("/create", name="marello_paymentterm_paymentterm_create") + * @Route( + * path="/create", + * name="marello_paymentterm_paymentterm_create" + * ) * @Template("MarelloPaymentTermBundle:PaymentTerm:update.html.twig") */ - public function createAction(Request $request) + public function createAction() { $entity = new PaymentTerm(); @@ -39,14 +42,17 @@ public function createAction(Request $request) } /** - * @param Request $request * @param PaymentTerm $entity * @return array|\Symfony\Component\HttpFoundation\RedirectResponse * - * @Route("/update/{id}", requirements={"id" = "\d+"}, name="marello_paymentterm_paymentterm_update") + * @Route( + * path="/update/{id}", + * requirements={"id" = "\d+"}, + * name="marello_paymentterm_paymentterm_update" + * ) * @Template("MarelloPaymentTermBundle:PaymentTerm:update.html.twig") */ - public function updateAction(Request $request, PaymentTerm $entity) + public function updateAction(PaymentTerm $entity) { return $this->update($entity); } @@ -75,14 +81,17 @@ protected function update(PaymentTerm $entity) } /** - * @param Request $request * @param PaymentTerm $entity * @return array * - * @Route("/view/{id}", requirements={"id" = "\d+"}, name="marello_paymentterm_paymentterm_view") - * @Template + * @Route( + * path="/view/{id}", + * requirements={"id" = "\d+"}, + * name="marello_paymentterm_paymentterm_view" + * ) + * @Template("MarelloPaymentTermBundle:PaymentTerm:view.html.twig") */ - public function viewAction(Request $request, PaymentTerm $entity) + public function viewAction(PaymentTerm $entity) { return [ 'entity' => $entity, diff --git a/src/Marello/Bundle/PaymentTermBundle/Entity/PaymentTermSettings.php b/src/Marello/Bundle/PaymentTermBundle/Entity/PaymentTermSettings.php new file mode 100644 index 000000000..f318823df --- /dev/null +++ b/src/Marello/Bundle/PaymentTermBundle/Entity/PaymentTermSettings.php @@ -0,0 +1,98 @@ +labels = new ArrayCollection(); + } + + /** + * @return Collection|LocalizedFallbackValue[] + */ + public function getLabels() + { + return $this->labels; + } + + /** + * @param LocalizedFallbackValue $label + * + * @return PaymentTermSettings + */ + public function addLabel(LocalizedFallbackValue $label) + { + if (!$this->labels->contains($label)) { + $this->labels->add($label); + } + + return $this; + } + + /** + * @param LocalizedFallbackValue $label + * + * @return PaymentTermSettings + */ + public function removeLabel(LocalizedFallbackValue $label) + { + if ($this->labels->contains($label)) { + $this->labels->removeElement($label); + } + + return $this; + } + + /** + * @return ParameterBag + */ + public function getSettingsBag() + { + if (null === $this->settings) { + $this->settings = new ParameterBag([ + self::SETTINGS_FIELD_LABELS => $this->getLabels()->toArray() + ]); + } + + return $this->settings; + } +} diff --git a/src/Marello/Bundle/PaymentTermBundle/Entity/Repository/PaymentTermSettingsRepository.php b/src/Marello/Bundle/PaymentTermBundle/Entity/Repository/PaymentTermSettingsRepository.php new file mode 100644 index 000000000..38faa7834 --- /dev/null +++ b/src/Marello/Bundle/PaymentTermBundle/Entity/Repository/PaymentTermSettingsRepository.php @@ -0,0 +1,38 @@ +aclHelper = $aclHelper; + } + + /** + * @return PaymentTermSettings[] + */ + public function findWithEnabledChannel() + { + $qb = $this->createQueryBuilder('pts'); + + $qb + ->join('pts.channel', 'ch') + ->where('ch.enabled = true') + ->orderBy('pts.id'); + + return $this->aclHelper->apply($qb)->getResult(); + } +} diff --git a/src/Marello/Bundle/PaymentTermBundle/EventListener/PaymentTermMethodViewListener.php b/src/Marello/Bundle/PaymentTermBundle/EventListener/PaymentTermMethodViewListener.php new file mode 100644 index 000000000..5f4ee72f1 --- /dev/null +++ b/src/Marello/Bundle/PaymentTermBundle/EventListener/PaymentTermMethodViewListener.php @@ -0,0 +1,46 @@ +paymentTermProvider = $paymentTermProvider; + } + + /** + * @param ApplicablePaymentMethodViewEvent $event + */ + public function onApplicablePaymentMethodView(ApplicablePaymentMethodViewEvent $event) + { + + if (strpos($event->getMethodId(), 'payment_term') !== false) { + $context = $event->getPaymentContext(); + $options = $event->getOptions(); + $paymentTerm = null; + if ($context->getCustomer()) { + $paymentTerm = $this->paymentTermProvider->getCustomerPaymentTerm($context->getCustomer()); + } + if (!$paymentTerm) { + $paymentTerm = $this->paymentTermProvider->getDefaultPaymentTerm(); + } + if ($paymentTerm) { + $options['code'] = $paymentTerm->getCode(); + $options['term'] = $paymentTerm->getTerm(); + $event->setOptions($options); + } + } + } +} diff --git a/src/Marello/Bundle/PaymentTermBundle/Form/Type/PaymentTermChoiceType.php b/src/Marello/Bundle/PaymentTermBundle/Form/Type/PaymentTermChoiceType.php index 1b1222a88..605422292 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Form/Type/PaymentTermChoiceType.php +++ b/src/Marello/Bundle/PaymentTermBundle/Form/Type/PaymentTermChoiceType.php @@ -2,7 +2,6 @@ namespace Marello\Bundle\PaymentTermBundle\Form\Type; -use Marello\Bundle\PaymentTermBundle\Entity\PaymentTerm; use Marello\Bundle\PaymentTermBundle\Provider\PaymentTermProvider; use Oro\Bundle\LocaleBundle\Helper\LocalizationHelper; use Symfony\Component\Form\AbstractType; diff --git a/src/Marello/Bundle/PaymentTermBundle/Form/Type/PaymentTermSettingsType.php b/src/Marello/Bundle/PaymentTermBundle/Form/Type/PaymentTermSettingsType.php new file mode 100644 index 000000000..00fa2f3b3 --- /dev/null +++ b/src/Marello/Bundle/PaymentTermBundle/Form/Type/PaymentTermSettingsType.php @@ -0,0 +1,61 @@ +add( + 'labels', + LocalizedFallbackValueCollectionType::class, + [ + 'label' => 'marello.paymentterm.settings.labels.label', + 'required' => true, + 'entry_options' => ['constraints' => [new NotBlank()]], + ] + ); + } + + /** + * @param OptionsResolver $resolver + * + * @throws AccessException + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => PaymentTermSettings::class, + ]); + } + + /** + * @return string + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } +} diff --git a/src/Marello/Bundle/PaymentTermBundle/Integration/PaymentTermChannelType.php b/src/Marello/Bundle/PaymentTermBundle/Integration/PaymentTermChannelType.php new file mode 100644 index 000000000..363952058 --- /dev/null +++ b/src/Marello/Bundle/PaymentTermBundle/Integration/PaymentTermChannelType.php @@ -0,0 +1,27 @@ +identifierGenerator = $identifierGenerator; + $this->localizationHelper = $localizationHelper; + $this->integrationIconProvider = $integrationIconProvider; + } + + /** + * @param Channel $channel + * + * @return ManualShippingMethod + */ + public function create(Channel $channel) + { + $id = $this->identifierGenerator->generateIdentifier($channel); + $label = $this->getChannelLabel($channel); + $icon = $this->getIcon($channel); + + return new PaymentTermMethod($id, $label, $icon, $channel->isEnabled()); + } + + /** + * @param Channel $channel + * + * @return string + */ + private function getChannelLabel(Channel $channel) + { + /** @var ManualShippingSettings $transport */ + $transport = $channel->getTransport(); + + return (string) $this->localizationHelper->getLocalizedValue($transport->getLabels()); + } + + /** + * @param Channel $channel + * + * @return string|null + */ + private function getIcon(Channel $channel) + { + return $this->integrationIconProvider->getIcon($channel); + } +} diff --git a/src/Marello/Bundle/PaymentTermBundle/Method/PaymentTermMethod.php b/src/Marello/Bundle/PaymentTermBundle/Method/PaymentTermMethod.php new file mode 100644 index 000000000..7e65dfd3d --- /dev/null +++ b/src/Marello/Bundle/PaymentTermBundle/Method/PaymentTermMethod.php @@ -0,0 +1,100 @@ +identifier = $identifier; + $this->label = $label; + $this->icon = $icon; + $this->enabled = $enabled; + } + + /** + * {@inheritDoc} + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * {@inheritDoc} + */ + public function isEnabled() + { + return $this->enabled; + } + + /** + * {@inheritDoc} + */ + public function getLabel() + { + return $this->label; + } + + /** + * {@inheritDoc} + */ + public function getIcon() + { + return $this->icon; + } + + /** + * {@inheritDoc} + */ + public function getOptionsConfigurationFormType() + { + return HiddenType::class; + } + + /** + * {@inheritDoc} + */ + public function getOptions() + { + return []; + } + + /** + * {@inheritDoc} + */ + public function getSortOrder() + { + return 10; + } +} diff --git a/src/Marello/Bundle/PaymentTermBundle/Method/Provider/PaymentTermMethodProvider.php b/src/Marello/Bundle/PaymentTermBundle/Method/Provider/PaymentTermMethodProvider.php new file mode 100644 index 000000000..9b4964bcd --- /dev/null +++ b/src/Marello/Bundle/PaymentTermBundle/Method/Provider/PaymentTermMethodProvider.php @@ -0,0 +1,21 @@ +container) { + return; + } + + $channel = $this->loadIntegration($manager); + + $this->addMethodConfigToDefaultPaymentRule($manager, $channel); + } + + /** + * @param ObjectManager $manager + * + * @return Channel + */ + private function loadIntegration(ObjectManager $manager) + { + $label = (new LocalizedFallbackValue())->setString('Payment Term'); + + $transport = new PaymentTermSettings(); + $transport->addLabel($label); + + $channel = new Channel(); + $channel->setType(PaymentTermChannelType::TYPE) + ->setName('Payment Term') + ->setEnabled(true) + ->setOrganization($this->getOrganization($manager)) + ->setTransport($transport); + + $manager->persist($channel); + $manager->flush(); + + return $channel; + } + + /** + * @param ObjectManager $manager + * @param Channel $channel + */ + private function addMethodConfigToDefaultPaymentRule(ObjectManager $manager, Channel $channel) + { + $methodConfig = new PaymentMethodConfig(); + $methodConfig->setMethod($this->getIdentifier($channel)); + + $defaultPaymentRule = $this->getReference(CreateDefaultPaymentRule::DEFAULT_RULE_REFERENCE); + $defaultPaymentRule->addMethodConfig($methodConfig); + + $manager->persist($defaultPaymentRule); + $manager->flush(); + } + + /** + * @param ObjectManager $manager + * + * @return Organization|object + */ + private function getOrganization(ObjectManager $manager) + { + if ($this->hasReference(LoadOrganizationAndBusinessUnitData::REFERENCE_DEFAULT_ORGANIZATION)) { + return $this->getReference(LoadOrganizationAndBusinessUnitData::REFERENCE_DEFAULT_ORGANIZATION); + } else { + return $manager + ->getRepository('OroOrganizationBundle:Organization') + ->getFirst(); + } + } + + /** + * @param Channel $channel + * + * @return int|string + */ + private function getIdentifier(Channel $channel) + { + return $this->container + ->get('marello_payment_term.method.identifier_generator.method') + ->generateIdentifier($channel); + } +} diff --git a/src/Marello/Bundle/PaymentTermBundle/Migrations/Schema/MarelloPaymentTermBundleInstaller.php b/src/Marello/Bundle/PaymentTermBundle/Migrations/Schema/MarelloPaymentTermBundleInstaller.php index df5ecbfef..2eb2125de 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Migrations/Schema/MarelloPaymentTermBundleInstaller.php +++ b/src/Marello/Bundle/PaymentTermBundle/Migrations/Schema/MarelloPaymentTermBundleInstaller.php @@ -16,7 +16,7 @@ class MarelloPaymentTermBundleInstaller implements Installation */ public function getMigrationVersion() { - return 'v1_0'; + return 'v1_1'; } /** @@ -27,6 +27,8 @@ public function up(Schema $schema, QueryBag $queries) self::createPaymentTermTable($schema); self::createPaymentTermLabelsTable($schema); self::createForeignKeys($schema); + self::createMarelloPaymentTermTransportLabelTable($schema); + self::addMarelloPaymentTermTransportLabelForeignKeys($schema); } /** @@ -57,7 +59,22 @@ protected static function createPaymentTermLabelsTable(Schema $schema) $table->setPrimaryKey(['paymentterm_id', 'localized_value_id']); } + + /** + * @param Schema $schema + */ + protected static function createMarelloPaymentTermTransportLabelTable(Schema $schema) + { + $table = $schema->createTable('marello_payment_term_trans_lbl'); + + $table->addColumn('transport_id', 'integer', []); + $table->addColumn('localized_value_id', 'integer', []); + $table->setPrimaryKey(['transport_id', 'localized_value_id']); + $table->addIndex(['transport_id'], 'marello_payment_term_trans_label_transport_id', []); + $table->addUniqueIndex(['localized_value_id'], 'marello_payment_term_trans_label_localized_value_id', []); + } + /** * @param Schema $schema */ @@ -77,4 +94,29 @@ protected static function createForeignKeys(Schema $schema) ['onDelete' => 'CASCADE'] ); } + + /** + * @param Schema $schema + * + * @throws SchemaException + */ + protected static function addMarelloPaymentTermTransportLabelForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_payment_term_trans_lbl'); + + $table->addForeignKeyConstraint( + $schema->getTable('oro_fallback_localization_val'), + ['localized_value_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + + $table->addForeignKeyConstraint( + $schema->getTable('oro_integration_transport'), + ['transport_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + } + } diff --git a/src/Marello/Bundle/PaymentTermBundle/Migrations/Schema/v1_1/MarelloPaymentTermBundle.php b/src/Marello/Bundle/PaymentTermBundle/Migrations/Schema/v1_1/MarelloPaymentTermBundle.php new file mode 100644 index 000000000..96f38fb1a --- /dev/null +++ b/src/Marello/Bundle/PaymentTermBundle/Migrations/Schema/v1_1/MarelloPaymentTermBundle.php @@ -0,0 +1,63 @@ +createTable('marello_payment_term_trans_lbl'); + + $table->addColumn('transport_id', 'integer', []); + $table->addColumn('localized_value_id', 'integer', []); + + $table->setPrimaryKey(['transport_id', 'localized_value_id']); + $table->addIndex(['transport_id'], 'marello_payment_term_trans_label_transport_id', []); + $table->addUniqueIndex(['localized_value_id'], 'marello_payment_term_trans_label_localized_value_id', []); + } + + /** + * @param Schema $schema + * + * @throws SchemaException + */ + protected static function addMarelloPaymentTermTransportLabelForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_payment_term_trans_lbl'); + + $table->addForeignKeyConstraint( + $schema->getTable('oro_fallback_localization_val'), + ['localized_value_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + + $table->addForeignKeyConstraint( + $schema->getTable('oro_integration_transport'), + ['transport_id'], + ['id'], + ['onDelete' => 'CASCADE', 'onUpdate' => null] + ); + } + +} diff --git a/src/Marello/Bundle/PaymentTermBundle/Provider/PaymentTermProvider.php b/src/Marello/Bundle/PaymentTermBundle/Provider/PaymentTermProvider.php index 63ba1deaf..569fb149d 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Provider/PaymentTermProvider.php +++ b/src/Marello/Bundle/PaymentTermBundle/Provider/PaymentTermProvider.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\PaymentTermBundle\Provider; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\PaymentTermBundle\DependencyInjection\Configuration; use Marello\Bundle\PaymentTermBundle\Entity\PaymentTerm; use Oro\Bundle\ConfigBundle\Config\ConfigManager; diff --git a/src/Marello/Bundle/PaymentTermBundle/Resources/config/form.yml b/src/Marello/Bundle/PaymentTermBundle/Resources/config/form.yml index 2d740731b..826baa052 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Resources/config/form.yml +++ b/src/Marello/Bundle/PaymentTermBundle/Resources/config/form.yml @@ -12,6 +12,7 @@ services: marello_payment_term.payment_term.form.handler: class: 'Marello\Bundle\PaymentTermBundle\Form\Handler\PaymentTermFormHandler' + public: true arguments: - '@marello_payment_term.payment_term.form' - '@request_stack' diff --git a/src/Marello/Bundle/PaymentTermBundle/Resources/config/oro/actions.yml b/src/Marello/Bundle/PaymentTermBundle/Resources/config/oro/actions.yml index a24046cdf..9cbe5a65e 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Resources/config/oro/actions.yml +++ b/src/Marello/Bundle/PaymentTermBundle/Resources/config/oro/actions.yml @@ -23,3 +23,156 @@ operations: '@and': - '@equal': [$.isDeleteAllowed, true] substitute_operation: DELETE + + oro_integration_deactivate: + label: oro.integration.deactivate + preactions: + - '@assign_constant_value': + attribute: $.paymentTermType + value: Marello\Bundle\PaymentTermBundle\Integration\PaymentTermChannelType::TYPE + preconditions: + '@and': + - '@not_equal': [$type, $.paymentTermType] + + oro_integration_delete: + label: oro.integration.delete + preactions: + - '@assign_constant_value': + attribute: $.paymentTermType + value: Marello\Bundle\PaymentTermBundle\Integration\PaymentTermChannelType::TYPE + preconditions: + '@and': + - '@not_equal': [$type, $.paymentTermType] + + marello_payment_term_integration_deactivate: + label: oro.integration.deactivate + extends: oro_integration_deactivate + for_all_entities: false + for_all_datagrids: false + replace: + - preactions + - preconditions + - frontend_options + preactions: + - '@assign_constant_value': + attribute: $.paymentTermType + value: Marello\Bundle\PaymentTermBundle\Integration\PaymentTermChannelType::TYPE + - '@call_service_method': + attribute: $.actionAllowed + service: oro_integration.utils.edit_mode + method: isSwitchEnableAllowed + method_parameters: [$.data.editMode] + - '@call_service_method': + attribute: $.methodIdentifier + service: marello_payment_term.method.identifier_generator.method + method: generateIdentifier + method_parameters: [$.data] + - '@call_service_method': + attribute: $.linkGrid + service: marello_payment.helper.filtered_datagrid_route + method: generate + method_parameters: [{'methodConfigs': $.methodIdentifier}] + preconditions: + '@and': + - '@marello_payment_method_has_enabled_payment_rules': + parameters: + method_identifier: $.methodIdentifier + - '@equal': [$type, $.paymentTermType] + - '@equal': [$.actionAllowed, true] + - '@equal': [$.data.enabled, true] + frontend_options: + confirmation: + title: marello.payment.integration.deactivate.title + okText: marello.payment.integration.deactivate.button.okText + message: marello.payment.integration.deactivate.message + message_parameters: + linkGrid: $.linkGrid + component: oroui/js/standart-confirmation + + marello_payment_term_integration_deactivate_without_rules: + label: oro.integration.deactivate + extends: marello_payment_term_integration_deactivate + for_all_entities: false + for_all_datagrids: false + replace: + - preconditions + - frontend_options + preconditions: + '@and': + - '@not': + - '@marello_payment_method_has_enabled_payment_rules': + parameters: + method_identifier: $.methodIdentifier + - '@equal': [$type, $.paymentTermType] + - '@equal': [$.actionAllowed, true] + - '@equal': [$.data.enabled, true] + frontend_options: ~ + + marello_payment_term_integration_delete: + label: oro.integration.delete + extends: oro_integration_delete + for_all_entities: false + for_all_datagrids: false + replace: + - preactions + - preconditions + - frontend_options + preactions: + - '@assign_constant_value': + attribute: $.paymentTermType + value: Marello\Bundle\PaymentTermBundle\Integration\PaymentTermChannelType::TYPE + - '@call_service_method': + service: oro_integration.utils.edit_mode + method: isEditAllowed + method_parameters: [$.data.editMode] + attribute: $.actionAllowed + - '@call_service_method': + attribute: $.methodIdentifier + service: marello_payment_term.method.identifier_generator.method + method: generateIdentifier + method_parameters: [$.data] + - '@call_service_method': + attribute: $.linkGrid + service: marello_payment.helper.filtered_datagrid_route + method: generate + method_parameters: [{'methodConfigs': $.methodIdentifier}] + preconditions: + '@and': + - '@marello_payment_method_has_payment_rules': + parameters: + method_identifier: $.methodIdentifier + - '@equal': [$type, $.paymentTermType] + - '@equal': [$.actionAllowed, true] + frontend_options: + confirmation: + title: marello.payment.integration.delete.title + okText: marello.payment.integration.delete.button.okText + message: marello.payment.integration.delete.message + message_parameters: + linkGrid: $.linkGrid + component: oroui/js/standart-confirmation + + marello_payment_term_integration_delete_without_rules: + label: oro.integration.delete + extends: marello_payment_term_integration_delete + for_all_entities: false + for_all_datagrids: false + replace: + - preconditions + - frontend_options + preconditions: + '@and': + - '@not': + - '@marello_payment_method_has_payment_rules': + parameters: + method_identifier: $.methodIdentifier + - '@equal': [$type, $.paymentTermType] + - '@equal': [$.actionAllowed, true] + frontend_options: + title: oro.action.delete_entity + confirmation: + title: oro.action.delete_entity + message: oro.action.delete_confirm + message_parameters: + entityLabel: $name + component: oroui/js/delete-confirmation diff --git a/src/Marello/Bundle/PaymentTermBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/PaymentTermBundle/Resources/config/oro/datagrids.yml index 091b503c4..617380eb3 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/PaymentTermBundle/Resources/config/oro/datagrids.yml @@ -44,7 +44,7 @@ datagrids: term: data_name: term default: - label: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC' + label: 'ASC' properties: id: ~ view_link: diff --git a/src/Marello/Bundle/PaymentTermBundle/Resources/config/services.yml b/src/Marello/Bundle/PaymentTermBundle/Resources/config/services.yml index aaf989304..3ba8e354f 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/PaymentTermBundle/Resources/config/services.yml @@ -1,3 +1,8 @@ +parameters: + marello_payment_term.method.identifier_prefix.payment_term: 'payment_term' + marello_payment_term.integration.channel.type: 'payment_term' + marello_payment_term.integration.transport.type: 'payment_term' + services: marello_payment_term.provider.payment_term: class: 'Marello\Bundle\PaymentTermBundle\Provider\PaymentTermProvider' @@ -7,11 +12,65 @@ services: marello_payment_term.provider.payment_term_delete_permission: class: 'Marello\Bundle\PaymentTermBundle\Provider\PaymentTermDeletePermissionProvider' + public: true arguments: - '@oro_entity.doctrine_helper' - '@marello_payment_term.provider.payment_term' marello_payment_term.action_permissions.payment_term: class: 'Marello\Bundle\PaymentTermBundle\Datagrid\PaymentTermActionPermissionProvider' + public: true arguments: - '@marello_payment_term.provider.payment_term_delete_permission' + + marello_payment_term.integration.channel: + class: 'Marello\Bundle\PaymentTermBundle\Integration\PaymentTermChannelType' + public: true + tags: + - { name: oro_integration.channel, type: payment_term } + + marello_payment_term.integration.transport: + class: 'Marello\Bundle\PaymentTermBundle\Integration\PaymentTermTransport' + public: false + tags: + - { name: oro_integration.transport, type: payment_term, channel_type: payment_term } + + marello_payment_term.method.identifier_generator.method: + parent: oro_integration.generator.prefixed_identifier_generator + public: true + arguments: + - '%marello_payment_term.method.identifier_prefix.payment_term%' + + marello_payment_term.factory.method: + class: 'Marello\Bundle\PaymentTermBundle\Method\Factory\PaymentTermMethodFromChannelFactory' + public: false + arguments: + - '@marello_payment_term.method.identifier_generator.method' + - '@oro_locale.helper.localization' + - '@oro_integration.provider.integration_icon' + + marello_payment_term.payment_method_provider.payment_term: + class: 'Marello\Bundle\PaymentTermBundle\Method\Provider\PaymentTermMethodProvider' + public: false + arguments: + - '%marello_payment_term.integration.channel.type%' + - '@oro_entity.doctrine_helper' + - '@marello_payment_term.factory.method' + tags: + - { name: marello_payment.payment_method_provider } + - { name: doctrine.orm.entity_listener, entity: 'Oro\Bundle\IntegrationBundle\Entity\Channel', event: postLoad } + + marello_payment_term.repository.payment_term_settings: + class: 'Marello\Bundle\PaymentTermBundle\Entity\Repository\PaymentTermSettingsRepository' + parent: oro_entity.abstract_repository + arguments: + - 'Marello\Bundle\PaymentTermBundle\Entity\PaymentTermSettings' + calls: + - [setAclHelper, ['@oro_security.acl_helper']] + + marello_payment_term.event_listener.payment_term_method_view: + class: 'Marello\Bundle\PaymentTermBundle\EventListener\PaymentTermMethodViewListener' + arguments: + - '@marello_payment_term.provider.payment_term' + tags: + - { name: kernel.event_listener, event: marello_payment.applicable_payment_method_view, method: onApplicablePaymentMethodView } diff --git a/src/Marello/Bundle/PaymentTermBundle/Resources/public/img/payment-term-logo.png b/src/Marello/Bundle/PaymentTermBundle/Resources/public/img/payment-term-logo.png new file mode 100644 index 000000000..bed373482 Binary files /dev/null and b/src/Marello/Bundle/PaymentTermBundle/Resources/public/img/payment-term-logo.png differ diff --git a/src/Marello/Bundle/PaymentTermBundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/PaymentTermBundle/Resources/translations/messages.en.yml index 3a2550566..281bb9c40 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Resources/translations/messages.en.yml +++ b/src/Marello/Bundle/PaymentTermBundle/Resources/translations/messages.en.yml @@ -24,3 +24,9 @@ marello: term.label: "Term" id.label: "ID" labels.label: "Label" + + settings: + label: 'Payment Term Settings' + labels.label: 'Label' + channel_type: + label: 'Payment Term' diff --git a/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/index.html.twig b/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/index.html.twig index 7c4307858..ee137e6cc 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/index.html.twig +++ b/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/index.html.twig @@ -5,7 +5,7 @@ {% set gridName = 'marello-paymentterm-grid' %} {% block navButtons %} - {% if resource_granted('marello_paymentterm_paymentterm_create') %} + {% if is_granted('marello_paymentterm_paymentterm_create') %} {{ UI.addButton({ 'path': path('marello_paymentterm_paymentterm_create'), 'entity_label': 'marello.paymentterm.entity_label'|trans diff --git a/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/update.html.twig b/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/update.html.twig index 0ed832082..bb4e501ce 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/update.html.twig +++ b/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} @@ -12,9 +13,9 @@ {{ parent() }} {{ UI.cancelButton(path('marello_paymentterm_paymentterm_index')) }} - {% if resource_granted('marello_paymentterm_paymentterm_update') %} + {% if is_granted('marello_paymentterm_paymentterm_update') %} {% set html = '' %} - {% if resource_granted('marello_paymentterm_paymentterm_view') %} + {% if is_granted('marello_paymentterm_paymentterm_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_paymentterm_paymentterm_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/view.html.twig b/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/view.html.twig index 3b6e96b68..90315bb5c 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/view.html.twig +++ b/src/Marello/Bundle/PaymentTermBundle/Resources/views/PaymentTerm/view.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:view.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% import 'OroEntityConfigBundle::macros.html.twig' as entityConfig %} diff --git a/src/Marello/Bundle/PaymentTermBundle/Tests/Functional/Controller/SystemConfigurationControllerTest.php b/src/Marello/Bundle/PaymentTermBundle/Tests/Functional/Controller/SystemConfigurationControllerTest.php index 10a76db04..30bc17a43 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Tests/Functional/Controller/SystemConfigurationControllerTest.php +++ b/src/Marello/Bundle/PaymentTermBundle/Tests/Functional/Controller/SystemConfigurationControllerTest.php @@ -2,7 +2,6 @@ namespace Marello\Bundle\PaymenttermBundle\Tests\Functional\Controller; -use Marello\Bundle\PaymentTermBundle\Form\Type\PaymentTermType; use Marello\Bundle\PaymentTermBundle\Tests\Functional\DataFixtures\LoadPaymentTermsData; use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Response; @@ -46,7 +45,7 @@ public function testSystemConfiguration() $crawler->filter('select[name="payment_config[marello_payment_term___default_payment_term][value]"]') ); - $form = $crawler->selectbutton('Save settings')->form(); + $form = $crawler->selectButton('Save settings')->form(); $selectField = $form['payment_config[marello_payment_term___default_payment_term][value]']; static::assertEquals( @@ -89,7 +88,7 @@ public function testSystemConfigurationUpdate() $result = $this->client->getResponse(); $this->assertHtmlResponseStatusCodeEquals($result, Response::HTTP_OK); - $form = $crawler->selectbutton('Save settings')->form(); + $form = $crawler->selectButton('Save settings')->form(); $selectField = $form['payment_config[marello_payment_term___default_payment_term][value]']; static::assertEquals( diff --git a/src/Marello/Bundle/PaymentTermBundle/Tests/Functional/DataFixtures/LoadPaymentTermIntegration.php b/src/Marello/Bundle/PaymentTermBundle/Tests/Functional/DataFixtures/LoadPaymentTermIntegration.php new file mode 100644 index 000000000..62632a7a5 --- /dev/null +++ b/src/Marello/Bundle/PaymentTermBundle/Tests/Functional/DataFixtures/LoadPaymentTermIntegration.php @@ -0,0 +1,54 @@ +setString('Payment Term'); + + $transport = new PaymentTermSettings(); + $transport->addLabel($label); + + $channel = new Channel(); + $channel->setType(PaymentTermChannelType::TYPE) + ->setName('Payment Term') + ->setEnabled(true) + ->setTransport($transport) + ->setOrganization($this->getOrganization()); + + $manager->persist($channel); + $manager->flush(); + + $this->setReference(self::REFERENCE_PAYMENT_TERM, $channel); + } + + + /** + * @return Organization + */ + private function getOrganization() + { + return $this->container->get('doctrine') + ->getRepository('OroOrganizationBundle:Organization') + ->getFirst(); + } +} diff --git a/src/Marello/Bundle/PaymentTermBundle/Tests/Unit/Provider/PaymentTermDeletePermissionProviderTest.php b/src/Marello/Bundle/PaymentTermBundle/Tests/Unit/Provider/PaymentTermDeletePermissionProviderTest.php index 652caf152..529d96833 100644 --- a/src/Marello/Bundle/PaymentTermBundle/Tests/Unit/Provider/PaymentTermDeletePermissionProviderTest.php +++ b/src/Marello/Bundle/PaymentTermBundle/Tests/Unit/Provider/PaymentTermDeletePermissionProviderTest.php @@ -38,6 +38,8 @@ public function setUp() } /** + * @param PaymentTerm $paymentTerm + * @param bool $expectedValue * @dataProvider isDeleteAllowedDataProvider */ public function testIsDeleteAllowed(PaymentTerm $paymentTerm, bool $expectedValue) diff --git a/src/Marello/Bundle/PdfBundle/Controller/DownloadController.php b/src/Marello/Bundle/PdfBundle/Controller/DownloadController.php new file mode 100644 index 000000000..0b86fa99f --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Controller/DownloadController.php @@ -0,0 +1,18 @@ +container->get('marello_pdf.request_handler.composite')->handle($request); + } +} diff --git a/src/Marello/Bundle/PdfBundle/DependencyInjection/CompilerPass/DocumentTableProviderPass.php b/src/Marello/Bundle/PdfBundle/DependencyInjection/CompilerPass/DocumentTableProviderPass.php new file mode 100644 index 000000000..0e671ce1e --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/DependencyInjection/CompilerPass/DocumentTableProviderPass.php @@ -0,0 +1,25 @@ +hasDefinition(self::PROVIDER_SERVICE_ID)) { + $definition = $container->getDefinition(self::PROVIDER_SERVICE_ID); + + $services = $container->findTaggedServiceIds(self::TAG_NAME); + foreach ($services as $serviceId => $tags) { + $definition->addMethodCall('addProvider', [new Reference($serviceId)]); + } + } + } +} diff --git a/src/Marello/Bundle/PdfBundle/DependencyInjection/CompilerPass/RenderParameterProviderPass.php b/src/Marello/Bundle/PdfBundle/DependencyInjection/CompilerPass/RenderParameterProviderPass.php new file mode 100644 index 000000000..22476e3f3 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/DependencyInjection/CompilerPass/RenderParameterProviderPass.php @@ -0,0 +1,25 @@ +hasDefinition(self::PROVIDER_SERVICE_ID)) { + $definition = $container->getDefinition(self::PROVIDER_SERVICE_ID); + + $services = $container->findTaggedServiceIds(self::TAG_NAME); + foreach ($services as $serviceId => $tags) { + $definition->addMethodCall('addProvider', [new Reference($serviceId)]); + } + } + } +} diff --git a/src/Marello/Bundle/PdfBundle/DependencyInjection/CompilerPass/RequestHandlersPass.php b/src/Marello/Bundle/PdfBundle/DependencyInjection/CompilerPass/RequestHandlersPass.php new file mode 100644 index 000000000..859fb59a0 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/DependencyInjection/CompilerPass/RequestHandlersPass.php @@ -0,0 +1,25 @@ +hasDefinition(self::PROVIDER_SERVICE_ID)) { + $definition = $container->getDefinition(self::PROVIDER_SERVICE_ID); + + $services = $container->findTaggedServiceIds(self::TAG_NAME); + foreach ($services as $serviceId => $tags) { + $definition->addMethodCall('addHandler', [new Reference($serviceId)]); + } + } + } +} diff --git a/src/Marello/Bundle/PdfBundle/DependencyInjection/Configuration.php b/src/Marello/Bundle/PdfBundle/DependencyInjection/Configuration.php new file mode 100644 index 000000000..06d825b73 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/DependencyInjection/Configuration.php @@ -0,0 +1,80 @@ +root(self::CONFIG_NAME); + + SettingsBuilder::append( + $rootNode, + [ + self::CONFIG_KEY_PAPER_SIZE => ['value' => self::PAPER_SIZE_A4], + self::CONFIG_KEY_LOCALIZATION => ['value' => null], + self::CONFIG_KEY_COMPANY_ADDRESS => ['value' => null], + self::CONFIG_KEY_COMPANY_EMAIL => ['value' => null], + self::CONFIG_KEY_LOGO => ['value' => null], + self::CONFIG_KEY_COMPANY_PHONE => ['value' => null], + self::CONFIG_KEY_COMPANY_BANK => ['value' => null], + self::CONFIG_KEY_COMPANY_COC => ['value' => null], + self::CONFIG_KEY_EMAIL_WORKFLOW_TRANSITION => ['value' => null], + self::CONFIG_KEY_EMAIL_SENDER_NAME => ['value' => null], + self::CONFIG_KEY_EMAIL_SENDER_EMAIL => ['value' => null], + self::CONFIG_KEY_EMAIL_BCC => ['value' => null], + ] + ); + + return $treeBuilder; + } + + /** + * @param string $name + * @return string + */ + public static function getConfigKeyByName(string $name): string + { + return TreeUtils::getConfigKey(self::CONFIG_NAME, $name, ConfigManager::SECTION_MODEL_SEPARATOR); + } + + /** + * @param string $name + * @return string + */ + public static function getFieldKeyByName(string $name): string + { + return TreeUtils::getConfigKey(self::CONFIG_NAME, $name, ConfigManager::SECTION_VIEW_SEPARATOR); + } +} diff --git a/src/Marello/Bundle/PdfBundle/DependencyInjection/MarelloPdfExtension.php b/src/Marello/Bundle/PdfBundle/DependencyInjection/MarelloPdfExtension.php new file mode 100644 index 000000000..424d1868c --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/DependencyInjection/MarelloPdfExtension.php @@ -0,0 +1,30 @@ +processConfiguration($configuration, $configs); + + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.yml'); + + $container->prependExtensionConfig(Configuration::CONFIG_NAME, $config); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Exception/PaperSizeNotSetException.php b/src/Marello/Bundle/PdfBundle/Exception/PaperSizeNotSetException.php new file mode 100644 index 000000000..ba09e65b4 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Exception/PaperSizeNotSetException.php @@ -0,0 +1,11 @@ +passedOptions = $options; + } + + public function create(array $options = []) + { + return new Mpdf($this->getOptions($options)); + } + + protected function getOptions(array $options = []) + { + if ($this->options === null) { + $this->options = array_merge($this->defaultOptions, $this->passedOptions); + } + + return array_merge($this->options, $options); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Form/Configurator/DocumentConfigurator.php b/src/Marello/Bundle/PdfBundle/Form/Configurator/DocumentConfigurator.php new file mode 100644 index 000000000..9e1f8f84f --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Form/Configurator/DocumentConfigurator.php @@ -0,0 +1,77 @@ +configHandler = $configHandler; + } + + /** + * @param FormBuilderInterface $builder + */ + public function buildForm(FormBuilderInterface $builder): void + { + $builder->addEventListener( + FormEvents::PRE_SET_DATA, + function (FormEvent $event) { + if ($event->getData() === null) { + return; + } + + $form = $event->getForm(); + $data = $event->getData(); + $configManager = $this->configHandler->getConfigManager(); + if ($data[PdfConfig::getFieldKeyByName(PdfConfig::CONFIG_KEY_LOCALIZATION)]['value'] === null) { + $data[PdfConfig::getFieldKeyByName(PdfConfig::CONFIG_KEY_LOCALIZATION)]['value'] = + $configManager->get(LocaleConfig::getConfigKeyByName(LocaleConfig::DEFAULT_LOCALIZATION)); + $event->setData($data); + } + + $this->setEnabledLocalizations($form, $configManager); + } + ); + } + + /** + * @param FormInterface $form + * @param ConfigManager $configManager + */ + private function setEnabledLocalizations(FormInterface $form, ConfigManager $configManager): void + { + $form = $form->get(PdfConfig::getFieldKeyByName(PdfConfig::CONFIG_KEY_LOCALIZATION)); + + $options = $form->get('value') + ->getConfig() + ->getOptions(); + + $options[LocaleConfig::ENABLED_LOCALIZATIONS] = $configManager->get( + LocaleConfig::getConfigKeyByName(LocaleConfig::ENABLED_LOCALIZATIONS) + ); + + $form->add('value', LocalizationSelectionType::class, $options); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Form/Type/WorkflowTransitionSelectType.php b/src/Marello/Bundle/PdfBundle/Form/Type/WorkflowTransitionSelectType.php new file mode 100644 index 000000000..9e5f21f97 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Form/Type/WorkflowTransitionSelectType.php @@ -0,0 +1,59 @@ +workflowManager = $workflowManager; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired('workflow') + ->setDefault('choices', function (Options $options) { + return $this->getWorkflowStepChoices($options['workflow']); + }) + ->setDefaults([ + 'required' => false, + ]) + ; + } + + protected function getWorkflowStepChoices($workflowName) + { + $workflow = $this->getWorkflow($workflowName); + $configuration = $workflow->getDefinition()->getConfiguration(); + + $choices = []; + foreach (array_keys($configuration['transitions']) as $transition) { + $choices[$this->formatTransitionName($transition)] = $transition; + } + + return $choices; + } + + protected function formatTransitionName($transition) + { + return ucfirst(str_replace('_', ' ', $transition)); + } + + protected function getWorkflow($workflowName) + { + return $this->workflowManager->getWorkflow($workflowName); + } + + public function getParent() + { + return ChoiceType::class; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Lib/View/EmptyDisplayLine.php b/src/Marello/Bundle/PdfBundle/Lib/View/EmptyDisplayLine.php new file mode 100644 index 000000000..922236697 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Lib/View/EmptyDisplayLine.php @@ -0,0 +1,26 @@ +fields[$field] = [null]; + } + } + + public function offsetExists($key) + { + return isset($this->fields[$key]) || array_key_exists($key, $this->fields); + } + + public function offsetGet($key) + { + if ($this->offsetExists($key) === false) { + throw $this->createInvalidArgumentException($key); + } + + return $this->fields[$key]; + } + + public function offsetSet($key, $value) + { + if ($this->offsetExists($key) === false) { + throw $this->createInvalidArgumentException($key); + } + + if (!is_array($value)) { + $value = [$value]; + } + + $this->fields[$key] = $value; + $this->recalculateHeight(); + } + + public function offsetUnset($key) + { + if ($this->offsetExists($key) === false) { + throw $this->createInvalidArgumentException($key); + } + + $this->fields[$key] = [null]; + } + + public function getHeight() + { + return $this->height; + } + + public function getDisplayLines() + { + $lines = []; + for ($i = 0; $i < $this->height; $i++) { + $line = []; + + foreach (array_keys($this->fields) as $key) { + $line[$key] = $this->fields[$key][$i] ?? null; + } + + $lines[] = $line; + } + + return $lines; + } + + public function isEmpty() + { + foreach ($this->fields as $field) { + if (count($field) > 1 || reset($field) !== null) { + return false; + } + } + + return true; + } + + protected function recalculateHeight() + { + $this->height = max(array_map('count', $this->fields)); + } + + private function createInvalidArgumentException($key) + { + return new \InvalidArgumentException(sprintf( + 'Key "%s" does not exist in %s object with keys %s', + $key, + __CLASS__, + implode(', ', array_keys($this->fields)) + )); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Lib/View/Table.php b/src/Marello/Bundle/PdfBundle/Lib/View/Table.php new file mode 100644 index 000000000..4fc28c6a2 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Lib/View/Table.php @@ -0,0 +1,90 @@ +maxHeight = $maxHeight; + $this->headerHeight = $headerHeight; + $this->footerHeight = $footerHeight; + $this->lines = new ArrayCollection(); + } + + public function disableHeader() + { + $this->headerEnabled = false; + + return $this; + } + + public function disableFooter() + { + $this->footerEnabled = false; + + return $this; + } + + public function getLines() + { + $lines = clone $this->lines; + + for ($i = $this->getHeight(); $i < $this->getMaxHeight(); $i++) { + $lines[] = new EmptyLine(); + } + + return $lines; + } + + public function addLine(Line $line) + { + $this->lines[] = $line; + + $this->height += $line->getHeight(); + + return $this; + } + + public function getHeight() + { + return $this->height; + } + + public function getMaxHeight() + { + $height = $this->maxHeight; + if ($this->headerEnabled) { + $height -= $this->headerHeight; + } + if ($this->footerEnabled) { + $height -= $this->footerHeight; + } + + return $height; + } + + public function fitsLine(Line $line) + { + $height = $this->getHeight(); + $height += $line->getHeight(); + + return $height <= $this->getMaxHeight(); + } +} diff --git a/src/Marello/Bundle/PdfBundle/MarelloPdfBundle.php b/src/Marello/Bundle/PdfBundle/MarelloPdfBundle.php new file mode 100644 index 000000000..0bbae1bf8 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/MarelloPdfBundle.php @@ -0,0 +1,21 @@ +addCompilerPass(new RenderParameterProviderPass()); + $container->addCompilerPass(new DocumentTableProviderPass()); + $container->addCompilerPass(new RequestHandlersPass()); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Provider/DocumentTableProvider.php b/src/Marello/Bundle/PdfBundle/Provider/DocumentTableProvider.php new file mode 100644 index 000000000..4f2150097 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Provider/DocumentTableProvider.php @@ -0,0 +1,24 @@ +providers[] = $provider; + } + + public function getTables($entity) + { + foreach ($this->providers as $provider) { + if ($provider->supports($entity)) { + return $provider->getTables($entity); + } + } + + return []; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Provider/Render/ConfigValuesProvider.php b/src/Marello/Bundle/PdfBundle/Provider/Render/ConfigValuesProvider.php new file mode 100644 index 000000000..4480076e9 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Provider/Render/ConfigValuesProvider.php @@ -0,0 +1,43 @@ +config = $config; + $this->parameters = $parameters; + } + + public function supports($entity, array $options) + { + return true; + } + + public function getParams($entity, array $options) + { + $params = []; + foreach ($this->parameters as $key => $configKey) { + $params[$key] = $this->getConfigValue($configKey, $entity, $options); + } + + return $params; + } + + protected function getConfigValue($configKey, $entity, array $options) + { + $scopeIdentifier = $options[self::SCOPE_IDENTIFIER_KEY] ?? null; + + return $this->config->get($configKey, false, false, $scopeIdentifier); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Provider/Render/EntityProvider.php b/src/Marello/Bundle/PdfBundle/Provider/Render/EntityProvider.php new file mode 100644 index 000000000..15af87c77 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Provider/Render/EntityProvider.php @@ -0,0 +1,18 @@ + $entity]; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Provider/Render/LocalizationProvider.php b/src/Marello/Bundle/PdfBundle/Provider/Render/LocalizationProvider.php new file mode 100644 index 000000000..eb1430e55 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Provider/Render/LocalizationProvider.php @@ -0,0 +1,118 @@ +config = $config; + $this->doctrine = $doctrine; + $this->localizationParameterName = $localizationParameterName; + } + + /** + * {@inheritdoc} + * @param $entity + * @param array $options + * @return bool + */ + public function supports($entity, array $options) + { + return true; + } + + /** + * {@inheritdoc} + * @param $entity + * @param array $options + * @return array + */ + public function getParams($entity, array $options) + { + $params = []; + $localizationId = (int)$this->getConfigValue($this->localizationParameterName, $options); + $localization = $this->doctrine + ->getManagerForClass(Localization::class) + ->find(Localization::class, $localizationId); + + if (null === $localization) { + $localizationAwareEntity = null; + if ($entity instanceof LocalizationAwareInterface) { + $localizationAwareEntity = $entity; + } elseif ($entity instanceof SalesChannelAwareInterface) { + $localizationAwareEntity = $entity->getSalesChannel(); + } + + if ($this->chainLocalizationProvider && null !== $localizationAwareEntity) { + $localization = $this->chainLocalizationProvider->getLocalization($localizationAwareEntity); + } + } + + if ($localization) { + $params['localization'] = $localization; + $params['language'] = $localization->getLanguageCode(); + $params['locale'] = $localization->getLanguageCode(); + } + + return $params; + } + + /** + * {@inheritdoc} + * @param $configKey + * @param array $options + * @return mixed + */ + protected function getConfigValue($configKey, array $options) + { + $scopeIdentifier = $options[self::SCOPE_IDENTIFIER_KEY] ?? null; + + return $this->config->get($configKey, false, false, $scopeIdentifier); + } + + /** + * {@inheritdoc} + * @param EntityLocalizationProviderInterface $chainProvider + */ + public function setChainLocalizationProvider(EntityLocalizationProviderInterface $chainProvider) + { + $this->chainLocalizationProvider = $chainProvider; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Provider/RenderParameterProviderInterface.php b/src/Marello/Bundle/PdfBundle/Provider/RenderParameterProviderInterface.php new file mode 100644 index 000000000..7fd67e2b9 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Provider/RenderParameterProviderInterface.php @@ -0,0 +1,9 @@ +providers[] = $provider; + } + + public function getParams($entity, array $options = []) + { + $params = []; + + foreach ($this->providers as $provider) { + if ($provider->supports($entity, $options)) { + $params = array_merge($params, $provider->getParams($entity, $options)); + } + } + + return $params; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Provider/TableProviderInterface.php b/src/Marello/Bundle/PdfBundle/Provider/TableProviderInterface.php new file mode 100644 index 000000000..64f7d6d20 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Provider/TableProviderInterface.php @@ -0,0 +1,10 @@ +config = $config; + $this->sizeConfig = [ + self::KEY_MAX_HEIGHT => $maxHeight, + self::KEY_MAX_TEXT_WIDTH => $maxTextWidth, + self::KEY_FIRST_PAGE_INFO => $firstPageInfoHeight, + self::KEY_LAST_PAGE_INFO => $lastPageInfoHeight, + ]; + } + + public function getMaxHeight(SalesChannel $salesChannel) + { + return $this->getValue($salesChannel, self::KEY_MAX_HEIGHT); + } + + public function getMaxTextWidth(SalesChannel $salesChannel) + { + return $this->getValue($salesChannel, self::KEY_MAX_TEXT_WIDTH); + } + + public function getFirstPageInfoHeight(SalesChannel $salesChannel) + { + return $this->getValue($salesChannel, self::KEY_FIRST_PAGE_INFO); + } + + public function getLastPageInfoHeight(SalesChannel $salesChannel) + { + return $this->getValue($salesChannel, self::KEY_LAST_PAGE_INFO); + } + + protected function getValue(SalesChannel $salesChannel, $key) + { + $compoundValue = $this->sizeConfig[$key]; + if (!is_array($compoundValue)) { + return $compoundValue; + } + + if (!isset($compoundValue[$this->getPaperSize($salesChannel)])) { + throw new PaperSizeNotSetException($this->getPaperSize($salesChannel), $key); + } + + return $compoundValue[$this->getPaperSize($salesChannel)]; + } + + private function getPaperSize(SalesChannel $salesChannel) + { + if (!isset($this->paperSizes[$salesChannel->getId()])) { + $this->paperSizes[$salesChannel->getId()] = $this->fetchPaperSize($salesChannel); + } + + return $this->paperSizes[$salesChannel->getId()]; + } + + private function fetchPaperSize(SalesChannel $salesChannel) + { + return $this->getConfigValue(Configuration::CONFIG_KEY_PAPER_SIZE, $salesChannel); + } + + private function getConfigValue($configKey, SalesChannel $salesChannel) + { + $key = sprintf('%s.%s', Configuration::CONFIG_NAME, $configKey); + + return $this->config->get($key, false, false, $salesChannel); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Renderer/HtmlRenderer.php b/src/Marello/Bundle/PdfBundle/Renderer/HtmlRenderer.php new file mode 100644 index 000000000..117611cd6 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Renderer/HtmlRenderer.php @@ -0,0 +1,39 @@ +pdfWriterFactory = $pdfWriterFactory; + } + + public function render($input) + { + $writer = $this->getWriter(); + + $writer->WriteHTML($input); + + return $writer->Output(null, Destination::STRING_RETURN); + } + + public function renderToFile($input, $filename) + { + $writer = $this->getWriter(); + + $writer->WriteHTML($input); + + return $writer->Output($filename, Destination::FILE); + } + + protected function getWriter() + { + return $this->pdfWriterFactory->create(); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Renderer/TwigRenderer.php b/src/Marello/Bundle/PdfBundle/Renderer/TwigRenderer.php new file mode 100644 index 000000000..a2c3a2587 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Renderer/TwigRenderer.php @@ -0,0 +1,38 @@ +renderer = $renderer; + $this->twig = $twig; + } + + public function render($template, array $params = []) + { + return $this->renderer->render($this->renderTwig($template, $params)); + } + + public function renderToFile($template, array $params, $filename) + { + return $this->renderer->renderToFile($this->renderTwig($template, $params), $filename); + } + + public function renderBase64($template, array $params = []) + { + return base64_encode($this->render($template, $params)); + } + + protected function renderTwig($template, array $params = []) + { + return $this->twig->render($template, $params); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Request/CompositePdfRequestHandler.php b/src/Marello/Bundle/PdfBundle/Request/CompositePdfRequestHandler.php new file mode 100644 index 000000000..0d7065ee8 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Request/CompositePdfRequestHandler.php @@ -0,0 +1,43 @@ +handlers[] = $handler; + } + + /** + * @inheritDoc + */ + public function isApplicable(Request $request) + { + return true; + } + + /** + * @inheritDoc + */ + public function handle(Request $request) + { + foreach ($this->handlers as $handler) { + if ($handler->isApplicable($request)) { + return $handler->handle($request); + } + } + + return null; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Request/PdfRequestHandlerInterface.php b/src/Marello/Bundle/PdfBundle/Request/PdfRequestHandlerInterface.php new file mode 100644 index 000000000..f25aa35b1 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Request/PdfRequestHandlerInterface.php @@ -0,0 +1,21 @@ + + +
+{% endblock header %} + +{% block footer %} + +{% endblock footer %} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/DependencyInjection/CompilerPass/DocumentTableProviderPassTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/DependencyInjection/CompilerPass/DocumentTableProviderPassTest.php new file mode 100644 index 000000000..6ec600a80 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/DependencyInjection/CompilerPass/DocumentTableProviderPassTest.php @@ -0,0 +1,122 @@ +containerBuilder = $this + ->getMockBuilder(ContainerBuilder::class) + ->getMock(); + + $this->compilerPass = new DocumentTableProviderPass(); + } + + public function testProcessRegistryDoesNotExist() + { + $this->containerBuilder + ->expects($this->once()) + ->method('hasDefinition') + ->with(DocumentTableProviderPass::PROVIDER_SERVICE_ID) + ->willReturn(false) + ; + + $this->containerBuilder + ->expects($this->never()) + ->method('getDefinition') + ; + + $this->containerBuilder + ->expects($this->never()) + ->method('findTaggedServiceIds') + ; + + $this->compilerPass->process($this->containerBuilder); + } + + public function testProcessNoTaggedServicesFound() + { + $this->containerBuilder + ->expects($this->once()) + ->method('hasDefinition') + ->with(DocumentTableProviderPass::PROVIDER_SERVICE_ID) + ->willReturn(true) + ; + + $this->containerBuilder + ->expects($this->once()) + ->method('findTaggedServiceIds') + ->with(DocumentTableProviderPass::TAG_NAME) + ->willReturn([]) + ; + + $this->containerBuilder + ->expects($this->once()) + ->method('getDefinition') + ->with(DocumentTableProviderPass::PROVIDER_SERVICE_ID) + ; + + $this->compilerPass->process($this->containerBuilder); + } + + public function testProcessWithTaggedServices() + { + $this->containerBuilder + ->expects($this->once()) + ->method('hasDefinition') + ->with(DocumentTableProviderPass::PROVIDER_SERVICE_ID) + ->willReturn(true); + + $registryServiceDefinition = $this->createMock(Definition::class); + + $this->containerBuilder + ->expects($this->once()) + ->method('getDefinition') + ->with(DocumentTableProviderPass::PROVIDER_SERVICE_ID) + ->willReturn($registryServiceDefinition); + + $taggedServices = [ + 'service.name.1' => [[]], + 'service.name.2' => [[]], + 'service.name.3' => [[]], + 'service.name.4' => [[]], + ]; + + $this->containerBuilder + ->expects($this->once()) + ->method('findTaggedServiceIds') + ->with(DocumentTableProviderPass::TAG_NAME) + ->willReturn($taggedServices); + + $registryServiceDefinition + ->expects($this->exactly(4)) + ->method('addMethodCall') + ->withConsecutive( + ['addProvider', [new Reference('service.name.1')]], + ['addProvider', [new Reference('service.name.2')]], + ['addProvider', [new Reference('service.name.3')]], + ['addProvider', [new Reference('service.name.4')]] + ); + + $this->compilerPass->process($this->containerBuilder); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/DependencyInjection/CompilerPass/RenderParameterProviderPassTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/DependencyInjection/CompilerPass/RenderParameterProviderPassTest.php new file mode 100644 index 000000000..9c324aa3b --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/DependencyInjection/CompilerPass/RenderParameterProviderPassTest.php @@ -0,0 +1,122 @@ +containerBuilder = $this + ->getMockBuilder(ContainerBuilder::class) + ->getMock(); + + $this->compilerPass = new RenderParameterProviderPass(); + } + + public function testProcessRegistryDoesNotExist() + { + $this->containerBuilder + ->expects($this->once()) + ->method('hasDefinition') + ->with(RenderParameterProviderPass::PROVIDER_SERVICE_ID) + ->willReturn(false) + ; + + $this->containerBuilder + ->expects($this->never()) + ->method('getDefinition') + ; + + $this->containerBuilder + ->expects($this->never()) + ->method('findTaggedServiceIds') + ; + + $this->compilerPass->process($this->containerBuilder); + } + + public function testProcessNoTaggedServicesFound() + { + $this->containerBuilder + ->expects($this->once()) + ->method('hasDefinition') + ->with(RenderParameterProviderPass::PROVIDER_SERVICE_ID) + ->willReturn(true) + ; + + $this->containerBuilder + ->expects($this->once()) + ->method('findTaggedServiceIds') + ->with(RenderParameterProviderPass::TAG_NAME) + ->willReturn([]) + ; + + $this->containerBuilder + ->expects($this->once()) + ->method('getDefinition') + ->with(RenderParameterProviderPass::PROVIDER_SERVICE_ID) + ; + + $this->compilerPass->process($this->containerBuilder); + } + + public function testProcessWithTaggedServices() + { + $this->containerBuilder + ->expects($this->once()) + ->method('hasDefinition') + ->with(RenderParameterProviderPass::PROVIDER_SERVICE_ID) + ->willReturn(true); + + $registryServiceDefinition = $this->createMock(Definition::class); + + $this->containerBuilder + ->expects($this->once()) + ->method('getDefinition') + ->with(RenderParameterProviderPass::PROVIDER_SERVICE_ID) + ->willReturn($registryServiceDefinition); + + $taggedServices = [ + 'service.name.1' => [[]], + 'service.name.2' => [[]], + 'service.name.3' => [[]], + 'service.name.4' => [[]], + ]; + + $this->containerBuilder + ->expects($this->once()) + ->method('findTaggedServiceIds') + ->with(RenderParameterProviderPass::TAG_NAME) + ->willReturn($taggedServices); + + $registryServiceDefinition + ->expects($this->exactly(4)) + ->method('addMethodCall') + ->withConsecutive( + ['addProvider', [new Reference('service.name.1')]], + ['addProvider', [new Reference('service.name.2')]], + ['addProvider', [new Reference('service.name.3')]], + ['addProvider', [new Reference('service.name.4')]] + ); + + $this->compilerPass->process($this->containerBuilder); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/DependencyInjection/MarelloPdfExtensionTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/DependencyInjection/MarelloPdfExtensionTest.php new file mode 100644 index 000000000..bfc738291 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/DependencyInjection/MarelloPdfExtensionTest.php @@ -0,0 +1,40 @@ +extension = new MarelloPdfExtension(); + } + + protected function tearDown() + { + unset($this->extension); + } + + public function testLoad() + { + $this->loadExtension($this->extension); + + $this->assertDefinitionsLoaded([ + 'marello_pdf.factory.pdf_writer', + 'marello_pdf.renderer.html', + 'marello_pdf.renderer.twig', + 'marello_pdf.provider.render_parameters', + 'marello_pdf.param_provider.entity', + 'marello_pdf.param_provider.saleschannel_config_values', + 'marello_pdf.provider.document_table', + 'marello_pdf.twig_extension.document_table', + ]); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Factory/PdfWriterFactoryTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Factory/PdfWriterFactoryTest.php new file mode 100644 index 000000000..d3338da87 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Factory/PdfWriterFactoryTest.php @@ -0,0 +1,24 @@ +factory = new PdfWriterFactory(); + } + + public function testCreate() + { + $writer = $this->factory->create(); + + $this->assertInstanceOf(Mpdf::class, $writer); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/EmptyDisplayLineTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/EmptyDisplayLineTest.php new file mode 100644 index 000000000..5ac626fd6 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/EmptyDisplayLineTest.php @@ -0,0 +1,40 @@ +line = new EmptyDisplayLine(); + } + + public function testOffsetGet() + { + $this->assertNull($this->line['not set value']); + } + + public function testOffsetExists() + { + $this->assertTrue(isset($this->line['not set value'])); + } + + public function testOffsetSet() + { + $this->expectException(\BadMethodCallException::class); + + $this->line['not set value'] = 'set value'; + } + + public function testOffsetUnset() + { + $this->expectException(\BadMethodCallException::class); + + unset($this->line['set value']); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/EmptyLineTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/EmptyLineTest.php new file mode 100644 index 000000000..4af5c0280 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/EmptyLineTest.php @@ -0,0 +1,66 @@ +line = new EmptyLine(); + } + + public function testOffsetSet() + { + $this->expectException(\BadMethodCallException::class); + + $this->line['not set value'] = 'set value'; + } + + public function testOffsetGet() + { + $value = $this->line['not set value']; + + $this->assertTrue(is_array($value)); + $this->assertCount(1, $value); + $this->assertNull(reset($value)); + } + + public function testOffsetUnset() + { + $this->expectException(\BadMethodCallException::class); + + unset($this->line['not set value']); + } + + public function testOffsetExists() + { + $this->assertTrue(isset($this->line['not set value'])); + } + + public function testGetDisplayLines() + { + $displayLines = $this->line->getDisplayLines(); + + $this->assertTrue(is_array($displayLines)); + $this->assertCount(1, $displayLines); + foreach ($displayLines as $line) { + $this->assertInstanceOf(EmptyDisplayLine::class, $line); + } + } + + public function testGetHeight() + { + $this->assertEquals(1, $this->line->getHeight()); + } + + public function testIsEmpty() + { + $this->assertTrue($this->line->isEmpty()); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/LineTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/LineTest.php new file mode 100644 index 000000000..ce2da5285 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/LineTest.php @@ -0,0 +1,191 @@ +line = new Line([ + 'test-field-1', + 'test-field-2', + ]); + } + + public function testOffsetExists() + { + $this->line['test-field-1'] = ['test value 1']; + + $this->assertTrue(isset($this->line['test-field-1'])); + $this->assertTrue(isset($this->line['test-field-2'])); + $this->assertFalse(isset($this->line['test-field-3'])); + } + + public function testOffsetGet() + { + $this->line['test-field-1'] = ['test value 1']; + + $this->assertEquals(['test value 1'], $this->line['test-field-1']); + $this->assertEquals([null], $this->line['test-field-2']); + + $this->expectException(\InvalidArgumentException::class); + $this->line['test-field-3']; + } + + public function testOffsetSet() + { + $this->line['test-field-1'] = ['test value 1']; + + $this->assertEquals(['test value 1'], $this->line['test-field-1']); + + $this->expectException(\InvalidArgumentException::class); + $this->line['test-field-3'] = ['test value 3']; + } + + public function testOffsetUnset() + { + $this->line['test-field-1'] = ['test value 1']; + + unset($this->line['test-field-1']); + + $this->assertEquals([null], $this->line['test-field-1']); + + $this->expectException(\InvalidArgumentException::class); + unset($this->line['test-field-3']); + } + + /** + * @param $fieldValues + * @param $returnValue + * + * @dataProvider getDisplayLinesProvider + */ + public function testGetDisplayLines($fieldValues, $returnValue) + { + foreach ($fieldValues as $key => $value) { + $this->line[$key] = $value; + } + + $displayLines = $this->line->getDisplayLines(); + $this->assertTrue(is_array($displayLines)); + $this->assertCount(count($returnValue), $displayLines); + + foreach ($returnValue as $i => $lineValue) { + foreach ($lineValue as $key => $fieldValue) { + $this->assertEquals($fieldValue, $displayLines[$i][$key]); + } + } + } + + /** + * @return array + */ + public function getDisplayLinesProvider() + { + return [ + 'empty' => [ + 'fieldValues' => [], + 'returnValue' => [ + ['test-field-1' => null, 'test-field-2' => null], + ], + ], + 'single' => [ + 'fieldValues' => [ + 'test-field-1' => ['test value 1'], + ], + 'returnValue' => [ + ['test-field-1' => 'test value 1', 'test-field-2' => null], + ], + ], + 'multiple' => [ + 'fieldValues' => [ + 'test-field-1' => ['test value line 1', 'test value line 2'], + ], + 'returnValue' => [ + ['test-field-1' => 'test value line 1', 'test-field-2' => null], + ['test-field-1' => 'test value line 2', 'test-field-2' => null], + ], + ], + ]; + } + + /** + * @param $fieldValues + * @param $height + * + * @dataProvider getHeightProvider + */ + public function testGetHeight($fieldValues, $height) + { + foreach ($fieldValues as $field => $value) { + $this->line[$field] = $value; + } + + $this->assertEquals($height, $this->line->getHeight()); + } + + /** + * @return array + */ + public function getHeightProvider() + { + return [ + 'empty' => [ + 'fieldValues' => [], + 'height' => 1, + ], + 'single' => [ + 'fieldValues' => [ + 'test-field-1' => ['test line 1'], + ], + 'height' => 1, + ], + 'multiple' => [ + 'fieldValues' => [ + 'test-field-1' => ['test line 1', 'test line 2'], + ], + 'height' => 2, + ], + ]; + } + + /** + * @param $fieldValues + * @param $isEmpty + * + * @dataProvider isEmptyProvider + */ + public function testIsEmpty($fieldValues, $isEmpty) + { + foreach ($fieldValues as $field => $value) { + $this->line[$field] = $value; + } + + $this->assertEquals($isEmpty, $this->line->isEmpty()); + } + + /** + * @return array + */ + public function isEmptyProvider() + { + return [ + 'empty' => [ + 'fieldValues' => [], + 'isEmpty' => true, + ], + 'not empty' => [ + 'fieldValues' => [ + 'test-field-1' => 'Value 1', + 'test-field-2' => 'Value 2', + ], + 'isEmpty' => false, + ], + ]; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/TableTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/TableTest.php new file mode 100644 index 000000000..1d7bbc518 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Lib/View/TableTest.php @@ -0,0 +1,178 @@ +table = new Table( + self::MAX_HEIGHT, + self::HEADER_HEIGHT, + self::FOOTER_HEIGHT + ); + } + + public function testDisableHeader() + { + $this->table->disableHeader(); + + $this->assertEquals(self::MAX_HEIGHT - self::FOOTER_HEIGHT, $this->table->getMaxHeight()); + } + + public function testDisableFooter() + { + $this->table->disableFooter(); + + $this->assertEquals(self::MAX_HEIGHT - self::HEADER_HEIGHT, $this->table->getMaxHeight()); + } + + /** + * @param $lines + * + * @dataProvider addLineProvider + */ + public function testAddLine($lines) + { + $line = new Line(['test-field-1']); + $line['test-field-1'] = $lines; + + $this->table->addLine($line); + + $tableLines = $this->table->getLines()->filter(function ($x) { return $x->isEmpty() === false; }); + + $this->assertCount(1, $tableLines); + $this->assertEquals($line, $tableLines->first()); + } + + /** + * @param $lines + * + * @dataProvider addLineProvider + */ + public function testGetHeight($lines) + { + $height = $this->table->getHeight(); + + $line = new Line(['test-field-1']); + $line['test-field-1'] = $lines; + + $this->table->addLine($line); + + $this->assertEquals($height + count($lines), $this->table->getHeight()); + } + + public function addLineProvider() + { + return [ + 'empty' => [ + 'lines' => [], + ], + 'single' => [ + 'lines' => ['test'], + ], + 'multiple' => [ + 'lines' => ['test line 1', 'test line 2'], + ], + ]; + } + + public function testGetMaxHeight() + { + $this->assertEquals(self::MAX_HEIGHT - self::HEADER_HEIGHT - self::FOOTER_HEIGHT, $this->table->getMaxHeight()); + $this->table->disableHeader(); + $this->table->disableFooter(); + $this->assertEquals(self::MAX_HEIGHT, $this->table->getMaxHeight()); + } + + /** + * @param $tableLines + * @param $addLine + * @param $isValid + * + * @dataProvider addFitsLineProvider + */ + public function testFitsLine($tableLines, $addLine, $isValid) + { + foreach ($tableLines as $line) { + $tableLine = new Line(['test-field']); + $tableLine['test-field'] = $line; + + $this->table->addLine($tableLine); + } + + $line = new Line(['test-field']); + $line['test-field'] = $addLine; + + $this->assertEquals($isValid, $this->table->fitsLine($line)); + } + + public function addFitsLineProvider() + { + $singleLine = ['test add 1']; + $multiLine = ['test add 1', 'test add 2']; + $tooManyLine = ['test add 1', 'test add 2', 'test add 3', 'test add 4', 'test add 5']; + + $emptyTable = []; + $nonEmptyTable = ['test table 1', 'test table 2']; + $fullTable = ['test table 1', 'test table 2', 'test table 3', 'test table 4']; + + return [ + 'empty add single' => [ + 'tableLines' => $emptyTable, + 'addLine' => $singleLine, + 'isValid' => true, + ], + 'empty add multiple' => [ + 'tableLines' => $emptyTable, + 'addLine' => $multiLine, + 'isValid' => true, + ], + 'empty add too many' => [ + 'tableLines' => $emptyTable, + 'addLine' => $tooManyLine, + 'isValid' => false, + ], + 'nonempty add single' => [ + 'tableLines' => $nonEmptyTable, + 'addLine' => $singleLine, + 'isValid' => true, + ], + 'nonempty add multiple' => [ + 'tableLines' => $nonEmptyTable, + 'addLine' => $multiLine, + 'isValid' => true, + ], + 'nonempty add too many' => [ + 'tableLines' => $nonEmptyTable, + 'addLine' => $tooManyLine, + 'isValid' => false, + ], + 'full add single' => [ + 'tableLines' => $fullTable, + 'addLine' => $singleLine, + 'isValid' => false, + ], + 'full add multiple' => [ + 'tableLines' => $fullTable, + 'addLine' => $multiLine, + 'isValid' => false, + ], + 'full add too many' => [ + 'tableLines' => $fullTable, + 'addLine' => $tooManyLine, + 'isValid' => false, + ], + ]; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/DocumentTableProviderTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/DocumentTableProviderTest.php new file mode 100644 index 000000000..317959c53 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/DocumentTableProviderTest.php @@ -0,0 +1,62 @@ +provider = new DocumentTableProvider(); + } + + public function testGetTablesSupported() + { + /** @var TableProviderInterface|\PHPUnit_Framework_MockObject_MockObject $workingProvider */ + $workingProvider = $this->createMock(TableProviderInterface::class); + $workingProvider->expects($this->once()) + ->method('supports') + ->willReturn(true) + ; + $workingProvider->expects($this->once()) + ->method('getTables') + ->willReturn(new Table(20, 5, 4)) + ; + + /** @var TableProviderInterface|\PHPUnit_Framework_MockObject_MockObject $notWorkingProvider */ + $notWorkingProvider = $this->createMock(TableProviderInterface::class); + $notWorkingProvider->expects($this->once()) + ->method('supports') + ->willReturn(false) + ; + + $this->provider->addProvider($notWorkingProvider); + $this->provider->addProvider($workingProvider); + + $tables = $this->provider->getTables(new \stdClass()); + + $this->assertNotEmpty($tables); + } + + public function testGetTablesNotSupported() + { + /** @var TableProviderInterface|\PHPUnit_Framework_MockObject_MockObject $notWorkingProvider */ + $notWorkingProvider = $this->createMock(TableProviderInterface::class); + $notWorkingProvider->expects($this->once()) + ->method('supports') + ->willReturn(false) + ; + + $this->provider->addProvider($notWorkingProvider); + + $tables = $this->provider->getTables(new \stdClass()); + + $this->assertEmpty($tables); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/Render/ConfigValuesProviderTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/Render/ConfigValuesProviderTest.php new file mode 100644 index 000000000..393a930e2 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/Render/ConfigValuesProviderTest.php @@ -0,0 +1,75 @@ +createMock(ConfigManager::class); + $parameters = []; + + $provider = new ConfigValuesProvider($configManager, $parameters); + + $this->assertTrue($provider->supports('test value', [])); + } + + public function testGetParams() + { + $parameters = [ + 'parameter-1' => 'value 1', + 'parameter-2' => 'value 2', + ]; + + /** @var ConfigManager|\PHPUnit_Framework_MockObject_MockObject $configManager */ + $configManager = $this->createMock(ConfigManager::class); + $configManager->expects($this->exactly(count($parameters))) + ->method('get') + ->withConsecutive(...array_map(function ($x) { return [$x, false, false, null]; }, array_keys($parameters))) + ->willReturnOnConsecutiveCalls(...array_values($parameters)) + ; + + $providerParameters = array_combine(array_keys($parameters), array_keys($parameters)); + + $provider = new ConfigValuesProvider($configManager, $providerParameters); + + $result = $provider->getParams('test value', []); + + $this->assertCount(count($parameters), $result); + foreach ($parameters as $key => $value) { + $this->assertEquals($value, $result[$key]); + } + } + + public function testGetParamsWithScope() + { + $parameters = [ + 'parameter-1' => 'value 1', + 'parameter-2' => 'value 2', + ]; + + /** @var ConfigManager|\PHPUnit_Framework_MockObject_MockObject $configManager */ + $configManager = $this->createMock(ConfigManager::class); + $configManager->expects($this->exactly(count($parameters))) + ->method('get') + ->withConsecutive(...array_map(function ($x) { return [$x, false, false, 'scope']; }, array_keys($parameters))) + ->willReturnOnConsecutiveCalls(...array_values($parameters)) + ; + + $providerParameters = array_combine(array_keys($parameters), array_keys($parameters)); + + $provider = new ConfigValuesProvider($configManager, $providerParameters); + + $result = $provider->getParams('test value', [ConfigValuesProvider::SCOPE_IDENTIFIER_KEY => 'scope']); + + $this->assertCount(count($parameters), $result); + foreach ($parameters as $key => $value) { + $this->assertEquals($value, $result[$key]); + } + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/Render/EntityProviderTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/Render/EntityProviderTest.php new file mode 100644 index 000000000..051164b29 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/Render/EntityProviderTest.php @@ -0,0 +1,29 @@ +provider = new EntityProvider(); + } + + public function testSupports() + { + $this->assertTrue($this->provider->supports('test value', [])); + } + + public function testGetParams() + { + $result = $this->provider->getParams('test value', []); + + $this->assertTrue(is_array($result)); + $this->assertEquals('test value', $result['entity']); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/RenderParametersProviderTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/RenderParametersProviderTest.php new file mode 100644 index 000000000..c06933f10 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/RenderParametersProviderTest.php @@ -0,0 +1,74 @@ +createMock(RenderParameterProviderInterface::class); + $firstProvider->expects($this->once()) + ->method('supports') + ->willReturn($firstProviderSupported) + ; + if ($firstProviderSupported) { + $firstProvider->expects($this->once()) + ->method('getParams') + ->willReturn(['first' => 'first value']) + ; + } + + /** @var RenderParameterProviderInterface|\PHPUnit_Framework_MockObject_MockObject $secondProvider */ + $secondProvider = $this->createMock(RenderParameterProviderInterface::class); + $secondProvider->expects($this->once()) + ->method('supports') + ->willReturn($secondProviderSupported) + ; + if ($secondProviderSupported) { + $secondProvider->expects($this->once()) + ->method('getParams') + ->willReturn(['second' => 'second value']) + ; + } + + $provider = new RenderParametersProvider(); + $provider->addProvider($firstProvider); + $provider->addProvider($secondProvider); + + $entity = new \stdClass(); + $this->assertEquals($result, $provider->getParams($entity, [])); + } + + public function getParamsProvider() + { + return [ + 'none supported' => [ + 'firstProviderSupported' => false, + 'secondProviderSupported' => false, + 'result' => [], + ], + 'one supported' => [ + 'firstProviderSupported' => true, + 'secondProviderSupported' => false, + 'result' => ['first' => 'first value'], + ], + 'multiple supported' => [ + 'firstProviderSupported' => true, + 'secondProviderSupported' => true, + 'result' => ['first' => 'first value', 'second' => 'second value'], + ], + ]; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/TableSizeProviderTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/TableSizeProviderTest.php new file mode 100644 index 000000000..d41b0c9d3 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Provider/TableSizeProviderTest.php @@ -0,0 +1,186 @@ +salesChannel = $this->getEntity(SalesChannel::class, ['id' => 1, 'name' => 'Test Sales Channel', 'code' => 'test1']); + } + + /** + * @param $maxHeight + * @param $maxTextWidth + * @param $firstPageInfoHeight + * @param $lastPageInfoHeight + * @param $pageSize + * + * @dataProvider dataProvider + */ + public function testGetMaxHeight($maxHeight, $maxTextWidth, $firstPageInfoHeight, $lastPageInfoHeight, $pageSize) + { + $provider = $this->getProvider($maxHeight, $maxTextWidth, $firstPageInfoHeight, $lastPageInfoHeight, $pageSize); + + $result = $this->getPageSizeProviderResult($maxHeight, $pageSize); + + $this->assertEquals($result, $provider->getMaxHeight($this->salesChannel)); + } + + /** + * @param $maxHeight + * @param $maxTextWidth + * @param $firstPageInfoHeight + * @param $lastPageInfoHeight + * @param $pageSize + * + * @dataProvider dataProvider + */ + public function testGetMaxTextWidth($maxHeight, $maxTextWidth, $firstPageInfoHeight, $lastPageInfoHeight, $pageSize) + { + $provider = $this->getProvider($maxHeight, $maxTextWidth, $firstPageInfoHeight, $lastPageInfoHeight, $pageSize); + + $result = $this->getPageSizeProviderResult($maxTextWidth, $pageSize); + + $this->assertEquals($result, $provider->getMaxTextWidth($this->salesChannel)); + } + + /** + * @param $maxHeight + * @param $maxTextWidth + * @param $firstPageInfoHeight + * @param $lastPageInfoHeight + * @param $pageSize + * + * @dataProvider dataProvider + */ + public function testGetFirstPageInfoHeight($maxHeight, $maxTextWidth, $firstPageInfoHeight, $lastPageInfoHeight, $pageSize) + { + $provider = $this->getProvider($maxHeight, $maxTextWidth, $firstPageInfoHeight, $lastPageInfoHeight, $pageSize); + + $result = $this->getPageSizeProviderResult($firstPageInfoHeight, $pageSize); + + $this->assertEquals($result, $provider->getFirstPageInfoHeight($this->salesChannel)); + } + + /** + * @param $maxHeight + * @param $maxTextWidth + * @param $firstPageInfoHeight + * @param $lastPageInfoHeight + * @param $pageSize + * + * @dataProvider dataProvider + */ + public function testGetLastPageInfoHeight($maxHeight, $maxTextWidth, $firstPageInfoHeight, $lastPageInfoHeight, $pageSize) + { + $provider = $this->getProvider($maxHeight, $maxTextWidth, $firstPageInfoHeight, $lastPageInfoHeight, $pageSize); + + $result = $this->getPageSizeProviderResult($lastPageInfoHeight, $pageSize); + + $this->assertEquals($result, $provider->getLastPageInfoHeight($this->salesChannel)); + } + + /** + * @return array + */ + public function dataProvider() + { + return [ + 'a4' => [ + 'maxHeight' => ['a4' => 10, 'letter' => 11], + 'maxTextWidth' => ['a4' => 12, 'letter' => 13], + 'firstPageInfoHeight' => ['a4' => 14, 'letter' => 15], + 'lastPageInfoHeight' => ['a4' => 16, 'letter' => 16], + 'pageSize' => Configuration::PAPER_SIZE_A4, + ], + 'letter' => [ + 'maxHeight' => ['a4' => 20, 'letter' => 21], + 'maxTextWidth' => ['a4' => 22, 'letter' => 23], + 'firstPageInfoHeight' => ['a4' => 14, 'letter' => 15], + 'lastPageInfoHeight' => ['a4' => 16, 'letter' => 17], + 'pageSize' => Configuration::PAPER_SIZE_LETTER, + ], + 'not compound' => [ + 'maxHeight' => 30, + 'maxTextWidth' => 32, + 'firstPageInfoHeight' => 34, + 'lastPageInfoHeight' => 36, + 'pageSize' => null, + ], + 'not set' => [ + 'maxHeight' => ['a4' => 20, 'letter' => 21], + 'maxTextWidth' => ['a4' => 22, 'letter' => 23], + 'firstPageInfoHeight' => ['a4' => 14, 'letter' => 15], + 'lastPageInfoHeight' => ['a4' => 16, 'letter' => 17], + 'pageSize' => 'not existing', + ], + ]; + } + + /** + * @param $maxHeight + * @param $maxTextWidth + * @param $firstPageInfoHeight + * @param $lastPageInfoHeight + * @param $pageSize + * @return TableSizeProvider + */ + protected function getProvider($maxHeight, $maxTextWidth, $firstPageInfoHeight, $lastPageInfoHeight, $pageSize) + { + /** @var SalesChannel $salesChannel */ + $salesChannel = $this->getEntity(SalesChannel::class, ['id' => 1, 'name' => 'Test Sales Channel', 'code' => 'test1']); + + /** @var ConfigManager|\PHPUnit_Framework_MockObject_MockObject $configManager */ + $configManager = $this->createMock(ConfigManager::class); + if ($pageSize !== null) { + $configManager->expects($this->once()) + ->method('get') + ->with('marello_pdf.paper_size', false, false, $salesChannel) + ->willReturn($pageSize) + ; + } + + return new TableSizeProvider( + $configManager, + $maxHeight, + $maxTextWidth, + $firstPageInfoHeight, + $lastPageInfoHeight + ); + } + + /** + * @param $values + * @param $pageSize + * @return mixed|null + */ + protected function getPageSizeProviderResult($values, $pageSize) + { + $result = null; + if (!is_array($values)) { + $result = $values; + } elseif (isset($values[$pageSize])) { + $result = $values[$pageSize]; + } else { + $this->expectException(PaperSizeNotSetException::class); + } + + return $result; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Renderer/HtmlRendererTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Renderer/HtmlRendererTest.php new file mode 100644 index 000000000..b01b3ea43 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Renderer/HtmlRendererTest.php @@ -0,0 +1,105 @@ +createMock(PdfWriterFactory::class); + $factory->expects($this->once()) + ->method('create') + ->willReturn(new Mpdf()) + ; + + $this->renderer = new HtmlRenderer($factory); + } + + public function tearDown() + { + $files = ['/tmp/pdf_single.pdf', '/tmp/pdf_multiple.pdf']; + + foreach ($files as $file) { + if (file_exists($file)) { + unlink($file); + } + } + } + + /** + * @param $input + * @param $expected + * + * @dataProvider renderProvider + */ + public function testRender($input, $expected) + { + $generatedPdf = $this->renderer->render($input); + $expectedMagick = new \Imagick(); + $expectedMagick->readImageBlob($expected); + $expectedMagick->resetIterator(); + $expectedMagick = $expectedMagick->appendImages(true); + + $generatedImagick = new \Imagick(); + $generatedImagick->readImageBlob($generatedPdf); + $generatedImagick->resetIterator(); + $generatedImagick = $generatedImagick->appendImages(true); + + $diff = $expectedMagick->compareImages($generatedImagick, 1); + + $this->assertSame(0.0, $diff[1]); + } + + /** + * @param $input + * @param $expected + * + * @dataProvider renderProvider + */ + public function testRenderToFile($input, $expected, $filename) + { + $generatedPdf = $this->renderer->renderToFile($input, $filename); + + $expectedMagick = new \Imagick(); + $expectedMagick->readImageBlob($expected); + $expectedMagick->resetIterator(); + $expectedMagick = $expectedMagick->appendImages(true); + + $generatedImagick = new \Imagick(); + $generatedImagick->readImageFile(fopen($filename, 'r')); + $generatedImagick->resetIterator(); + $generatedImagick = $generatedImagick->appendImages(true); + + $diff = $expectedMagick->compareImages($generatedImagick, 1); + + $this->assertSame(0.0, $diff[1]); + } + + /** + * @return array + */ + public function renderProvider() + { + return [ + 'single page' => [ + 'input' => 'PDF test

Page 1

Page 1

', + 'expected' => base64_decode(file_get_contents(__DIR__.'/data/single.txt')), + 'filename' => '/tmp/pdf_single.pdf', + ], + 'multiple pages' => [ + 'input' => 'PDF test

Page 1

Page 1

Page 2

Page 2

', + 'expected' => base64_decode(file_get_contents(__DIR__.'/data/multiple.txt')), + 'filename' => '/tmp/pdf_multiple.pdf', + ], + ]; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Renderer/data/multiple.txt b/src/Marello/Bundle/PdfBundle/Tests/Unit/Renderer/data/multiple.txt new file mode 100644 index 000000000..9d62e8b44 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Renderer/data/multiple.txt @@ -0,0 +1 @@ +JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXBlIC9QYWdlCi9QYXJlbnQgMSAwIFIKL01lZGlhQm94IFswIDAgNTk1LjI4MCA4NDEuODkwXQovVHJpbUJveCBbMC4wMDAgMC4wMDAgNTk1LjI4MCA4NDEuODkwXQovUmVzb3VyY2VzIDIgMCBSCi9Hcm91cCA8PCAvVHlwZSAvR3JvdXAgL1MgL1RyYW5zcGFyZW5jeSAvQ1MgL0RldmljZVJHQiA+PiAKL0NvbnRlbnRzIDQgMCBSPj4KZW5kb2JqCjQgMCBvYmoKPDwvRmlsdGVyIC9GbGF0ZURlY29kZSAvTGVuZ3RoIDE0MT4+CnN0cmVhbQp4nI2OsQoCQQxEU+crptQml4S9W2tBD6wUtxML4XTBTi38ffc8PNRCrBKYmTcDx4pV6ibizvOEamkwE1VFOmGRePhz8fS35ardGvJtzBTflXd7KLpRe3Ic7m+cCwZCBvoIiie41K6IMYg2M6QOmNCaDpTpSCCbIp1LFJtP8NfAl/ajIJi4+z8FDwKxOJwKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8L1R5cGUgL1BhZ2UKL1BhcmVudCAxIDAgUgovTWVkaWFCb3ggWzAgMCA1OTUuMjgwIDg0MS44OTBdCi9UcmltQm94IFswLjAwMCAwLjAwMCA1OTUuMjgwIDg0MS44OTBdCi9SZXNvdXJjZXMgMiAwIFIKL0dyb3VwIDw8IC9UeXBlIC9Hcm91cCAvUyAvVHJhbnNwYXJlbmN5IC9DUyAvRGV2aWNlUkdCID4+IAovQ29udGVudHMgNiAwIFI+PgplbmRvYmoKNiAwIG9iago8PC9GaWx0ZXIgL0ZsYXRlRGVjb2RlIC9MZW5ndGggMTQzPj4Kc3RyZWFtCnicjY6xCgIxEES33q+YUpu9TXATrxX0wEoxnVgIpwE7tfD3zXkY0EKsdmFm3gw81qxiIeLBi4Rm5eCcqCrSGcvE45+LZ7gdN93OId9rpvhuvD9A0VftxfFwQaxyrhgJGRgiKJ6ZF/OKGFsJc0PqgQlt6EiZTgTyU6RLiWL7Cf4a+NZ+FJhKa38VPAEQDji9CmVuZHN0cmVhbQplbmRvYmoKMSAwIG9iago8PC9UeXBlIC9QYWdlcwovS2lkcyBbMyAwIFIgNSAwIFIgXQovQ291bnQgMgovTWVkaWFCb3ggWzAgMCA1OTUuMjgwIDg0MS44OTBdCj4+CmVuZG9iago3IDAgb2JqCjw8L1R5cGUgL0V4dEdTdGF0ZQovQk0gL05vcm1hbAovY2EgMQovQ0EgMQo+PgplbmRvYmoKOCAwIG9iago8PC9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMAovQmFzZUZvbnQgL01QREZBQStEZWphVnVTZXJpZkNvbmRlbnNlZAovRW5jb2RpbmcgL0lkZW50aXR5LUgKL0Rlc2NlbmRhbnRGb250cyBbOSAwIFJdCi9Ub1VuaWNvZGUgMTAgMCBSCj4+CmVuZG9iago5IDAgb2JqCjw8L1R5cGUgL0ZvbnQKL1N1YnR5cGUgL0NJREZvbnRUeXBlMgovQmFzZUZvbnQgL01QREZBQStEZWphVnVTZXJpZkNvbmRlbnNlZAovQ0lEU3lzdGVtSW5mbyAxMSAwIFIKL0ZvbnREZXNjcmlwdG9yIDEyIDAgUgovRFcgNTQwCi9XIFsgMzIgWyAyODYgMzYxIDQxNCA3NTQgNTcyIDg1NSA4MDEgMjQ3IDM1MSAzNTEgNDUwIDc1NCAyODYgMzA0IDI4NiAzMDMgXQogNDggNTcgNTcyIDU4IDU5IDMwMyA2MCA2MiA3NTQgNjMgWyA0ODIgOTAwIDY1MCA2NjEgNjg4IDcyMSA2NTcgNjI0IDcxOSA3ODUgMzU1IDM2MCA2NzIgNTk4IDkyMSA3ODcgNzM4IDYwNSA3MzggNjc3IDYxNiA2MDAgNzU4IDY1MCA5MjUgNjQxIDU5NCA2MjUgMzUxIDMwMyAzNTEgNzU0IDQ1MCA0NTAgNTM2IDU3NiA1MDQgNTc2IDUzMiAzMzMgNTc2IDU4MCAyODggMjc5IDU0NSAyODggODUzIDU4MCA1NDIgNTc2IDU3NiA0MzAgNDYxIDM2MSA1ODAgNTA4IDc3MCA1MDcgNTA4IDQ3NCA1NzIgMzAzIDU3MiA3NTQgXQogXQovQ0lEVG9HSURNYXAgMTMgMCBSCj4+CmVuZG9iagoxMCAwIG9iago8PC9MZW5ndGggMzQ2Pj4Kc3RyZWFtCi9DSURJbml0IC9Qcm9jU2V0IGZpbmRyZXNvdXJjZSBiZWdpbgoxMiBkaWN0IGJlZ2luCmJlZ2luY21hcAovQ0lEU3lzdGVtSW5mbwo8PC9SZWdpc3RyeSAoQWRvYmUpCi9PcmRlcmluZyAoVUNTKQovU3VwcGxlbWVudCAwCj4+IGRlZgovQ01hcE5hbWUgL0Fkb2JlLUlkZW50aXR5LVVDUyBkZWYKL0NNYXBUeXBlIDIgZGVmCjEgYmVnaW5jb2Rlc3BhY2VyYW5nZQo8MDAwMD4gPEZGRkY+CmVuZGNvZGVzcGFjZXJhbmdlCjEgYmVnaW5iZnJhbmdlCjwwMDAwPiA8RkZGRj4gPDAwMDA+CmVuZGJmcmFuZ2UKZW5kY21hcApDTWFwTmFtZSBjdXJyZW50ZGljdCAvQ01hcCBkZWZpbmVyZXNvdXJjZSBwb3AKZW5kCmVuZAoKZW5kc3RyZWFtCmVuZG9iagoxMSAwIG9iago8PC9SZWdpc3RyeSAoQWRvYmUpCi9PcmRlcmluZyAoVUNTKQovU3VwcGxlbWVudCAwCj4+CmVuZG9iagoxMiAwIG9iago8PC9UeXBlIC9Gb250RGVzY3JpcHRvcgovRm9udE5hbWUgL01QREZBQStEZWphVnVTZXJpZkNvbmRlbnNlZAogL0NhcEhlaWdodCA3MjkKIC9YSGVpZ2h0IDUxOQogL0ZvbnRCQm94IFstNjkzIC0zNDcgMTUxMiAxMTA5XQogL0ZsYWdzIDQKIC9Bc2NlbnQgOTI4CiAvRGVzY2VudCAtMjM2CiAvTGVhZGluZyAwCiAvSXRhbGljQW5nbGUgMAogL1N0ZW1WIDg3CiAvTWlzc2luZ1dpZHRoIDU0MAogL1N0eWxlIDw8IC9QYW5vc2UgPCAwIDAgMiA2IDYgNiA1IDYgNSAyIDIgND4gPj4KL0ZvbnRGaWxlMiAxNCAwIFIKPj4KZW5kb2JqCjEzIDAgb2JqCjw8L0xlbmd0aCAzMDQKL0ZpbHRlciAvRmxhdGVEZWNvZGUKPj4Kc3RyZWFtCnic7c/nVggAAIDR75zsUWZkJXtkVCqEbC2UyIjo/V+ih+ifc+8b3Nqlgfa0t33t70AHO9ThjnS0wYY61vFOdLJTnW64M51tpHOd70IXu9RolxvrSle71vVudLNb3e5OdxvvXvd70MMmmmyqR00302yPe9LT5nrW814038te9bo3ve1d7/vQQosttdxKH/vU51Zb60vrfe1bG33vRz/71Wa/+9NWf/vX9m7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vbnQKL1N1YnR5cGUgL0NJREZvbnRUeXBlMgovQmFzZUZvbnQgL01QREZBQStEZWphVnVTZXJpZkNvbmRlbnNlZC1Cb2xkCi9DSURTeXN0ZW1JbmZvIDE4IDAgUgovRm9udERlc2NyaXB0b3IgMTkgMCBSCi9EVyA1NDAKL1cgWyAzMiBbIDMxMyAzOTUgNDY5IDc1NCA2MjYgODU1IDgxMyAyNzUgNDI2IDQyNiA0NzAgNzU0IDMxMyAzNzQgMzEzIDMyOSBdCiA0OCA1NyA2MjYgNTggNTkgMzMyIDYwIDYyIDc1NCA2MyBbIDUyNyA5MDAgNjk4IDc2MCA3MTYgNzgwIDY4NiA2MzkgNzY5IDg1MCA0MjEgNDI2IDc4MiA2MzMgOTk2IDgyMiA3ODQgNjc3IDc4NCA3NDggNjUwIDY2OSA3ODUgNjk4IDEwMTEgNjk4IDY0MiA2NTcgNDI2IDMyOSA0MjYgNzU0IDQ1MCA0NTAgNTgzIDYyOSA1NDggNjI5IDU3MiAzODcgNjI5IDY1NCAzNDIgMzI1IDYyNCAzNDIgOTUyIDY1NCA2MDAgNjI5IDYyOSA0NzQgNTA2IDQxNiA2NTQgNTIzIDc3NCA1MzYgNTIzIDUxMSA1NzkgMzI3IDU3OSA3NTQgXQogXQovQ0lEVG9HSURNYXAgMjAgMCBSCj4+CmVuZG9iagoxNyAwIG9iago8PC9MZW5ndGggMzQ2Pj4Kc3RyZWFtCi9DSURJbml0IC9Qcm9jU2V0IGZpbmRyZXNvdXJjZSBiZWdpbgoxMiBkaWN0IGJlZ2luCmJlZ2luY21hcAovQ0lEU3lzdGVtSW5mbwo8PC9SZWdpc3RyeSAoQWRvYmUpCi9PcmRlcmluZyAoVUNTKQovU3VwcGxlbWVudCAwCj4+IGRlZgovQ01hcE5hbWUgL0Fkb2JlLUlkZW50aXR5LVVDUyBkZWYKL0NNYXBUeXBlIDIgZGVmCjEgYmVnaW5jb2Rlc3BhY2VyYW5nZQo8MDAwMD4gPEZGRkY+CmVuZGNvZGVzcGFjZXJhbmdlCjEgYmVnaW5iZnJhbmdlCjwwMDAwPiA8RkZGRj4gPDAwMDA+CmVuZGJmcmFuZ2UKZW5kY21hcApDTWFwTmFtZSBjdXJyZW50ZGljdCAvQ01hcCBkZWZpbmVyZXNvdXJjZSBwb3AKZW5kCmVuZAoKZW5kc3RyZWFtCmVuZG9iagoxOCAwIG9iago8PC9SZWdpc3RyeSAoQWRvYmUpCi9PcmRlcmluZyAoVUNTKQovU3VwcGxlbWVudCAwCj4+CmVuZG9iagoxOSAwIG9iago8PC9UeXBlIC9Gb250RGVzY3JpcHRvcgovRm9udE5hbWUgL01QREZBQStEZWphVnVTZXJpZkNvbmRlbnNlZC1Cb2xkCiAvQ2FwSGVpZ2h0IDcyOQogL1hIZWlnaHQgNTE5CiAvRm9udEJCb3ggWy03NTIgLTM4OSAxNjE3IDExNDVdCiAvRmxhZ3MgMjYyMTQ4CiAvQXNjZW50IDkzOQogL0Rlc2NlbnQgLTIzNgogL0xlYWRpbmcgMAogL0l0YWxpY0FuZ2xlIDAKIC9TdGVtViAxNjUKIC9NaXNzaW5nV2lkdGggNTQwCiAvU3R5bGUgPDwgL1Bhbm9zZSA8IDAgMCAyIDYgOCA2IDUgNiA1IDIgMiA0PiA+PgovRm9udEZpbGUyIDIxIDAgUgo+PgplbmRvYmoKMjAgMCBvYmoKPDwvTGVuZ3RoIDMwNAovRmlsdGVyIC9GbGF0ZURlY29kZQo+PgpzdHJlYW0KeJztz+dWCAAAgNHvnOxRZmQle2RUKoRsLZTIiOj9X6KH6J9z7xvc2qWB9rS3fe3vQAc71OGOdLTBhjrW8U50slOdbrgznW2kc53vQhe71GiXG+tKV7vW9W50s1vd7k53G+9e93vQwyaabKpHTTfTbI970tPmetbzXjTfy171uje97V3v+9BCiy213Eof+9TnVlvrS+t97Vsbfe9HP/vVZr/701Z/+9f2bvMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwf9kBd7wSjwplbmRzdHJlYW0KZW5kb2JqCjIxIDAgb2JqCjw8L0xlbmd0aCAxMTM2MQovRmlsdGVyIC9GbGF0ZURlY29kZQovTGVuZ3RoMSAxOTk2NAo+PgpzdHJlYW0KeJzVfAl8FMXWb1VX92QPWUiCQKCTEEIgBE0IERDINlnIShICyJZJZiYJJJlhZrIMIRAQBEQ2BVQ2UZE1KCoiih+CiHCvKCpycd9wu24ocnEhU3mnqnsmk4jc+773vt/7vbQ9Xd1V9T/7qVM9gwgjhPxRGyKovLBkRILx3QAfePI9nGWVdTqzV733WoRwBtx3VDba5Lu/2wP3Qgo822k0V9V9veLnVxAiX0D/qiqd1Yw84ECiGe59q2rtxln5m5+E+wUIDRSrDTq9sG3SIoSi2qB/VDU88Dd7GuH+CNwPqq6zNZ8bOPABuP8Q8FfUmip11n/VViMUrYf+V+t0zWbxU+FXhAbHwb1cr6szrPT45Cu4z0Mo0t9ssto6F6M7EcqewfrNFoP5vrL1Y+Ae6EuLEBaDhReRCO1E6SGgMEC5kovIKASBVBpPjaSRBEH8Amk6i9DP171FJAMSKjJq9dCSOzs1vWlvvNnjLnypHKGHkfOPqNf+ars/fGJ+FdFdcBXhYPcLOzsZZ52dnZf4PRstIglpQGueyAt5Ix/ki/zAIr1QAApEQSgY9UYhKBSFoT7oFtQX9QPMcDQADQR+IlAkikKDUDQajGLQEBSLhqJhKA4NR/FoBLoV3YYSUCIaiZLQKJSMbkej0Rg0Ft2BxqHxaAJKQakoDaWjDKRFmSgLZaMcNBHlojyUjwpQISpCk1AxKkGlaDIqQ1PQVDQNdDsdzUAz0Sw0G5UjHfAvIAG3sE84ytE1HIDmIDt/XgY9M3AF3C2Hz7WoHLfgB2FsASCMQ8vQFXi+mM8fiBfD0c71cQyeHFNa8LwdUBBwMA8tRg9yGqfFGaJRbBGNuAKvwqvgyUesTxwORyyMXQxnC/RVsDaeAEcsMoKMsehReHIN+u1oM/aWXgfkUzgaVQOdAjh3Yg32BF4uYH/BiH1hrFVkXIQz+aQLIFUDssO8C/z4Ce71yC5d0PQWroFc2dCnEcvRl4A+D83D3jiRJAq/wdwCeLKSSYX3IYHMAVcohyOGHxmoAnB2wQg72ipUCIliDBvFeR+HPkCHOd9GtB7ui9E+/olIBzpPkrAR+Ge6uUU6gco8JuIIjT8e4bEIdIU04WixYEQDSQayInCfwxpJJAJGcXLAQSE6R38wZdJU+cy0iOFxPW7lAA/5ICo66GeXj3R2Fk0V+0nTDkr9D5Joz4NidNRnf9X52fC43KKp8sG3tBkqqrY8A56VTIUmu4PH8FybMRxxzwA9QxwQaE2nm8g1aSe0IWsEB0YERkcERswgD3W8IZx1jKSbPPx/u2LRxPJZ33ZeElLAEj4QFyh55KjEhNCQ3pqoyMHBESQkKulbfWamwZCZqRfwpLmtjxaYzQWFZrO0pOPwsWN8/pNkHwmE+ZwWDowCalGBeNkFQQcWdVwQhrETxrVCfrhXOgbjBsK4KJLogxODwZwhESQCzqjgKHYmRfCTbP5xwo8xZT9MOT3ld3pxPEb0rbLTcHum7DqOmUDxrWWkmv4I5yK8mC66QH+8SBfjRey8iIMugP8T9Bi9IM7RhEEcD4N4RcmBiQPwWBwYj5NGjoKbwAE4LDAqHsdAA+T1AKbjMfaHdmhY8HgYNDjmYG2v1Ut8P335w1frDEem1vVaab5y6ovzdZWv4fi8aXnT6qbPmqobi6PuXoo3Jh977NA5Dfajv2iGxdCfbIsFeu/Y04eef11Dr4Dnx4krfUrHZZb1d/j7TJk4sXwg5Ka4zkua/eDxPpCD+kGeSQStRGqAfGLCKEbdaQZgNyIRR2G3+zC3cXjNmISEMWMSE8e0Hz7cvv+558inmzuObCHn2p99lt3uH5uQyLqlVat27b733t27VhneOnXq7bdPnXrr91/I8LdeOXX+/KlX3tI7e7lNc4C32cBbPGQ3FItj/HGUjJjeuPbAQ7jqohgbYaFheDxOBqOFRPljj7DEaEWJwJ1mNp1kvfO9xfQKPbd1V3z8b5+mPW2yBVTrplc/EuSFt48a5/kYfvnhXsXpLzzkuIeS2xKPb8hceldREUafPvZ1Sfr80TsP9wmjZxZYyotnHO7tRQdkPD2vufm+z6dcrBXy0h8o2fv+bczzMfNBvIz7IPdA8D7F9VjfAXqCeGp6Q/ZHeBAKDEDRIRohMCAojHjSN+k/cAxOXLNw4Rp6ohW/je+E4+zS5nK6nO6me+jycgV/Jj0hPK9gBCeNCgoMEGKSRIY1a01r6xqcgGPpO/REczm246l4GraXNy+lI+kuOOL5/CHCaLIV9AkRhv2wR3RwVLAUMxwnSySRRJMt9EWcMZW+12dnH/reNKylR6fhGLjBMeKLe/ZtuUxb8LLLW/bt2X4Fr6DNVwDvJBKkTEkE3wF5IXpY1CVFBJJa/B6NOUcH4/cl0XHlK8cVwf8rwV+RoQ99Ap/DE2A1RGHj8TgcEdJb8OgzayrGuw+cKn3znjLPj+iJOmVsJd4ubBbamT6DIXPohSTHa0I7vcL6NnZewj/gckbbPVtsNGRm6/XZmQZnimBjUWea8KhqF+bD752hg09LF36vY+tY5yXxgOr/KDphVGDAYHCowIDQMJlpNoJ/Cktqi4pq2Ul/oZ/ByuWHffEA+vnU9/Fz+NDH79NsmvN+A16Lm+BYR810JRzzuAyfwZqQC/jeCCUmJQZKSZADE0McHfQbPAn3d9jFI0c2fv7HSjsb2wJjZ8LYfiBvVOB4nCiLLCfgCJgYkZQ8KAgCDZj7pbb+Yj1GeOD2T+hKobWlY8Bp1Ll3ccucNlGP06Kj//nmhu8eoZcP0olXHzqJg5478OxaRZ9tIOtqwB8K+ICewNFZ3hEjIgfHsMykBPMwnKQ0gJhH1PamT+b+gsVN6w4eoP+gX875vKXO+94FKzduWXvHiKXLWhvmLPCrk/ZFRZ04tHxP+MCzT731yZAhOHvNxr1bHj3YvGL5ouWLWQWRC7I9B7T9mffFYi4RZqqICkzEgVh80NH/jONNIQEXf/31GccX1GzHAVZytaPvYvoTXi9ccAxTZHgAZGgCWw7kPseUlIBCeqPuAnC+A8UAx6133DfpNyzTT6mj/r3GWs2iBUvbWhdp58bfKV2gf9AjiSPpP3+6Sn8aMhRn3r/u1N9Pn5qQJlxiuccGdHYAv7dA/YUwIxMEZKIUp+jKPMFOepBr8Ktj1k8+9x399Sr9gX4BWX7UpOfn1IW2Vu5Yd2ZmYeHMGYWFpGHkSHr121/pL3geNuIH8MMDBtDfjDVXr7/15LvvPslOkBEqAnEOyOiJkBe4K6S0wAhxjuPCc0J/R00LeVe8eD1WfOHv+Evw6Nkqn2FQK45gVS83pzzImSYHKWYmTiZBQcFubTJtx276Df0Njq/3PLx6NUZXf8Fo9bmMktL09NLS9KhSY3VxSZWxlPQLP/v4Gx9++MbjZ8MH7V1y7MyZY0v24pjtTU3btzU0AG/33PPc86tX87xdCTyt5TEV3X01CYYMHikkjUSJih67jCbYQDszQEu1Y9dPfv077HEVB+P+9BR9bdILc+rCWvXb10ktThV1fJmUhH2++w370jV0C62hugEDsKex5l9Aey/khU7yvRLPwW6ZIdCtvVefmaXXZ2VVKtdMPRleaJ5XUFBf7zjpyhsCGkEPq1i9u2MRnrkgcbmA7DyDtbtwOgwl5+6Z+hl9sY7nyTQpUzwJ+wDkJYTgJCxpO9aSedcXikvIefoEPXgZv/k5Psd1dxIPlzJhz6LUTCypwnlSXMIGX18oBFwS9l1C3TGDkzAD5YPIPHL+J3rb5/TWy7iI4TV1XiJfcVtEQn7rLoPS9ghhucYZNXikITPLYMjKNLxo+vusS79892Hrwob6uZVzPetcVde0yMiPXv/7P4Nex8OGHdm87v6lS5TKz063ap6SdsAepwBYTHJzsxBm+MHqog2xr1Q2PII8NEJI76Aw7gXJYWzcoJjB4CNByaOY74aFBoUEoGFYtJunT6urmzbdPGRxUfupU+1Fi4ecWnxvpbH404UNr01edM+UIt31++pfn5lo/X3JE/RDi6Wx0WLFg5/fgT2qmpvopc7++Le8xdue3LDh4Ja7s/N+On/+cl5Wq6NvzLcH135bXlCaqZ1Dj5zaQn8wNM3PSJ9VWrpw4QKce/w4nti6cIGuwkS/eIJ+T8+CnJ0/Qw25H+KzF+zgUFRSIiwqUD8mJZIIqCPhw78O/3ERX29zrLrLRD3n7SB4xzm7NOOPnXY7vo2eswtRwli2N4QKX3oKcIIg4w9S1gcoYBSjKHFMnAk50Nkoa28n4deu7dv993fpeXrsi8YtG9c/3Lh4/67d+xbbxUv2I88v29o75NWdH79NjK2LlzReD3/w4R3bldyZ33lJmgW+0BduNKIzl4lqLmPVCLOBNDOJ/vAN3U8bYAs2uQmL6yxRzz946tixV5fvG4z3ff0pfgQbIHk9kpJK1794lL5En4Hj+K496r4QST/z3BUC/gZZE6qUYRgroikUhZmtb79y8nyr4/f2duFpPAcq5jb6gB3H4SysxcNATSAHe0Q30kWMb4Y5EDDDXDriZ4TaZldQi3hkgeM1XN7SQncImS0cY8dFGnHM0XHMsfMi3aHogGFJgBVyY6zEEIb0RIvjH3gyIO0/3x1ng92px42gxwFw4645jtBNseLVIf/V/tbJo6/mLkl6FH/xAD2/Haoxptg71+Gw1SJ69SnIcQfoQXpy8GC8uRnSTx7ejqtxDd7O1OviWcN2qf1cPIfw/Q2/cs8LVDkXDh0QQvcLzxw44Mjf7/j2AOPeTr3xNbv9erjdLugdW+0uTOLN91tumE4MZR7M4GM77bSMRMJYyIIkSV1k1Rk8lSxvkDIzpmS+Ipxt//Dn3Afo5u9mTd34ujTMbv/j9V8+jewmQx93ephfvFj04C4BRq/D156h4cK6ZfR2xz6FF8cnQgRIktjxrR1qgZ12J6b4DWD6ddOL4g0MKHC+Isfw7+kqbjeEPB7txoMX7lIhrLTQyt+/H/wV9nPC0PZ2UCKd/QXV7Wc4HWnkGNeiaL1+WQy8vpr7wiSQKxcwg12YWGWEw01qb8c7ybD9+B/t33f8jeNc/4TsAc3sFJM70pR3Kcyffu2qQVnIKE7lXoOSyPbdu9v379mzH/xlIb4LF+MS2HUunIHTcC7Ow2n0KH2OHqFH7XgrxNQcvI3qIYA2Ub0rLscBnwFQ0bgyDVQGIsstIcQtvQiOz7An/fXTa++9Z2hYdU+jgTH9w+kzl2kvu3DpwPp1+znPdC3nuTd7hyYN6uJTYvvbELbAuAlC+tN2uszF9Lv08oiVdfF993+ZoQoVrsEPgts7uX6Z2kaMTri2E8f2lE6VRePB42EQr3jCgtn2PR5zUSK6ZNNEO8UibxfVx75yD828OH5KfvIBRx6I2fkt6nzW8U7jwhUrFjYK7/nNmELL7fjkvfmOj+xM5LeOb3024sHVqzdxX7NDTXMK5I1V9/AK8gjsrLG6tqKhA9UiLHLQGku/l9tX3Ve55W+ndmCvy42oc6Wl38HHlq2YdfjvR/dA3XX5LtqRjSv27Gq03Kkflnz76SMf/xoXRw9vXltlnKkbmTzywxOff5fA6EPsSTl8zUE83p0ZACTGBU/TdS3SyPl09SG7eP573EBXfX99uBIjE2APzXLnLWxPwf0ygC3It2D1hlvtW0gZ7duOPb91P36nnV6DlHSGXpVmQNiF0H9+8hkOEb6DNqL76Ed4MJ7McsK/1LULtnVgbYgdNZSjkqSDZir9g0qLhMYlYND6HdIF8HaG1hFFPrLD3MsIecYp+SSCTXHOjfBSWprIb2BP/OY3tG2J0LoOYxwBu50YrNmBd9wjTrh+gmPNFf2vf0Z22dV8BvtJyMk8R3opq7FXF0vOJJOUKMyj3zxOo3bhCjq9Rdi6AYtY2Ibf2UUP4ecXOSo2ChPoGMiTYwUIUToDw4rt+MaRxIlwvqUklW+eMNzwmevhcc9Ss0VYuhG8YvjdeLGFrnrWjn8WrgDKaWE0fKY5PlD5zYDc1enMXUqJH8I3ehkk0tHaIq7p+FrY3bJafOkTXL36eupbdJcy7yt6RfhS01utDnnu+gp3Hj9OsaZ38+9vNPfY7waDdk/j98/QGLbf1dzPMZYAxhIFA6srn7CEouPHNb1/+7hZk9AMY14ge6RCwNDwPQgzEmzrPz9OG2njcfxP6YLjPB5LTwnDOT0aTjbTD/j+GpRMNncY6Qf8vRxGC8UfhUqNkfcFR2E8/5v76UGNka7AzTwv5UJctYkZKBwNYdwwIzlrxbGuojSJ7YVDcVLXtuXXlXT0hC2V92/7/NKeemt5Vaix7Dkjxj/Sl5unlWdmTbxTWO343r68tOjJx555esLilimVX0ZGvuP4+EKtwaCvBbpDge5LoAPYOrKIZpHEN3k8ZQ2OATawc9fCqD6zEp8ZstJw9rvvzhpWDqG34zOTyyuKiirKJ9ulqGbHvSWT6AV6HY53JpUst2PjC7sOfvnlk7te4DpgMgaAjKzu4u+wIOy6vb1ihJLFgKjzhzsR/QaHXNOfrTH4W4zL5tUvKG/wxXmHn4G488GeeMjQofS95Xed+fXnc23NTv19DHL4Mjm61dySU5dKecLIJUWQsLLZlUWTKmaX4Qkr6Ogh9xhe++c/XzPcMwTkw5/UvLDryS+/PLir3b68ZBLUYxLW4KGTSnBHl61SQQ4P9k0I5qTCsRSh8p/IaCmiBQmXMicXZwn3OerNC6ragjbFfP3G7/RX3OuXH6gw9e29u04GNXm+8dx827P7wLH8mVz0b4p/3w41xgKQh1FQ1BOOE11rNBTb6iaCLDB4ZIzPTvnppyOOxSvWrHntVP59mZJ3QZ518Up7x2t2O0myL3vqueBgxjcdLX4MfA9EcczHlNcdaiEzDrurCCIZu6kwSSyjn9E/yl+pMvi32jbYu9SFT9PRTk2SJzrKP/1XRAStMW47bXdX3HI73ayq9AVFNikIZAvvqj2VTZgqnUIZbiasWINfH73B/MjjKx2v2Mbnld9p427WOrv0/OuOC5CgkpbXbNtPo5T6YQJ8rAVcH1bVuu9TsWs53GvIYRvVHAOeucLx1IoV+LRUOslkmlRoqr/ewJUF+5omOloQOc6A7jhs5VBKvZgkZaeWtEM/MdPAAKMB683XRs1PMwpJY9NGMlhzEcDSffbrMx7b3Tv4c1yUNauFr0Ugfzjg36K+AWKoSvb0UDjFHoGJwkLHCcYfXmLFzz59Dd9f7fjATH+d1sw00HELXmy3X3OcgJ1Uy1T6keIzTvm92I7DKbGgUQW129lMdZyHDfzgVvWNzp8V/6cbxh0fKs5O21K987GVjletybmTZ1ocH1luzymD62lGI3ldZcN9xD5Hd+4tZh587XT1+rsdV9xboOJNM8sqnXoI4jnPVbf+lR8Adg8/YDg3cgM1PpcCbvd3meyrBDUu+bvMan3xJIOxpMToYK8yHQ48kH5aehxWWPTSCUpfngdllAZ74Fh6kf5O/6AXFOyhtEx8CbB9URTL8m65MdrJvzN3KlLgK84MSU90S573Ox5fKWTXqBmSp5qu3Emz7YpP5wI9FrOhnJ6qmJuH6grHrvv/KkKvw+4BH7lJbCZB3mH0+vB390pe7ukD5GOjlK2tbj7guHfFffit+M3m/kViIj05ueTss3QGN/F/meZ6cZ8s7LxETooZN6wXu391odaLLLrKjH7Lbbqq1Nl3bVz27meTX2owerbYdPoxuUteXPf11WlnBuLYptbczJTMvtGxDy05sHdgBL1aV69NGzU+JDpp66pD+wdw2nFAO1eax9+ARfHNIXuP5JalByc9t2oVhIA3vTZk5Jhk4W7P1QdO3UuesOOJ9LDd0bAqo+zOHYtXPgdYbJ2kYgyLWffVmceuonhoig2wlih+is+MLdRNs4kxHcWKnwrD7I77X527vR1/wGq/j9k7bsCDujFCif1+agGFt9ADNUK07crPrbjKRA8AO3ZHubADar1i5Ky/NFNhLtRf2DkrysdZP/bD5PP3DrVi4zz6yQevnX2fflwtDJj31D/EGMdYIY+hCJsdRo56QjiClG/oRYbXj9cmDIMoechHuQQrwMLyvY24YdFVHL7I8XDLoUNttHElpT/MFbJtwi7sCYXiMpoJ7tWHfgOfq/EetSbt/BrWnRmAD2uZxNXmzJ4TcDfRhQnD5lfH1aYYhXF3jM0e5E9nVwkxll8uLcJVtp8aKkJDPsb5E7Mqo0UoRB2rhAZnzZsCPvse4P+phkwVomnlXNLieB8fnWsnow/jYU0dp48719jN9GnSAflyKPDFdmox8Xgcdr72h9ao5PHKHg5CQHkVxUxNOu5+Z0Ncv9DyRcnJi8pD+8VteOfumYaJ02yNUyYaLthvGz+o3vzHA1a79YE/zHXR425rnrWCXnl4+YDIpQ/Tn5fPAro/0HB8UBPu+n7syROa8N/YW27o202fFj0UnsK4VynvrwNVtkJBceFQkSlsQUvj4Q+Javd0fe7UpoY7c/XTl76zYXjfUN3C5OSFutC+wze8s/TCrOW417a7Iwes2I79V8xqvm1cdJ2Lw/pB42/j71HxKSmTTOD1Bn9LGTOYHazsZFVnQhiEJ6+mtKV7pk9e2ctT47t5qnbTlCm77ixbEeDht3W69gEy4Y2y4jEaQqS07Jw3pkwa7UE0GdmKriGXTfF+5Nq7bbN73fEvNNCT//jhjQkXDzuvv1/vuOR72SsWxnoi5x/M87iLwvrg1xf6z/peVn9P0vVXIRaj6Tw174NzMXhCLPqWHEdPajxRqySgxzQXUZymDeUII9GTpBodgHMmOYmGQP9JGN9HcKBKuG4UHgEvgD01nJ/B2QJnG5y5cD4Apw3O9XDOhrNS2IP2wjmCYThP8XbU5JGI7NKmziuaEMA5ivI14+G6AM7+qEwzGO5fR2VkFpzVnXZNGjwPQGUev8HzDWiSRovypTkwjl2vQx/Dmo56a7LQBMC85hna+ZO0CXlJUXBNRRkgx1eMZ7guAfovEPYbn4dgn7EPePZEQ8UKfs0VN6Bc0gfdztrSHeh24UU0Qdjc2Sw64Aptj+2AfweaIL6uzGPjyGsoSfRDhaQExUHfUHFE5yeahM6fxImQKUZ0fiOGohTxEbRZ2I5+gOtuJj+YIFQ9RqGJaAl6G8u4Gd+N90BF/ZPgLchCvJAhFAtLhAeFM0RDhpJZZCuBCBTHiOWwXm8WnxBfFn+TaqV10hnpD80gzURNrWan5g3NFx4aj1s9sj0sHhs8XvX40TPO0+i5y/Nlzx+9fL1GeVV7rfHa5/Wi11mvj72uegveQd4F3ku9n/b+xqevT4bPCp9DPu/6evqO9M32vdf3iO9XfgP8yvwW+r3o97m/t/8Q/yL/Nv+D/md7efYK7VXca4nqqxWCFxqG9rDfcKAAyC/s117sizdP9rsq1BePd/nhbHRcbWMUigvUtoBEbHL99qkfblfbIuonqBkXSchXGKW2NegWoUlte6JA4Vm17YPCCVHbfl73h+aobX80Us5S2wHIV96ktgORv3yC/SpLZKvvMU6dtWEtxLLaFpAnNqhtgkZhm9oWoX1GbUsQF/3VtgYlCOlq2xNFCuvVtg8aI5xW237Bg0mE2vZH1QM/UtsBqI+8QG0Hov7yYygdmZAZ2ZEF1aAqVI1sSIYdbyXUBzJKgIr0VpQILfZ7KhmlwRgbssJpQQakQ3Wwb5FRDqqH8fHQSkW1cMio2IVl5XcGuBpgTiN86mGkN8qA1hxAKEMNMKISxuoApYqPlKHN8GVAqYdPM4ypANwaGCfDfBPQ1fE+b4TSTWa7paaq2iYPqYyVE269NVGusMtpNTarzWLQ1cXJOfWV8XJqba1czEZZ5WKD1WBpNOjjvTMMc3RlDXJlta6+ymCVdRaDXFMvmxsqamsqZb2pTldTDwS6c1rC5ahBRmgzzdUDPwb4tHLJkApZYrDUGOV0U73eUG81wPM0GFrLBqSZavX/e5hy1+Qbo8v/c5hlHMUKOCZuiQSwHftlHyozWKw1pno5IT5xZHfSXYT/THZ4T7Kcqovo8BsJYuQoiiPZVKdzMm001YNBbWBmxJ3NBq4yBo2AQ69iNAJGPMw1wdUC7mPgeBbuaPGAa4A5qNpmM48ZMUIPoI0N8VZTg6XSYDRZqgzx9QboznTjwOmYzgD5c0CwPiatgQeNAaQ1oSYYy8Lj/47Ts/DxviFlxVw6aLnz/OcA9wZL/PcPRv3/RdK4sba7ZK5RtSjzfh33gTqu1bnwzMRD4ea8MMmKOF4dR+tyfAW7mvcZVLmqOJV67pV6jmPkvQYXNcXCirfFcb5MnMN6Pt+sBpdCwQSoNtXCNdwrFFkqVU07MW2ci+5xoYNRldxDzCq6E4GNVnhXPMkZi8xakW5eEsktp+Pxyq5WzlclzNGp8ik+WAleWcdRbLzHqR8jtGpVPx7i4rGLAktIjH8bxILi54xil07YEzN8moBKA+ezixs9l8DGfa0Cem2810njrynEqbFUCZw1cBRFJ03cB6p5TrCpmqnjz9wlcuJbunmlwm0D12Gcm3VYu47b02nrrvi1wuy4v5AjziXnCJ6XZI6sxIOCXaNqtbv1by61U3MKt2aXR9t6eF2XRE1cH3X/EQVnNBh5Tq1XJTS4UdTzT0Yjjl8b+A+ODSCRzTXG3Y9r1SzptFClunLUuOxhhbzOorNUnaUDRBPPDF02cM9FXRr4cyaoh/E2NRqs3cY6Y6VLY+45wH2ezGXWqZaqcOVtp68p2lAyue4m9jTxNUhWbV/Hr1354z+xhQ0kN/N1TadKFN9NUzeby3Rid/Ffx6OvhseyM6Mx3m1q1lOeKJwynerdbO7udc71i1FR9NUAKDo+zymRnnPK7FXvpo0qGMekqVafWdxyqI57j+K7Tho99WP9tzK55zh9Nw/TcRvdiIObc9KdXk+93IjHONXutXxezU2yukXNQAbOX103XOcTq8sznXHTcxUxqPnO0M0CTVwqPZ8feYN1MdIld88ZbLxz1Y108zYldvJ6rDMVPO5Nbrw2qPHgtEQj9NbcQGMG1Mz1XK9GtBkOZRXT8cxqcM1wt7/C880jpppneplfrSqPBu5Rf+0vinQ3yuGst0GtdN31dSOtym6ac7fhfzdmrWo9LauSOKPOGVGsgqh11SAWdUZ3RDP36LnwWaVaTFkX67lue9Yf/xMZ66+lqlBjxKaui0aXprKRltMpRAVwx+gUwl0pmgL1ZDHvy4FnMtRzxdBTBnfsHxxlcLuk8h7WH8mjcQq0GWIhmsyxFIxi+GTY0+AJw5b5PbvLhfEFgMXmatFUTkMLaCV8ZDHHzoeneXDVquPYjHR4MhnuWTsLsWpUocf+2VMpjx02j/GicFoKz7uoducqh1N0cpYPd8WAn632sn9ilcPxGP9xXFOsXeDiM1PlNJXriCEzzHTgKI/fsaeT4VoE40q4PlO5zAq3BVyGTOhXZNFyDhRLKByl83/KNY2PYP/Iq5RzwSiVqiPjuIRMngw+n1HN5U8VzgpVK7N2F0q8qkuFD6b/MhflEi5/Hhwyl7+U/zMyZptUwHfiOn0niyPku/xoMpcvleuhkFNI431Mi0yfea6RxW5WSef6YnZjnGdwSqlcIyU3lMSJ1t06N/IOJ4UsLp+WayqPjy4BPWphfI7rieKPOVzWdFW3Cqbi94pP5LlpN53LyCw7CahqVZ9K5brrLoUSIYz/LikUC6Sqn+luOuuyfoFq3XSXrQu5l/1ZK1N4LGr5qFRu6xKXFjJ5/OarnE928zCnHSer/lno4qy7fp1x5Bz3n+QOBctJu7sFM7g/5akclri08e9xldylhXWtku93bK683X3ldq8eu6pS9/ozzi3XulcCShbO4mPreozreqrkZ2XN6trzuNdwN1q5nLtkpabvqn6d1YeSuxtcr5ic1a+e1+lKLWh1VSXK+mFyVSZNvLdrTVd2g3V8hPt+z8rpKpI1qDN6Yin1pY5XC4ya9QbavNkK1XOHaObrvUKlibdtamXC5GtQx7Ln83vsii09dlX/zgZOWf6d/i3c3mZ1T1XDNczqyXgV14Kc+7MunTANKG+/6npYvcv7GNoY1LMOZTqocuNcr1pceZPGaHojlMlfxrGXqOxFrOsFrDzEajDIFYZaU1NsvPwfvHKN9/bumlxmsOhkBdn1otd7+E3/vL3/+6+E5R6Ua4BF2WbR6Q11Ostc2WTsieLtXWSw1NVY+ctQGF1tsBiAVpVFV28z6ONkowWEh2kgsKXKECfbTLKu3i6bDRYrTDBV2EDgmvoqoFIJTLORtmqD+l5TV1lpqjPDcDbAVg3ooCT2olQeEslVEhkLYHpZZ7WaKmt0QA80WNlQZ6i36WyMH2NNLeh4CEPkE+QSk9HWBDqPjOWcWAxmi0nfUGngMPoaEKymosFm4Dx0mxAHVqqsbdAzTppqbNWmBhswU1ejEmLjLYoqAbbBCuOZOHFynYFLze1rrY5zoxHHaI4wWWSrAewAo2uAVVX8HqQZcwBrZoq2qarjhJqqTXV/nsDMYGyw1ANBA5+oN8lWU5xsbaiYY6i0sSeKjmvBJZlAlaZ6fQ2TwzrG27sUunQVpkYDl0DxIs6AywnqTTYwg1V5yqxi7vIApU+2VutAqAqDqjVgA5xc101OUz34hUWuM1kMNxRbttnNBqMOCMUrTHXvrdPZGX6dSV9jrGGOpqu1getBA0B1ej2XXFEdiy+dBfhqqNVZOCG9wVpTVc/ZqKq1m6utbBLzUF0lgFjZDCc/1p6UFI/TKwrT1boB9ABR5zl56UIEFutr7XJNN1cHkSwG9j9W4GNZw8qUyWzjDBED+J1BEaDJZNFb5UhXLEYy2s4OOZKFbiRXG1gnT42ZCgNEE0NtADswIRpNNS7GDM02iBpZZzZDiOkqag2sQ5EfkHsYplpnk6t1VkA01HfXC5Dr8nC93FCvVxmO7J5XIhUJb2ZZq6mWRTY3HTOUTq5lGQTixTnQrKucq6sCwSAW602u/PGfO1Y3UpC0gEVDrZExla2VMwsLSuWSwszSKanFWjmnRC4qLizLydBmyJGpJXAfGSdPySnNLpxcKsOI4tSC0mlyYaacWjBNzs0pyIiTtVOLirUlJXJhsZyTX5SXo4VnOQXpeZMzcgqy5DSYV1BYKufl5OeUAmhpIZ+qQuVoSxhYvrY4PRtuU9Ny8nJKp8XJmTmlBQwzE0BT5aLU4tKc9Ml5qcVy0eTiosISLWBkAGxBTkFmMVDR5mtBCABKLyyaVpyTlV0aB5NK4WGcXFqcmqHNTy3OjWMcFoLIxTIfEg9cAoasLWOTS7JT8/LktJzSktJibWo+G8u0k1VQmM90NLkgI7U0p7BATtOCKKlpeVqFNxAlPS81Jz9OzkjNT83SlnQRYcNUcbrUwSZkaQu0xal5cXJJkTY9hzVAjznF2vRSPhJ0D5rI4+ymFxaUaCdNhgcwzkkCDJKt5SRAgFT4L51zxsUvAHEZTmlhcamLlSk5Jdo4ObU4p4SxkFlcCOwye8IMJuNk0CczXoHKL7MRe/Zn74BRbLYqYIY2NQ8ASxgbfxoL3qVtrjSYbcy31eBW0iNPpUr+jONeqyQBcOGseghc5Rlvgj9DZPGVR8lwXcHFluQ4Nf2y9AHe3WBV06++0QBZ0MpSCcSHiSWTphorj3RYButM6rpn1dUCMZjlGgX5UlcL06wuNrsHlHNBNFtqYEqTpcYGyUTWNcBTS818dSm2qEtVTwkYlZ78WwxWM6xUNY2GWns8jLWw9YxzUlNvNFnqVNG5+iptY5w51CZXcXA9CG6yVMXL3v8n34qO4FXwXDhH8MpRz9/HxfN3o2Z41v09382/Qx3RVDO3ZkQNpMPmeHO1eYSak//6O+tu30qjm3+B3fM7a9f/FadzIft/8vz574jQlvLFOUreyCWvU3LWm/zNn5xJIKePklePklN/kFc2kZcpOUHJ8ZeypOOt5KUscuxW8l+t5EUfcpSSFyh5npIjvchhb/JsCDk0mDzjTZ5JEZ9+qq/0VF9y8Mm+0sEB5Mm+5ImH/aQnkskBuByIIO3JZL8P2bc3UNqXQPYGkr1t4p54snvzAGk3JbseD5J2hZPHg8jOx4ZJO4+Sx2zh0mPDyKNwefQoeWRHX+kRSnb0JQ/7ke3bjkrbKdm2daa07SjZ1iZu3RItbZ1JtqaIWwBtSzTZ/FCgtHkA2Xyk83hKp/hQIHnQlzyYIj4QTjb5kI2byAY/cv8t5L71euk+StYDifV6sm6tj7SuN1nrQ9amiGtW+0lrepPVfuTeVd7SvQlklTe5J5ysXNEqraRkBcxY0Uru9iHLBpClcLM0gdy1JFi6i5Il83pJS4JJ2yI/qY2SRX5kUYq4EEYspKR1wUCplZIFA0nL/KNSCyXz7TOl+UfJ/DbR3hwt2WcSe4rYHE2akkkjzGicSxrg0vAHsYUTKyUWQLZQMq8Xmdcmmk3xkpkSUzypp6SOktoAMjeXzPEm1ZRUeZOqFNEYQQytRE9JyurKuaTiKNG1knJKZoWSmT69pJmUTA8k06aGS9OGk6nhZEoCKfMhpSV9pdJNpKQvKe5LJhWFSpOiSZF/gFQUSgrhUhhGCvL7SwWtJD/HT8rvT/JTxLxe/aW820gudOcmkInwfGIryfEj2VneUnYryfImmVo/KTOBaDN8Ja0f0SomyfAl6Wl9pPRNJK0PSU3xl1JbScooLynFn6S0iRPGxUoTjpLxcBk/k4wDEuNiyR1j+0h3BJGxY4KksX3ImNHe0pggMtqb3J7cS7q9lSTD7OReJLlNHOVFRqWISSP7SEmbyMhhXtLIPiTRK1xK3EQSYv2kBEpu8ye3+vpItw4gIwbFSiOSSXyEtxQ/gAyPC5SGbyJxMCcukMSliMO8yNDBntLQcBLrR2JTxCExgdKQTSQGnsUEkpgUcbAniQaI6KNkUHCENCiWRMElipJIAIzcRCJkTynCm0S0ibInkVPEgdA7cChJOTQgcLg0YAwJjyD9W0m/ENI3gdySQPpAdx9KwkJjpbC5JBTuQmNJiOQthQwgvfuQYFBycAQJgrlBrSQQRAocTgJAOwGU9IK+Xv2JfwDxbxP9QDi/P4ivD/FNEX16EW8Y6n2UeIUTT49gyfMo8QgmGoDV9CaSN5FSRJEESWIYEdtEgntJJIiQFFGAlkDhnuA2EfkTfATrl63Gw/7//EP/rxno+gtH/wskVOJ0CmVuZHN0cmVhbQplbmRvYmoKMiAwIG9iago8PC9Qcm9jU2V0IFsvUERGIC9UZXh0IC9JbWFnZUIgL0ltYWdlQyAvSW1hZ2VJXQovRm9udCA8PAovRjEgOCAwIFIKL0YyIDE1IDAgUgo+PgovRXh0R1N0YXRlIDw8Ci9HUzEgNyAwIFIKPj4KPj4KZW5kb2JqCjIyIDAgb2JqCjw8Ci9Qcm9kdWNlciAo/v8AbQBQAEQARgAgADcALgAxAC4AOSkKL1RpdGxlICj+/wBQAEQARgAgAHQAZQBzAHQpCi9DcmVhdGlvbkRhdGUgKDIwMTkwODI4MTQxNzEzKzAwJzAwJykKL01vZERhdGUgKDIwMTkwODI4MTQxNzEzKzAwJzAwJykKPj4KZW5kb2JqCjIzIDAgb2JqCjw8Ci9UeXBlIC9DYXRhbG9nCi9QYWdlcyAxIDAgUgovT3BlbkFjdGlvbiBbMyAwIFIgL1hZWiBudWxsIG51bGwgMV0KL1BhZ2VMYXlvdXQgL09uZUNvbHVtbgo+PgplbmRvYmoKeHJlZgowIDI0CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDg1NSAwMDAwMCBuIAowMDAwMDI3NTM3IDAwMDAwIG4gCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDIyMyAwMDAwMCBuIAowMDAwMDAwNDM0IDAwMDAwIG4gCjAwMDAwMDA2NDIgMDAwMDAgbiAKMDAwMDAwMDk1MCAwMDAwMCBuIAowMDAwMDAxMDExIDAwMDAwIG4gCjAwMDAwMDExNjMgMDAwMDAgbiAKMDAwMDAwMTcwMyAwMDAwMCBuIAowMDAwMDAyMDk5IDAwMDAwIG4gCjAwMDAwMDIxNjggMDAwMDAgbiAKMDAwMDAwMjQ3NiAwMDAwMCBuIAowMDAwMDAyODUyIDAwMDAwIG4gCjAwMDAwMTQyMjEgMDAwMDAgbiAKMDAwMDAxNDM4MCAwMDAwMCBuIAowMDAwMDE0OTI3IDAwMDAwIG4gCjAwMDAwMTUzMjMgMDAwMDAgbiAKMDAwMDAxNTM5MiAwMDAwMCBuIAowMDAwMDE1NzExIDAwMDAwIG4gCjAwMDAwMTYwODcgMDAwMDAgbiAKMDAwMDAyNzY2NCAwMDAwMCBuIAowMDAwMDI3ODIwIDAwMDAwIG4gCnRyYWlsZXIKPDwKL1NpemUgMjQKL1Jvb3QgMjMgMCBSCi9JbmZvIDIyIDAgUgovSUQgWzw5NWYzMzNhYmJmYWQ5Y2I4NGJmZjQ4MjI3ZGQwMzQ2MD4gPDk1ZjMzM2FiYmZhZDljYjg0YmZmNDgyMjdkZDAzNDYwPl0KPj4Kc3RhcnR4cmVmCjI3OTMwCiUlRU9G \ No newline at end of file diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Renderer/data/single.txt b/src/Marello/Bundle/PdfBundle/Tests/Unit/Renderer/data/single.txt new file mode 100644 index 000000000..88b6839d4 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Renderer/data/single.txt @@ -0,0 +1 @@ +JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXBlIC9QYWdlCi9QYXJlbnQgMSAwIFIKL01lZGlhQm94IFswIDAgNTk1LjI4MCA4NDEuODkwXQovVHJpbUJveCBbMC4wMDAgMC4wMDAgNTk1LjI4MCA4NDEuODkwXQovUmVzb3VyY2VzIDIgMCBSCi9Hcm91cCA8PCAvVHlwZSAvR3JvdXAgL1MgL1RyYW5zcGFyZW5jeSAvQ1MgL0RldmljZVJHQiA+PiAKL0NvbnRlbnRzIDQgMCBSPj4KZW5kb2JqCjQgMCBvYmoKPDwvRmlsdGVyIC9GbGF0ZURlY29kZSAvTGVuZ3RoIDE0MT4+CnN0cmVhbQp4nI2OsQoCQQxEU+crptQml4S9W2tBD6wUtxML4XTBTi38ffc8PNRCrBKYmTcDx4pV6ibizvOEamkwE1VFOmGRePhz8fS35ardGvJtzBTflXd7KLpRe3Ic7m+cCwZCBvoIiie41K6IMYg2M6QOmNCaDpTpSCCbIp1LFJtP8NfAl/ajIJi4+z8FDwKxOJwKZW5kc3RyZWFtCmVuZG9iagoxIDAgb2JqCjw8L1R5cGUgL1BhZ2VzCi9LaWRzIFszIDAgUiBdCi9Db3VudCAxCi9NZWRpYUJveCBbMCAwIDU5NS4yODAgODQxLjg5MF0KPj4KZW5kb2JqCjUgMCBvYmoKPDwvVHlwZSAvRXh0R1N0YXRlCi9CTSAvTm9ybWFsCi9jYSAxCi9DQSAxCj4+CmVuZG9iago2IDAgb2JqCjw8L1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUwCi9CYXNlRm9udCAvTVBERkFBK0RlamFWdVNlcmlmQ29uZGVuc2VkCi9FbmNvZGluZyAvSWRlbnRpdHktSAovRGVzY2VuZGFudEZvbnRzIFs3IDAgUl0KL1RvVW5pY29kZSA4IDAgUgo+PgplbmRvYmoKNyAwIG9iago8PC9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9DSURGb250VHlwZTIKL0Jhc2VGb250IC9NUERGQUErRGVqYVZ1U2VyaWZDb25kZW5zZWQKL0NJRFN5c3RlbUluZm8gOSAwIFIKL0ZvbnREZXNjcmlwdG9yIDEwIDAgUgovRFcgNTQwCi9XIFsgMzIgWyAyODYgMzYxIDQxNCA3NTQgNTcyIDg1NSA4MDEgMjQ3IDM1MSAzNTEgNDUwIDc1NCAyODYgMzA0IDI4NiAzMDMgXQogNDggNTcgNTcyIDU4IDU5IDMwMyA2MCA2MiA3NTQgNjMgWyA0ODIgOTAwIDY1MCA2NjEgNjg4IDcyMSA2NTcgNjI0IDcxOSA3ODUgMzU1IDM2MCA2NzIgNTk4IDkyMSA3ODcgNzM4IDYwNSA3MzggNjc3IDYxNiA2MDAgNzU4IDY1MCA5MjUgNjQxIDU5NCA2MjUgMzUxIDMwMyAzNTEgNzU0IDQ1MCA0NTAgNTM2IDU3NiA1MDQgNTc2IDUzMiAzMzMgNTc2IDU4MCAyODggMjc5IDU0NSAyODggODUzIDU4MCA1NDIgNTc2IDU3NiA0MzAgNDYxIDM2MSA1ODAgNTA4IDc3MCA1MDcgNTA4IDQ3NCA1NzIgMzAzIDU3MiA3NTQgXQogXQovQ0lEVG9HSURNYXAgMTEgMCBSCj4+CmVuZG9iago4IDAgb2JqCjw8L0xlbmd0aCAzNDY+PgpzdHJlYW0KL0NJREluaXQgL1Byb2NTZXQgZmluZHJlc291cmNlIGJlZ2luCjEyIGRpY3QgYmVnaW4KYmVnaW5jbWFwCi9DSURTeXN0ZW1JbmZvCjw8L1JlZ2lzdHJ5IChBZG9iZSkKL09yZGVyaW5nIChVQ1MpCi9TdXBwbGVtZW50IDAKPj4gZGVmCi9DTWFwTmFtZSAvQWRvYmUtSWRlbnRpdHktVUNTIGRlZgovQ01hcFR5cGUgMiBkZWYKMSBiZWdpbmNvZGVzcGFjZXJhbmdlCjwwMDAwPiA8RkZGRj4KZW5kY29kZXNwYWNlcmFuZ2UKMSBiZWdpbmJmcmFuZ2UKPDAwMDA+IDxGRkZGPiA8MDAwMD4KZW5kYmZyYW5nZQplbmRjbWFwCkNNYXBOYW1lIGN1cnJlbnRkaWN0IC9DTWFwIGRlZmluZXJlc291cmNlIHBvcAplbmQKZW5kCgplbmRzdHJlYW0KZW5kb2JqCjkgMCBvYmoKPDwvUmVnaXN0cnkgKEFkb2JlKQovT3JkZXJpbmcgKFVDUykKL1N1cHBsZW1lbnQgMAo+PgplbmRvYmoKMTAgMCBvYmoKPDwvVHlwZSAvRm9udERlc2NyaXB0b3IKL0ZvbnROYW1lIC9NUERGQUErRGVqYVZ1U2VyaWZ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wIDYyOSA2MjkgNDc0IDUwNiA0MTYgNjU0IDUyMyA3NzQgNTM2IDUyMyA1MTEgNTc5IDMyNyA1NzkgNzU0IF0KIF0KL0NJRFRvR0lETWFwIDE4IDAgUgo+PgplbmRvYmoKMTUgMCBvYmoKPDwvTGVuZ3RoIDM0Nj4+CnN0cmVhbQovQ0lESW5pdCAvUHJvY1NldCBmaW5kcmVzb3VyY2UgYmVnaW4KMTIgZGljdCBiZWdpbgpiZWdpbmNtYXAKL0NJRFN5c3RlbUluZm8KPDwvUmVnaXN0cnkgKEFkb2JlKQovT3JkZXJpbmcgKFVDUykKL1N1cHBsZW1lbnQgMAo+PiBkZWYKL0NNYXBOYW1lIC9BZG9iZS1JZGVudGl0eS1VQ1MgZGVmCi9DTWFwVHlwZSAyIGRlZgoxIGJlZ2luY29kZXNwYWNlcmFuZ2UKPDAwMDA+IDxGRkZGPgplbmRjb2Rlc3BhY2VyYW5nZQoxIGJlZ2luYmZyYW5nZQo8MDAwMD4gPEZGRkY+IDwwMDAwPgplbmRiZnJhbmdlCmVuZGNtYXAKQ01hcE5hbWUgY3VycmVudGRpY3QgL0NNYXAgZGVmaW5lcmVzb3VyY2UgcG9wCmVuZAplbmQKCmVuZHN0cmVhbQplbmRvYmoKMTYgMCBvYmoKPDwvUmVnaXN0cnkgKEFkb2JlKQovT3JkZXJpbmcgKFVDUykKL1N1cHBsZW1lbnQgMAo+PgplbmRvYmoKMTcgMCBvYmoKPDwvVHlwZSAvRm9udERlc2NyaXB0b3IKL0ZvbnROYW1lIC9NUERGQUErRGVqYVZ1U2VyaWZDb25kZW5zZWQtQm9sZAogL0NhcEhlaWdodCA3MjkKIC9YSGVpZ2h0IDUxOQogL0ZvbnRCQm94IFstNzUyIC0zODkgMTYxNyAxMTQ1XQogL0ZsYWdzIDI2MjE0OAogL0FzY2VudCA5MzkKIC9EZXNjZW50IC0yMzYKIC9MZWFkaW5nIDAKIC9JdGFsaWNBbmdsZSAwCiAvU3RlbVYgMTY1CiAvTWlzc2luZ1dpZHRoIDU0MAogL1N0eWxlIDw8IC9QYW5vc2UgPCAwIDAgMiA2IDggNiA1IDYgNSAyIDIgND4gPj4KL0ZvbnRGaWxlMiAxOSAwIFIKPj4KZW5kb2JqCjE4IDAgb2JqCjw8L0xlbmd0aCA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4vz+Irw/xTRF9ehFvGOp9lHiFE0+PYMnzKPEIJhqA1fQmkjeRUkSRBEliGBHbRIJ7SSSIkBRRgJZA4Z7gNhH5E3wE65etxsP+//xD/68Z6PoLR/8LJFTidAplbmRzdHJlYW0KZW5kb2JqCjIgMCBvYmoKPDwvUHJvY1NldCBbL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSV0KL0ZvbnQgPDwKL0YxIDYgMCBSCi9GMiAxMyAwIFIKPj4KL0V4dEdTdGF0ZSA8PAovR1MxIDUgMCBSCj4+Cj4+CmVuZG9iagoyMCAwIG9iago8PAovUHJvZHVjZXIgKP7/AG0AUABEAEYAIAA3AC4AMQAuADkpCi9UaXRsZSAo/v8AUABEAEYAIAB0AGUAcwB0KQovQ3JlYXRpb25EYXRlICgyMDE5MDgyODE0MTcxMyswMCcwMCcpCi9Nb2REYXRlICgyMDE5MDgyODE0MTcxMyswMCcwMCcpCj4+CmVuZG9iagoyMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMSAwIFIKL09wZW5BY3Rpb24gWzMgMCBSIC9YWVogbnVsbCBudWxsIDFdCi9QYWdlTGF5b3V0IC9PbmVDb2x1bW4KPj4KZW5kb2JqCnhyZWYKMCAyMgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDA0MzQgMDAwMDAgbiAKMDAwMDAyNzEwNiAwMDAwMCBuIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAyMjMgMDAwMDAgbiAKMDAwMDAwMDUyMyAwMDAwMCBuIAowMDAwMDAwNTg0IDAwMDAwIG4gCjAwMDAwMDA3MzUgMDAwMDAgbiAKMDAwMDAwMTI3NCAwMDAwMCBuIAowMDAwMDAxNjY5IDAwMDAwIG4gCjAwMDAwMDE3MzcgMDAwMDAgbiAKMDAwMDAwMjA0NSAwMDAwMCBuIAowMDAwMDAyNDIxIDAwMDAwIG4gCjAwMDAwMTM3OTAgMDAwMDAgbiAKMDAwMDAxMzk0OSAwMDAwMCBuIAowMDAwMDE0NDk2IDAwMDAwIG4gCjAwMDAwMTQ4OTIgMDAwMDAgbiAKMDAwMDAxNDk2MSAwMDAwMCBuIAowMDAwMDE1MjgwIDAwMDAwIG4gCjAwMDAwMTU2NTYgMDAwMDAgbiAKMDAwMDAyNzIzMyAwMDAwMCBuIAowMDAwMDI3Mzg5IDAwMDAwIG4gCnRyYWlsZXIKPDwKL1NpemUgMjIKL1Jvb3QgMjEgMCBSCi9JbmZvIDIwIDAgUgovSUQgWzwxMzY5NGYwNjYzMTgzNjkwMmZjNDM1ZTVlOThkODk4Zj4gPDEzNjk0ZjA2NjMxODM2OTAyZmM0MzVlNWU5OGQ4OThmPl0KPj4Kc3RhcnR4cmVmCjI3NDk5CiUlRU9G \ No newline at end of file diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Twig/Extension/DocumentTableExtensionTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Twig/Extension/DocumentTableExtensionTest.php new file mode 100644 index 000000000..66b558029 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Twig/Extension/DocumentTableExtensionTest.php @@ -0,0 +1,63 @@ +createMock(DocumentTableProvider::class); + + $this->extension = new DocumentTableExtension($tableProvider); + } + + public function testGetFilters() + { + $this->assertCount(0, $this->extension->getFilters()); + } + + public function testGetNodeVisitors() + { + $this->assertCount(0, $this->extension->getNodeVisitors()); + } + + public function testGetOperators() + { + $this->assertCount(0, $this->extension->getOperators()); + } + + public function testGetTests() + { + $this->assertCount(0, $this->extension->getTests()); + } + + public function testGetTokenParsers() + { + $this->assertCount(0, $this->extension->getTokenParsers()); + } + + public function testGetFunctions() + { + $functions = $this->extension->getFunctions(); + + $this->assertCount(1, $functions); + + /** @var TwigFunction $twigFunction */ + $twigFunction = reset($functions); + $this->assertEquals('get_document_tables', $twigFunction->getName()); + $this->assertTrue(is_callable($twigFunction->getCallable())); + } + + public function testGetName() + { + $this->assertEquals(DocumentTableExtension::NAME, $this->extension->getName()); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Workflow/Action/SendEmailTemplateAttachmentActionTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Workflow/Action/SendEmailTemplateAttachmentActionTest.php new file mode 100644 index 000000000..36f1c35ba --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Workflow/Action/SendEmailTemplateAttachmentActionTest.php @@ -0,0 +1,137 @@ +createMock(Processor::class); + /** @var EntityNameResolver|\PHPUnit_Framework_MockObject_MockObject $entityNameResolver */ + $entityNameResolver = $this->createMock(EntityNameResolver::class); + /** @var EmailRenderer|\PHPUnit_Framework_MockObject_MockObject $renderer */ + $renderer = $this->createMock(EmailRenderer::class); + /** @var ManagerRegistry|\PHPUnit_Framework_MockObject_MockObject $managerRegistry */ + $managerRegistry = $this->createMock(ManagerRegistry::class); + /** @var ValidatorInterface|\PHPUnit_Framework_MockObject_MockObject $validator */ + $validator = $this->createMock(ValidatorInterface::class); + /** @var EmailOriginHelper|\PHPUnit_Framework_MockObject_MockObject $emailOriginHelper */ + $emailOriginHelper = $this->getMockBuilder(EmailOriginHelper::class)->disableOriginalConstructor()->getMock(); + + $this->action = new SendEmailTemplateAttachmentAction( + new ContextAccessor(), + $emailProcessor, + new EmailAddressHelper(), + $entityNameResolver, + $managerRegistry, + $validator, + $emailOriginHelper, + $renderer, + ); + } + + /** + * @dataProvider initializeProvider + */ + public function testInitialize($options, $exception = null, $exceptionMessage = null) + { + if ($exception !== null) { + $this->expectException($exception); + if ($exceptionMessage !== null) { + $this->expectExceptionMessage($exceptionMessage); + } + } + + $this->action->initialize($options); + } + + public function initializeProvider() + { + $baseOptions = [ + 'from' => 'test@example.com', + 'to' => 'test@example.com', + 'template' => 'template-name', + 'subject' => 'subject', + 'entity' => new \stdClass(), + ]; + + return [ + 'valid bcc simple' => [ + 'options' => array_merge($baseOptions, [ + SendEmailTemplateAttachmentAction::OPTION_BCC => 'test@example.com', + ]), + ], + 'valid bcc array' => [ + 'options' => array_merge($baseOptions, [ + SendEmailTemplateAttachmentAction::OPTION_BCC => [ + 'email' => 'test@example.com', + 'name' => 'test bcc' + ], + ]), + ], + 'invalid bcc' => [ + 'options' => array_merge($baseOptions, [ + SendEmailTemplateAttachmentAction::OPTION_BCC => ['name' => 'test bcc'], + ]), + 'exception' => InvalidArgumentException::class, + 'exceptionMessage' => 'Email parameter is required', + ], + 'empty_bcc' => [ + 'options' => array_merge($baseOptions, [ + SendEmailTemplateAttachmentAction::OPTION_BCC => null, + ]), + ], + 'attachments not array' => [ + 'options' => array_merge($baseOptions, [ + SendEmailTemplateAttachmentAction::OPTION_ATTACHMENTS => 'attachments', + ]), + 'exception' => InvalidArgumentException::class, + 'exceptionMessage' => 'Attachments should be array', + ], + 'attachment options not array' => [ + 'options' => array_merge($baseOptions, [ + SendEmailTemplateAttachmentAction::OPTION_ATTACHMENTS => ['attachments'], + ]), + 'exception' => InvalidArgumentException::class, + 'exceptionMessage' => 'Attachment options invalid', + ], + 'body or file not set' => [ + 'options' => array_merge($baseOptions, [ + SendEmailTemplateAttachmentAction::OPTION_ATTACHMENTS => [ + [] + ], + ]), + 'exception' => InvalidArgumentException::class, + 'exceptionMessage' => 'Attachment option "body" or "file" should be set', + ], + 'body and file set' => [ + 'options' => array_merge($baseOptions, [ + SendEmailTemplateAttachmentAction::OPTION_ATTACHMENTS => [ + [ + SendEmailTemplateAttachmentAction::OPTION_ATTACHMENT_BODY => 'body', + SendEmailTemplateAttachmentAction::OPTION_ATTACHMENT_FILE => 'file', + ] + ], + ]), + 'exception' => InvalidArgumentException::class, + 'exceptionMessage' => 'Only one of options "body" and "file" should be set', + ], + ]; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Tests/Unit/Workflow/Condition/IsSendEmailTransitionTest.php b/src/Marello/Bundle/PdfBundle/Tests/Unit/Workflow/Condition/IsSendEmailTransitionTest.php new file mode 100644 index 000000000..2bbbd2c1a --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Tests/Unit/Workflow/Condition/IsSendEmailTransitionTest.php @@ -0,0 +1,94 @@ +configManager = $this->createMock(ConfigManager::class); + + $this->isSendTransition = new IsSendEmailTransition($this->configManager); + $this->isSendTransition->setContextAccessor(new ContextAccessor()); + } + + /** + * @dataProvider isConditionAllowedProvider + */ + public function testIsConditionAllowed($configTransition, $context, $options, $salesChannel, $allowed) + { + $this->configManager->expects($this->once()) + ->method('get') + ->with('marello_pdf.email_workflow_transition', false, false, $salesChannel) + ->willReturn($configTransition) + ; + + $this->isSendTransition->initialize($options); + + $this->assertEquals($allowed, $this->isSendTransition->isConditionAllowed($context)); + } + + public function isConditionAllowedProvider() + { + $salesChannel = $this->getEntity(SalesChannel::class, [ + 'id' => 1, + 'name' => 'test channel', + 'code' => 'test', + ]); + + $options = [ + IsSendEmailTransition::OPTION_CURRENT_TRANSITION => 'provided', + IsSendEmailTransition::OPTION_CONFIG_SCOPE => new PropertyPath('scope'), + ]; + + $context = [ + 'scope' => $salesChannel, + ]; + + return [ + 'not_set' => [ + 'configTransition' => null, + 'context' => $context, + 'options' => $options, + 'salesChannel' => $salesChannel, + 'allowed' => false, + ], + 'allowed' => [ + 'configTransition' => 'provided', + 'context' => $context, + 'options' => $options, + 'salesChannel' => $salesChannel, + 'allowed' => true, + ], + 'not_allowed' => [ + 'configTransition' => 'config', + 'context' => $context, + 'options' => $options, + 'salesChannel' => $salesChannel, + 'allowed' => false, + ], + ]; + } + + public function testGetName() + { + $this->assertEquals(IsSendEmailTransition::NAME, $this->isSendTransition->getName()); + } +} diff --git a/src/Marello/Bundle/PdfBundle/Twig/Extension/DocumentTableExtension.php b/src/Marello/Bundle/PdfBundle/Twig/Extension/DocumentTableExtension.php new file mode 100644 index 000000000..433344d6d --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Twig/Extension/DocumentTableExtension.php @@ -0,0 +1,31 @@ +tableProvider = $tableProvider; + } + + public function getFunctions() + { + return [ + new TwigFunction('get_document_tables', [$this->tableProvider, 'getTables']), + ]; + } + + public function getName() + { + return self::NAME; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Workflow/Action/SendEmailTemplateAttachmentAction.php b/src/Marello/Bundle/PdfBundle/Workflow/Action/SendEmailTemplateAttachmentAction.php new file mode 100644 index 000000000..00dcf56f6 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Workflow/Action/SendEmailTemplateAttachmentAction.php @@ -0,0 +1,375 @@ +registry = $registry; + $this->validator = $validator; + $this->renderer = $renderer; + $this->emailOriginHelper = $emailOriginHelper; + } + + /** + * @param array $options + * @return SendEmailTemplate + */ + public function initialize(array $options): self + { + if (isset($options[self::OPTION_BCC])) { + $this->assertEmailAddressOption($options[self::OPTION_BCC]); + } + if (empty($options['from'])) { + throw new InvalidParameterException('From parameter is required'); + } + + $this->assertEmailAddressOption($options['from']); + + if (empty($options['to'])) { + throw new InvalidParameterException('Need to specify "to" parameters'); + } + + $options = $this->normalizeToOption($options); + + if (empty($options['template'])) { + throw new InvalidParameterException('Template parameter is required'); + } + + if (empty($options['entity'])) { + throw new InvalidParameterException('Entity parameter is required'); + } + + if (isset($options[self::OPTION_ATTACHMENTS])) { + $attachments = $options[self::OPTION_ATTACHMENTS]; + + if (!is_array($attachments)) { + throw new InvalidArgumentException('Attachments should be array'); + } + foreach ($attachments as $attachment) { + if (!is_array($attachment)) { + throw new InvalidArgumentException('Attachment options invalid'); + } + + if (!isset($attachment[self::OPTION_ATTACHMENT_BODY]) + && !isset($attachment[self::OPTION_ATTACHMENT_FILE]) + ) { + throw new InvalidArgumentException(sprintf( + 'Attachment option "%s" or "%s" should be set', + self::OPTION_ATTACHMENT_BODY, + self::OPTION_ATTACHMENT_FILE + )); + } + if (isset($attachment[self::OPTION_ATTACHMENT_BODY]) + && isset($attachment[self::OPTION_ATTACHMENT_FILE]) + ) { + throw new InvalidArgumentException(sprintf( + 'Only one of options "%s" and "%s" should be set', + self::OPTION_ATTACHMENT_BODY, + self::OPTION_ATTACHMENT_FILE + )); + } + } + } + + $this->options = $options; + $this->emailConstraint = new EmailConstraints(['message' => 'Invalid email address']); + + return $this; + } + + /** + * @param mixed $context + * @throws EntityNotFoundException + * @throws \Twig\Error\Error + * @throws \Twig_Error + */ + public function executeAction($context): void + { + $emailModel = new Email(); + + $from = $this->getEmailAddress($context, $this->options['from']); + $this->validateAddress($from); + $emailModel->setFrom($from); + $to = []; + + foreach ($this->options['to'] as $email) { + if ($email) { + $address = $this->getEmailAddress($context, $email); + $this->validateAddress($address); + $to[] = $address; + } + } + $emailModel->setTo($to); + $entity = $this->contextAccessor->getValue($context, $this->options['entity']); + $template = $this->contextAccessor->getValue($context, $this->options['template']); + + $emailTemplate = $this + ->registry + ->getManagerForClass(\get_class($entity)) + ->getRepository(EmailTemplate::class) + ->findByName($template) + ; + if (!$emailTemplate) { + $errorMessage = sprintf('Template "%s" not found.', $template); + $this->logger->error('Workflow send email action.' . $errorMessage); + throw new EntityNotFoundException($errorMessage); + } + $templateData = $this->renderer->compileMessage($emailTemplate, ['entity' => $entity]); + + list ($subjectRendered, $templateRendered) = $templateData; + + $emailModel->setSubject($subjectRendered); + $emailModel->setBody($templateRendered); + $emailModel->setType($emailTemplate->getType()); + $emailModel->setBcc($this->getBcc($context)); + + $this->addAttachments($emailModel, $context); + + $emailUser = null; + try { + $emailOrigin = $this->emailOriginHelper->getEmailOrigin( + $emailModel->getFrom(), + $emailModel->getOrganization() + ); + + $emailUser = $this->emailProcessor->process($emailModel, $emailOrigin); + } catch (\Swift_SwiftException $exception) { + $this->logger->error('Workflow send email template action.', ['exception' => $exception]); + } + + if (array_key_exists('attribute', $this->options) && $emailUser instanceof EmailUser) { + $this->contextAccessor->setValue($context, $this->options['attribute'], $emailUser->getEmail()); + } + } + + /** + * @param $context + * @return array|string + */ + protected function getBcc($context) + { + if (isset($this->options[self::OPTION_BCC])) { + $bcc = $this->getEmailAddress($context, $this->options[self::OPTION_BCC]); + $this->validateAddress($bcc); + + $bcc = [$bcc]; + } else { + $bcc = []; + } + + return array_filter($bcc); + } + + /** + * @param Email $emailModel + * @param $context + */ + protected function addAttachments(Email $emailModel, $context) + { + if (isset($this->options[self::OPTION_ATTACHMENTS])) { + $attachments = $this->options[self::OPTION_ATTACHMENTS]; + foreach ($attachments as $attachment) { + $emailModel->addAttachment($this->buildAttachment($attachment, $context)); + } + } + } + + /** + * @param $attachment + * @param $context + * @return EmailAttachment + */ + protected function buildAttachment($attachment, $context) + { + if (isset($attachment[self::OPTION_ATTACHMENT_FILE])) { + $emailAttachment = $this->buildFileAttachment($attachment, $context); + } else { + $emailAttachment = $this->buildStringAttachment($attachment, $context); + } + + return $emailAttachment; + } + + /** + * @param $attachment + * @param $context + * @return EmailAttachment + */ + protected function buildFileAttachment($attachment, $context) + { + $path = $this->contextAccessor->getValue($context, $attachment[self::OPTION_ATTACHMENT_FILE]); + $content = base64_encode(file_get_contents($path)); + if (isset($attachment[self::OPTION_ATTACHMENT_MIMETYPE])) { + $mimetype = $this->contextAccessor->getValue($context, $attachment[self::OPTION_ATTACHMENT_MIMETYPE]); + } else { + $extension = pathinfo($path, PATHINFO_EXTENSION); + $mimetype = $this->getMimeTypeGuesser()->guess($extension); + } + if (isset($attachment[self::OPTION_ATTACHMENT_FILENAME])) { + $filename = $this->contextAccessor->getValue($context, $attachment[self::OPTION_ATTACHMENT_FILENAME]); + } else { + $filename = pathinfo($path, PATHINFO_BASENAME); + } + + return $this->buildAttachmentFromString($content, $filename, $mimetype); + } + + /** + * @param $attachment + * @param $context + * @return EmailAttachment + */ + protected function buildStringAttachment($attachment, $context) + { + $content = $this->contextAccessor->getValue($context, $attachment[self::OPTION_ATTACHMENT_BODY]); + $filename = $this->contextAccessor->getValue($context, $attachment[self::OPTION_ATTACHMENT_FILENAME]); + $mimetype = $this->contextAccessor->getValue($context, $attachment[self::OPTION_ATTACHMENT_MIMETYPE]); + + return $this->buildAttachmentFromString($content, $filename, $mimetype); + } + + /** + * @param $content + * @param $filename + * @param $mimetype + * @return EmailAttachment + */ + protected function buildAttachmentFromString($content, $filename, $mimetype) + { + $attachmentEntity = new AttachmentEntity(); + + $attachmentContent = new EmailAttachmentContent(); + $attachmentContent->setContent($content); + $attachmentContent->setContentTransferEncoding('base64'); + $attachmentContent->setEmailAttachment($attachmentEntity); + + $attachmentEntity->setContent($attachmentContent); + $attachmentEntity->setContentType($mimetype); + $attachmentEntity->setFileName($filename); + + $emailAttachment = new EmailAttachment(); + $emailAttachment->setType(EmailAttachment::TYPE_EMAIL_ATTACHMENT); + $emailAttachment->setEmailAttachment($attachmentEntity); + + return $emailAttachment; + } + + /** + * @return MimeTypeGuesser + */ + protected function getMimeTypeGuesser() + { + if ($this->mimeTypeGuesser === null) { + $this->mimeTypeGuesser = MimeTypeGuesser::getInstance(); + } + + return $this->mimeTypeGuesser; + } + + /** + * @param string $email + * @throws ValidatorException + */ + protected function validateAddress($email): void + { + $errorList = $this->validator->validate($email, $this->emailConstraint); + + if ($errorList && $errorList->count() > 0) { + throw new ValidatorException($errorList->get(0)->getMessage()); + } + } + + /** + * @param array $options + * @return array + */ + protected function normalizeToOption(array $options): array + { + if (empty($options['to'])) { + $options['to'] = []; + } + if (!is_array($options['to']) + || array_key_exists('name', $options['to']) + || array_key_exists('email', $options['to']) + ) { + $options['to'] = [$options['to']]; + } + + foreach ($options['to'] as $to) { + $this->assertEmailAddressOption($to); + } + + return $options; + } +} diff --git a/src/Marello/Bundle/PdfBundle/Workflow/Condition/IsSendEmailTransition.php b/src/Marello/Bundle/PdfBundle/Workflow/Condition/IsSendEmailTransition.php new file mode 100644 index 000000000..6bbcef491 --- /dev/null +++ b/src/Marello/Bundle/PdfBundle/Workflow/Condition/IsSendEmailTransition.php @@ -0,0 +1,70 @@ +configManager = $configManager; + } + + public function initialize(array $options) + { + $this->currentTransition = $options[self::OPTION_CURRENT_TRANSITION]; + $this->configScope = $options[self::OPTION_CONFIG_SCOPE]; + + return $this; + } + + public function isConditionAllowed($context) + { + $currentTransition = $this->contextAccessor->getValue($context, $this->currentTransition); + $configScope = $this->contextAccessor->getValue($context, $this->configScope); + + $configKey = sprintf('%s.%s', Configuration::CONFIG_NAME, Configuration::CONFIG_KEY_EMAIL_WORKFLOW_TRANSITION); + + $configuredTransitions = $this->configManager->get($configKey, false, false, $configScope); + if ($configuredTransitions === null) { + $configuredTransitions = []; + } + if (is_scalar($configuredTransitions)) { + $configuredTransitions = [$configuredTransitions]; + } + + return in_array($currentTransition, $configuredTransitions); + } + + public function getName() + { + return self::NAME; + } + + public function toArray() + { + return $this->convertToArray([$this->currentTransition]); + } + + public function compile($factoryAccessor) + { + return $this->convertToPhpCode([$this->currentTransition], $factoryAccessor); + } +} diff --git a/src/Marello/Bundle/PricingBundle/Controller/PricingController.php b/src/Marello/Bundle/PricingBundle/Controller/PricingController.php index 062061631..9a7db55d3 100644 --- a/src/Marello/Bundle/PricingBundle/Controller/PricingController.php +++ b/src/Marello/Bundle/PricingBundle/Controller/PricingController.php @@ -2,17 +2,20 @@ namespace Marello\Bundle\PricingBundle\Controller; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; +use Symfony\Component\Routing\Annotation\Route; -class PricingController extends Controller +class PricingController extends AbstractController { /** - * @Config\Route("/get-currency-by-channel", name="marello_pricing_currency_by_channel") - * @Config\Method({"GET"}) + * @Route( + * path="/get-currency-by-channel", + * methods={"GET"}, + * name="marello_pricing_currency_by_channel" + * ) * @AclAncestor("marello_sales_saleschannel_view") * * {@inheritdoc} diff --git a/src/Marello/Bundle/PricingBundle/EventListener/Datagrid/ChannelPricesDatagridListener.php b/src/Marello/Bundle/PricingBundle/EventListener/Datagrid/ChannelPricesDatagridListener.php new file mode 100644 index 000000000..d3445f71b --- /dev/null +++ b/src/Marello/Bundle/PricingBundle/EventListener/Datagrid/ChannelPricesDatagridListener.php @@ -0,0 +1,190 @@ +relatedEntityClass = $relatedEntityClass; + + $this->expressionBuilder = new Expr(); + } + + /** + * @param BuildBefore $event + */ + public function onBuildBefore(BuildBefore $event) + { + $config = $event->getConfig(); + + $this->addSelect($config); + $this->addJoin($config); + $this->addColumn($config); + $this->addSorter($config); + $this->addFilter($config); + } + + /** + * @param DatagridConfiguration $configuration + * @return string + * @throws \InvalidArgumentException when a root entity not found in the grid + */ + protected function getAlias(DatagridConfiguration $configuration) + { + $rootAlias = $configuration->getOrmQuery()->getRootAlias(); + if (!$rootAlias) { + throw new \InvalidArgumentException( + sprintf( + 'A root entity is missing for grid "%s"', + $configuration->getName() + ) + ); + } + + return $rootAlias; + } + + /** + * @return string + */ + protected function getDataName() + { + return self::DATA_NAME; + } + + /** + * @return string + */ + protected function getJoinAlias() + { + return self::JOIN_ALIAS; + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSelect(DatagridConfiguration $config) + { + $ormQuery = $config->getOrmQuery(); + $ormQuery + ->addSelect( + sprintf( + 'GROUP_CONCAT( + DISTINCT CONCAT_WS(\'|\', %1$s.name, %2$s.value, %2$s.currency) SEPARATOR \';\' + ) as defaultChannelPrices', + self::CHANNEL_JOIN_ALIAS, + self::DEFAULT_JOIN_ALIAS + ) + ) + ->addSelect( + sprintf( + 'GROUP_CONCAT( + DISTINCT CONCAT_WS(\'|\', %1$s.name, %2$s.value, %2$s.currency) SEPARATOR \';\' + ) as specialChannelPrices', + self::CHANNEL_JOIN_ALIAS, + self::SPECIAL_JOIN_ALIAS + ) + ) + ->addSelect(sprintf('sum(%s.value) as defaultChannelPricesSum', self::DEFAULT_JOIN_ALIAS)) + ->addSelect(sprintf('sum(%s.value) as specialChannelPricesSum', self::SPECIAL_JOIN_ALIAS)); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addJoin(DatagridConfiguration $config) + { + $ormQuery = $config->getOrmQuery(); + $ormQuery + ->addLeftJoin(sprintf('%s.%s', $this->getAlias($config), self::DATA_NAME), $this->getJoinAlias()) + ->addLeftJoin(sprintf('%s.%s', $this->getJoinAlias(), self::CHANNEL_DATA_NAME), self::CHANNEL_JOIN_ALIAS) + ->addLeftJoin(sprintf('%s.%s', $this->getJoinAlias(), self::DEFAULT_DATA_NAME), self::DEFAULT_JOIN_ALIAS) + ->addLeftJoin(sprintf('%s.%s', $this->getJoinAlias(), self::SPECIAL_DATA_NAME), self::SPECIAL_JOIN_ALIAS); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addColumn(DatagridConfiguration $config) + { + $config->offsetSetByPath(sprintf('[columns][%s]', 'defaultChannelPrices'), [ + 'label' => 'marello.pricing.assembledchannelpricelist.default_price.plural_label', + 'type' => 'twig', + 'frontend_type' => 'html', + 'template' => 'MarelloPricingBundle:Datagrid/Property:defaultChannelPrices.html.twig', + 'renderable' => false, + 'align' => 'right' + ]); + $config->offsetSetByPath(sprintf('[columns][%s]', 'specialChannelPrices'), [ + 'label' => 'marello.pricing.assembledchannelpricelist.special_price.plural_label', + 'type' => 'twig', + 'frontend_type' => 'html', + 'template' => 'MarelloPricingBundle:Datagrid/Property:specialChannelPrices.html.twig', + 'renderable' => false, + 'align' => 'right' + ]); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSorter(DatagridConfiguration $config) + { + $config + ->offsetSetByPath( + sprintf('[sorters][columns][%s]', 'defaultChannelPrices'), + ['data_name' => 'defaultChannelPricesSum'] + ) + ->offsetSetByPath( + sprintf('[sorters][columns][%s]', 'specialChannelPrices'), + ['data_name' => 'specialChannelPricesSum'] + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addFilter(DatagridConfiguration $config) + { + $config->offsetSetByPath( + sprintf('[filters][columns][%s]', 'defaultChannelPrices'), + [ + 'type' => 'number', + 'data_name' => self::DEFAULT_JOIN_ALIAS . '.value', + 'enabled' => false, + ] + ); + $config->offsetSetByPath( + sprintf('[filters][columns][%s]', 'specialChannelPrices'), + [ + 'type' => 'number', + 'data_name' => self::SPECIAL_JOIN_ALIAS . '.value', + 'enabled' => false, + ] + ); + } +} diff --git a/src/Marello/Bundle/PricingBundle/EventListener/Datagrid/PricesDatagridListener.php b/src/Marello/Bundle/PricingBundle/EventListener/Datagrid/PricesDatagridListener.php new file mode 100644 index 000000000..0a0fa4b17 --- /dev/null +++ b/src/Marello/Bundle/PricingBundle/EventListener/Datagrid/PricesDatagridListener.php @@ -0,0 +1,208 @@ +relatedEntityClass = $relatedEntityClass; + + $this->expressionBuilder = new Expr(); + } + + /** + * @param BuildBefore $event + */ + public function onBuildBefore(BuildBefore $event) + { + $config = $event->getConfig(); + + $this->addSelect($config); + $this->addJoin($config); + $this->addColumn($config); + $this->addSorter($config); + $this->addFilter($config); + } + + /** + * @param DatagridConfiguration $configuration + * @return string + * @throws \InvalidArgumentException when a root entity not found in the grid + */ + protected function getAlias(DatagridConfiguration $configuration) + { + $rootAlias = $configuration->getOrmQuery()->getRootAlias(); + if (!$rootAlias) { + throw new \InvalidArgumentException( + sprintf( + 'A root entity is missing for grid "%s"', + $configuration->getName() + ) + ); + } + + return $rootAlias; + } + + /** + * @return string + */ + protected function getDataName() + { + return self::DATA_NAME; + } + + /** + * @return string + */ + protected function getJoinAlias() + { + return self::JOIN_ALIAS; + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSelect(DatagridConfiguration $config) + { + $ormQuery = $config->getOrmQuery(); + $ormQuery + ->addSelect( + sprintf( + 'GROUP_CONCAT( + DISTINCT CONCAT_WS(\'|\', %1$s.value, %1$s.currency) SEPARATOR \';\' + ) as defaultPrices', + self::DEFAULT_JOIN_ALIAS + ) + ) + ->addSelect( + sprintf( + 'GROUP_CONCAT( + DISTINCT CONCAT_WS(\'|\', %1$s.value, %1$s.currency) SEPARATOR \';\' + ) as specialPrices', + self::SPECIAL_JOIN_ALIAS + ) + ) + ->addSelect( + sprintf( + 'GROUP_CONCAT( + DISTINCT CONCAT_WS(\'|\', %1$s.value, %1$s.currency) SEPARATOR \';\' + ) as msrpPrices', + self::MSRP_JOIN_ALIAS + ) + ) + ->addSelect(sprintf('SUM(%s.value) as defaultPricesSum', self::DEFAULT_JOIN_ALIAS)) + ->addSelect(sprintf('SUM(%s.value) as specialPricesSum', self::SPECIAL_JOIN_ALIAS)) + ->addSelect(sprintf('SUM(%s.value) as msrpPricesSum', self::MSRP_JOIN_ALIAS)); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addJoin(DatagridConfiguration $config) + { + $ormQuery = $config->getOrmQuery(); + $ormQuery + ->addLeftJoin(sprintf('%s.%s', $this->getAlias($config), self::DATA_NAME), $this->getJoinAlias()) + ->addLeftJoin(sprintf('%s.%s', $this->getJoinAlias(), self::DEFAULT_DATA_NAME), self::DEFAULT_JOIN_ALIAS) + ->addLeftJoin(sprintf('%s.%s', $this->getJoinAlias(), self::SPECIAL_DATA_NAME), self::SPECIAL_JOIN_ALIAS) + ->addLeftJoin(sprintf('%s.%s', $this->getJoinAlias(), self::MSRP_DATA_NAME), self::MSRP_JOIN_ALIAS); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addColumn(DatagridConfiguration $config) + { + $config->offsetSetByPath(sprintf('[columns][%s]', 'defaultPrices'), [ + 'label' => 'marello.pricing.assembledpricelist.default_price.plural_label', + 'type' => 'twig', + 'frontend_type' => 'html', + 'template' => 'MarelloPricingBundle:Datagrid/Property:defaultPrices.html.twig', + 'renderable' => false, + 'align' => 'right' + ]); + $config->offsetSetByPath(sprintf('[columns][%s]', 'specialPrices'), [ + 'label' => 'marello.pricing.assembledpricelist.special_price.plural_label', + 'type' => 'twig', + 'frontend_type' => 'html', + 'template' => 'MarelloPricingBundle:Datagrid/Property:specialPrices.html.twig', + 'renderable' => false, + 'align' => 'right' + ]); + $config->offsetSetByPath(sprintf('[columns][%s]', 'msrpPrices'), [ + 'label' => 'marello.pricing.assembledpricelist.msrp_price.plural_label', + 'type' => 'twig', + 'frontend_type' => 'html', + 'template' => 'MarelloPricingBundle:Datagrid/Property:msrpPrices.html.twig', + 'renderable' => false, + 'align' => 'right' + ]); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSorter(DatagridConfiguration $config) + { + $config + ->offsetSetByPath(sprintf('[sorters][columns][%s]', 'defaultPrices'), ['data_name' => 'defaultPricesSum']) + ->offsetSetByPath(sprintf('[sorters][columns][%s]', 'specialPrices'), ['data_name' => 'specialPricesSum']) + ->offsetSetByPath(sprintf('[sorters][columns][%s]', 'msrpPrices'), ['data_name' => 'msrpPricesSum']); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addFilter(DatagridConfiguration $config) + { + $config->offsetSetByPath( + sprintf('[filters][columns][%s]', 'defaultPrices'), + [ + 'type' => 'number', + 'data_name' => self::DEFAULT_JOIN_ALIAS . '.value', + 'enabled' => false, + ] + ); + $config->offsetSetByPath( + sprintf('[filters][columns][%s]', 'specialPrices'), + [ + 'type' => 'number', + 'data_name' => self::SPECIAL_JOIN_ALIAS . '.value', + 'enabled' => false, + ] + ); + $config->offsetSetByPath( + sprintf('[filters][columns][%s]', 'msrpPrices'), + [ + 'type' => 'number', + 'data_name' => self::MSRP_JOIN_ALIAS . '.value', + 'enabled' => false, + ] + ); + } +} diff --git a/src/Marello/Bundle/PricingBundle/Form/EventListener/PricingSubscriber.php b/src/Marello/Bundle/PricingBundle/Form/EventListener/PricingSubscriber.php index dded8a447..33ee0d1d3 100644 --- a/src/Marello/Bundle/PricingBundle/Form/EventListener/PricingSubscriber.php +++ b/src/Marello/Bundle/PricingBundle/Form/EventListener/PricingSubscriber.php @@ -11,7 +11,7 @@ use Marello\Bundle\PricingBundle\Entity\ProductPrice; use Marello\Bundle\PricingBundle\Provider\CurrencyProvider; use Marello\Bundle\PricingBundle\Model\PricingAwareInterface; -use Marello\Bundle\SalesBundle\Model\SalesChannelAwareInterface; +use Marello\Bundle\SalesBundle\Model\SalesChannelsAwareInterface; use Marello\Bundle\PricingBundle\Entity\AssembledPriceList; use Marello\Bundle\PricingBundle\Entity\PriceType; use Marello\Bundle\PricingBundle\Form\Type\AssembledPriceListCollectionType; @@ -67,7 +67,7 @@ public function preSetData(FormEvent $event) $entity = $event->getData(); $form = $event->getForm(); - if ($entity instanceof PricingAwareInterface && $entity instanceof SalesChannelAwareInterface) { + if ($entity instanceof PricingAwareInterface && $entity instanceof SalesChannelsAwareInterface) { $currencies = $this->provider->getCurrencies($entity->getChannels()); if ($entity->hasPrices()) { $existingCurrencies = []; diff --git a/src/Marello/Bundle/PricingBundle/Form/Type/AssembledChannelPriceListType.php b/src/Marello/Bundle/PricingBundle/Form/Type/AssembledChannelPriceListType.php index 35437b5be..2f282883c 100644 --- a/src/Marello/Bundle/PricingBundle/Form/Type/AssembledChannelPriceListType.php +++ b/src/Marello/Bundle/PricingBundle/Form/Type/AssembledChannelPriceListType.php @@ -8,6 +8,7 @@ use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\NotNull; class AssembledChannelPriceListType extends AbstractType { @@ -27,6 +28,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ]) ->add('defaultPrice', ProductChannelPriceType::class, [ 'required' => true, + 'allowed_empty_value' => false ]) ->add('specialPrice', ProductChannelPriceType::class, [ 'required' => false, diff --git a/src/Marello/Bundle/PricingBundle/Form/Type/ProductChannelPriceType.php b/src/Marello/Bundle/PricingBundle/Form/Type/ProductChannelPriceType.php index b2bc176f4..045c5292a 100644 --- a/src/Marello/Bundle/PricingBundle/Form/Type/ProductChannelPriceType.php +++ b/src/Marello/Bundle/PricingBundle/Form/Type/ProductChannelPriceType.php @@ -9,6 +9,7 @@ use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\NotNull; class ProductChannelPriceType extends AbstractType { @@ -28,6 +29,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ]) ->add('value', OroMoneyType::class, [ 'required' => false, + 'constraints' => $options['allowed_empty_value'] === false ? new NotNull() : null, 'label' => 'marello.pricing.productprice.value.label', ]); } @@ -41,7 +43,8 @@ public function configureOptions(OptionsResolver $resolver) 'data_class' => ProductChannelPrice::class, 'intention' => 'productchannelprice', 'single_form' => true, - 'excluded_channels' => [] + 'excluded_channels' => [], + 'allowed_empty_value' => true ]); } diff --git a/src/Marello/Bundle/PricingBundle/Formatter/LabelVATAwareFormatter.php b/src/Marello/Bundle/PricingBundle/Formatter/LabelVATAwareFormatter.php index 49113744a..854d5612b 100644 --- a/src/Marello/Bundle/PricingBundle/Formatter/LabelVATAwareFormatter.php +++ b/src/Marello/Bundle/PricingBundle/Formatter/LabelVATAwareFormatter.php @@ -4,7 +4,7 @@ use Marello\Bundle\PricingBundle\DependencyInjection\Configuration; use Oro\Bundle\ConfigBundle\Config\ConfigManager; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; class LabelVATAwareFormatter { diff --git a/src/Marello/Bundle/PricingBundle/Model/PricingAwareInterface.php b/src/Marello/Bundle/PricingBundle/Model/PricingAwareInterface.php index d53d3ad7c..a29862f1b 100644 --- a/src/Marello/Bundle/PricingBundle/Model/PricingAwareInterface.php +++ b/src/Marello/Bundle/PricingBundle/Model/PricingAwareInterface.php @@ -5,7 +5,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Marello\Bundle\PricingBundle\Entity\AssembledChannelPriceList; use Marello\Bundle\PricingBundle\Entity\AssembledPriceList; -use Marello\Bundle\PricingBundle\Entity\ProductChannelPrice; interface PricingAwareInterface { diff --git a/src/Marello/Bundle/PricingBundle/Resources/config/jsmodules.yml b/src/Marello/Bundle/PricingBundle/Resources/config/jsmodules.yml new file mode 100644 index 000000000..8874ecdc2 --- /dev/null +++ b/src/Marello/Bundle/PricingBundle/Resources/config/jsmodules.yml @@ -0,0 +1,4 @@ +dynamic-imports: + marellopricing: + - marellopricing/js/app/views/channel-pricing-item-view + - marellopricing/js/app/views/channel-pricing-items-view \ No newline at end of file diff --git a/src/Marello/Bundle/PricingBundle/Resources/config/oro/twig.yml b/src/Marello/Bundle/PricingBundle/Resources/config/oro/twig.yml new file mode 100644 index 000000000..7532b70f4 --- /dev/null +++ b/src/Marello/Bundle/PricingBundle/Resources/config/oro/twig.yml @@ -0,0 +1,2 @@ +bundles: + - MarelloPricingBundle:Form:fields.html.twig diff --git a/src/Marello/Bundle/PricingBundle/Resources/config/services.yml b/src/Marello/Bundle/PricingBundle/Resources/config/services.yml index cefb7c7fb..3fd6760df 100644 --- a/src/Marello/Bundle/PricingBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/PricingBundle/Resources/config/services.yml @@ -1,6 +1,7 @@ services: marello_productprice.pricing.provider.currency_provider: class: Marello\Bundle\PricingBundle\Provider\CurrencyProvider + public: true arguments: - '@doctrine' - '@oro_locale.settings' @@ -64,3 +65,17 @@ services: - '@marello_productprice.pricing.formatter.vat_aware_label' tags: - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before.marello-productchannelprice-grid, method: onBuildBefore } + + marello_productprice.pricing.listener.datagrid.products_grid.prices: + class: 'Marello\Bundle\PricingBundle\EventListener\Datagrid\PricesDatagridListener' + arguments: + - 'Marello\Bundle\ProductBundle\Entity\Product' + tags: + - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before.marello-products-grid, method: onBuildBefore } + + marello_productprice.pricing.listener.datagrid.products_grid.channel_prices: + class: 'Marello\Bundle\PricingBundle\EventListener\Datagrid\ChannelPricesDatagridListener' + arguments: + - 'Marello\Bundle\ProductBundle\Entity\Product' + tags: + - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before.marello-products-grid, method: onBuildBefore } \ No newline at end of file diff --git a/src/Marello/Bundle/PricingBundle/Resources/public/js/app/views/channel-pricing-item-view.js b/src/Marello/Bundle/PricingBundle/Resources/public/js/app/views/channel-pricing-item-view.js index 54fefe60b..d42ff5c6d 100644 --- a/src/Marello/Bundle/PricingBundle/Resources/public/js/app/views/channel-pricing-item-view.js +++ b/src/Marello/Bundle/PricingBundle/Resources/public/js/app/views/channel-pricing-item-view.js @@ -1,7 +1,7 @@ define(function(require) { 'use strict'; - var ChannelPricingItemView, + const $ = require('jquery'), _ = require('underscore'), mediator = require('oroui/js/mediator'), @@ -12,7 +12,7 @@ define(function(require) { * @extends marellolayout.app.views.AbstractItemView * @class marellopricing.app.views.ChannelPricingItemView */ - ChannelPricingItemView = AbstractItemView.extend({ + const ChannelPricingItemView = AbstractItemView.extend({ /** * @property {Object} */ diff --git a/src/Marello/Bundle/PricingBundle/Resources/public/js/app/views/channel-pricing-items-view.js b/src/Marello/Bundle/PricingBundle/Resources/public/js/app/views/channel-pricing-items-view.js index 9b8179372..f6b027f17 100644 --- a/src/Marello/Bundle/PricingBundle/Resources/public/js/app/views/channel-pricing-items-view.js +++ b/src/Marello/Bundle/PricingBundle/Resources/public/js/app/views/channel-pricing-items-view.js @@ -1,7 +1,7 @@ define(function(require) { 'use strict'; - var PricingItemsView, + const $ = require('jquery'), __ = require('orotranslation/js/translator'), routing = require('routing'), @@ -14,7 +14,7 @@ define(function(require) { * @extends marellolayout.app.views.AbstractItemsView * @class marellopricing.app.views.PricingItemsView */ - PricingItemsView = AbstractItemsView.extend({ + const PricingItemsView = AbstractItemsView.extend({ /** * @property {Object} */ diff --git a/src/Marello/Bundle/PricingBundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/PricingBundle/Resources/translations/messages.en.yml index bc71b2666..7d22bea85 100644 --- a/src/Marello/Bundle/PricingBundle/Resources/translations/messages.en.yml +++ b/src/Marello/Bundle/PricingBundle/Resources/translations/messages.en.yml @@ -45,7 +45,6 @@ marello: No configured channels found for the current product. Please make sure Sales Channels exist and linked. no_prices_configured: No prices configured for current product - type.label: Price Type pricetype: entity_label: Price Type diff --git a/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/defaultChannelPrices.html.twig b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/defaultChannelPrices.html.twig new file mode 100644 index 000000000..63a2d5436 --- /dev/null +++ b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/defaultChannelPrices.html.twig @@ -0,0 +1,7 @@ +{% set prices = record.getValue('defaultChannelPrices')|split(';') %} +{% for index, valueSet in prices %} + {% set values = valueSet|split('|') %} + {% if values|length == 3 %} + {{ values[0] }}: {{ values[1]|oro_format_currency({'currency': values[2]}) }}
+ {% endif %} +{% endfor %} diff --git a/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/defaultPrices.html.twig b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/defaultPrices.html.twig new file mode 100644 index 000000000..cac4859ba --- /dev/null +++ b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/defaultPrices.html.twig @@ -0,0 +1,7 @@ +{% set prices = record.getValue('defaultPrices')|split(';') %} +{% for index, valueSet in prices %} + {% set values = valueSet|split('|') %} + {% if values|length == 2 %} + {{ values[0]|oro_format_currency({'currency': values[1]}) }}
+ {% endif %} +{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/msrpPrices.html.twig b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/msrpPrices.html.twig new file mode 100644 index 000000000..c2bd44916 --- /dev/null +++ b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/msrpPrices.html.twig @@ -0,0 +1,7 @@ +{% set prices = record.getValue('msrpPrices')|split(';') %} +{% for index, valueSet in prices %} + {% set values = valueSet|split('|') %} + {% if values|length == 2 %} + {{ values[0]|oro_format_currency({'currency': values[1]}) }}
+ {% endif %} +{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/specialChannelPrices.html.twig b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/specialChannelPrices.html.twig new file mode 100644 index 000000000..b625f13de --- /dev/null +++ b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/specialChannelPrices.html.twig @@ -0,0 +1,7 @@ +{% set prices = record.getValue('specialChannelPrices')|split(';') %} +{% for index, valueSet in prices %} + {% set values = valueSet|split('|') %} + {% if values|length == 3 %} + {{ values[0] }}: {{ values[1]|oro_format_currency({'currency': values[2]}) }}
+ {% endif %} +{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/specialPrices.html.twig b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/specialPrices.html.twig new file mode 100644 index 000000000..7791957fb --- /dev/null +++ b/src/Marello/Bundle/PricingBundle/Resources/views/Datagrid/Property/specialPrices.html.twig @@ -0,0 +1,7 @@ +{% set prices = record.getValue('specialPrices')|split(';') %} +{% for index, valueSet in prices %} + {% set values = valueSet|split('|') %} + {% if values|length == 2 %} + {{ values[0]|oro_format_currency({'currency': values[1]}) }}
+ {% endif %} +{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/PricingBundle/Resources/views/Form/fields.html.twig b/src/Marello/Bundle/PricingBundle/Resources/views/Form/fields.html.twig index da57a0f41..d21d72418 100644 --- a/src/Marello/Bundle/PricingBundle/Resources/views/Form/fields.html.twig +++ b/src/Marello/Bundle/PricingBundle/Resources/views/Form/fields.html.twig @@ -1,176 +1,3 @@ -{% block marello_product_channel_price_widget %} - -
- {{ form_widget(form.channel) }} -
- {{ form_errors(form.channel) }} - - -
-
- {{ marello_pricing_get_currency_data({'salesChannel':form.channel.vars.value}) }} -
- {{ form_widget(form.currency) }} -
- {{ form_errors(form.currency) }} - - -
- {{ form_widget(form.value) }} -
- {{ form_errors(form.value) }} - -{% endblock %} - -{% macro marello_product_channel_price_collection_item_prototype(widget) %} - {% if 'collection' in widget.vars.block_prefixes %} - {% set form = widget.vars.prototype %} - {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} - {% set disabled = widget.vars.disabled %} - {% set allow_delete = widget.vars.allow_delete %} - {% set allow_add_after = widget.vars.allow_add_after %} - {% else %} - {% set form = widget %} - {% set name = widget.vars.full_name %} - {% set disabled = widget.parent.vars.disabled %} - {% set allow_delete = widget.parent.vars.allow_delete %} - {% set allow_add_after = widget.parent.vars.allow_add_after %} - {% endif %} - - {{ form_widget(form, {disabled: disabled}) }} - {% if allow_delete %} - - - - {% elseif widget.parent.vars.allow_delete %} - - {% endif %} - -{% endmacro %} - -{% block marello_product_channel_price_collection_widget %} - {% spaceless %} - {% if prototype is defined %} - {% set prototype_html = _self.marello_product_channel_price_collection_item_prototype(form) %} - {% endif %} - {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'marello-item-collection grid-container' }) %} - {% set id = id ~ '_collection' %} -
- {{ form.parent.pricing_enabled is defined ? form_row(form.parent.pricing_enabled): '' }} - {% set prototype_name = form.vars.prototype_name %} -
- - - - - - - {% if form.vars.allow_delete %} - - {% endif %} - - - - {% if form.children|length %} - {% for child in form.children %} - {{ _self.marello_product_channel_price_collection_item_prototype(child) }} - {% endfor %} - {% elseif show_form_when_empty and prototype_html is defined %} - {{ prototype_html|replace({(prototype_name): '0'})|raw }} - {% endif %} - -
{{ 'marello.sales.saleschannel.entity_label'|trans }}{{ 'marello.pricing.productchannelprice.currency.label'|trans }}{{ marello_pricing_vat_aware_label('marello.pricing.productchannelprice.value.label') }}
- {% if allow_add %} - {{ form.vars.add_label|default('marello.pricing.productchannelprice.form.add_channel_price')|trans }} - {% endif %} -
-
- {% if handle_primary and (prototype is not defined or prototype.primary is defined) %} - {{ _self.oro_collection_validate_primary_js(_context) }} - {% endif %} - {% endspaceless %} -{% endblock %} - -{% block marello_product_price_widget %} - -
-
- {{ marello_pricing_get_currency_data({'currencyCode' : form.currency.vars.value}) }} -
- {{ form_widget(form.currency) }} -
- {{ form_errors(form.currency) }} - - -
- {{ form_widget(form.value) }} -
- {{ form_errors(form.value) }} - -{% endblock %} - -{% macro marello_product_price_collection_item_prototype(widget) %} - {% if 'collection' in widget.vars.block_prefixes %} - {% set form = widget.vars.prototype %} - {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} - {% set disabled = widget.vars.disabled %} - {% set allow_delete = widget.vars.allow_delete %} - {% set allow_add_after = widget.vars.allow_add_after %} - {% else %} - {% set form = widget %} - {% set name = widget.vars.full_name %} - {% set disabled = widget.parent.vars.disabled %} - {% set allow_delete = widget.parent.vars.allow_delete %} - {% set allow_add_after = widget.parent.vars.allow_add_after %} - {% endif %} - - {{ form_widget(form, {disabled: disabled}) }} - -{% endmacro %} - -{% block marello_product_price_collection_widget %} - {% spaceless %} - {% if prototype is defined %} - {% set prototype_html = _self.marello_product_price_collection_item_prototype(form) %} - {% endif %} - {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'marello-item-collection grid-container' }) %} - {% set id = id ~ '_collection' %} -
- {% set prototype_name = form.vars.prototype_name %} -
- - - - - - - - - {% if form.children|length %} - {% for child in form.children %} - {{ _self.marello_product_price_collection_item_prototype(child) }} - {% endfor %} - {% elseif show_form_when_empty and prototype_html is defined %} - {{ prototype_html|replace({(prototype_name): '0'})|raw }} - {% endif %} - -
{{ 'marello.pricing.productprice.currency.label'|trans }}{{ marello_pricing_vat_aware_label('marello.pricing.productprice.value.label') }}
-
-
- {% if handle_primary and (prototype is not defined or prototype.primary is defined) %} - {{ _self.oro_collection_validate_primary_js(_context) }} - {% endif %} - {% endspaceless %} -{% endblock %} - {% block marello_assembled_price_list_widget %}
@@ -201,7 +28,7 @@ {% endblock %} -{% macro marello_assembled_price_list_collection_item_prototype(widget) %} +{% macro marello_assembled_price_list_collection_item_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -215,7 +42,7 @@ {% set allow_delete = widget.parent.vars.allow_delete %} {% set allow_add_after = widget.parent.vars.allow_add_after %} {% endif %} - {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_assembled_price_list_collection_item_prototype(child) }} + {{ fields.marello_assembled_price_list_collection_item_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} @@ -256,7 +86,7 @@
{% if handle_primary and (prototype is not defined or prototype.primary is defined) %} - {{ _self.oro_collection_validate_primary_js(_context) }} + {{ fields.oro_collection_validate_primary_js(_context) }} {% endif %} {% endspaceless %} {% endblock %} @@ -291,7 +121,7 @@ {% endblock %} -{% macro marello_assembled_channel_price_list_collection_item_prototype(widget) %} +{% macro marello_assembled_channel_price_list_collection_item_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -305,7 +135,7 @@ {% set allow_delete = widget.parent.vars.allow_delete %} {% set allow_add_after = widget.parent.vars.allow_add_after %} {% endif %} - {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_assembled_channel_price_list_collection_item_prototype(child) }} + {{ fields.marello_assembled_channel_price_list_collection_item_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} @@ -360,7 +193,7 @@ {% if handle_primary and (prototype is not defined or prototype.primary is defined) %} - {{ _self.oro_collection_validate_primary_js(_context) }} + {{ fields.oro_collection_validate_primary_js(_context) }} {% endif %} {% endspaceless %} {% endblock %} diff --git a/src/Marello/Bundle/PricingBundle/Subtotal/Provider/AbstractSubtotalProvider.php b/src/Marello/Bundle/PricingBundle/Subtotal/Provider/AbstractSubtotalProvider.php index 4e9d72960..b89439d3f 100644 --- a/src/Marello/Bundle/PricingBundle/Subtotal/Provider/AbstractSubtotalProvider.php +++ b/src/Marello/Bundle/PricingBundle/Subtotal/Provider/AbstractSubtotalProvider.php @@ -6,7 +6,7 @@ use Marello\Bundle\PricingBundle\Subtotal\Model\Subtotal; use Oro\Bundle\CurrencyBundle\Provider\DefaultCurrencyProviderInterface; use Oro\Bundle\CurrencyBundle\Rounding\RoundingServiceInterface; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; abstract class AbstractSubtotalProvider implements SubtotalProviderInterface { diff --git a/src/Marello/Bundle/PricingBundle/Tests/Functional/Api/requests/product_without_prices.yml b/src/Marello/Bundle/PricingBundle/Tests/Functional/Api/requests/product_without_prices.yml index 32883693b..b98c63b52 100644 --- a/src/Marello/Bundle/PricingBundle/Tests/Functional/Api/requests/product_without_prices.yml +++ b/src/Marello/Bundle/PricingBundle/Tests/Functional/Api/requests/product_without_prices.yml @@ -1,9 +1,12 @@ data: type: marelloproducts id: 'my-sku' - attributes: - name: 'My name' relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'names-1' saleschannels: data: - @@ -17,3 +20,14 @@ data: data: type: marellotaxcodes id: 'taxCode->code)>' +included: + - + type: localizedfallbackvalues + id: 'names-1' + attributes: + fallback: null + string: 'My name' + text: null + relationships: + localization: + data: null diff --git a/src/Marello/Bundle/PricingBundle/Tests/Functional/Controller/PricingControllerTest.php b/src/Marello/Bundle/PricingBundle/Tests/Functional/Controller/PricingControllerTest.php index 6b2fe10cf..23267c20c 100644 --- a/src/Marello/Bundle/PricingBundle/Tests/Functional/Controller/PricingControllerTest.php +++ b/src/Marello/Bundle/PricingBundle/Tests/Functional/Controller/PricingControllerTest.php @@ -2,13 +2,10 @@ namespace Marello\Bundle\PricingBundle\Tests\Functional\Controller; -use Symfony\Component\HttpFoundation\Response; - -use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase; - -use Marello\Bundle\SalesBundle\Tests\Functional\DataFixtures\LoadSalesData; -use Marello\Bundle\ProductBundle\Tests\Functional\DataFixtures\LoadProductData; use Marello\Bundle\PricingBundle\Tests\Functional\DataFixtures\LoadProductChannelPricingData; +use Marello\Bundle\SalesBundle\Tests\Functional\DataFixtures\LoadSalesData; +use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase; +use Symfony\Component\HttpFoundation\Response; /** * @outputBuffering enabled diff --git a/src/Marello/Bundle/PricingBundle/Tests/Unit/Formatter/LabelVATAwareFormatterTest.php b/src/Marello/Bundle/PricingBundle/Tests/Unit/Formatter/LabelVATAwareFormatterTest.php index 4fc857c6c..65143db5e 100644 --- a/src/Marello/Bundle/PricingBundle/Tests/Unit/Formatter/LabelVATAwareFormatterTest.php +++ b/src/Marello/Bundle/PricingBundle/Tests/Unit/Formatter/LabelVATAwareFormatterTest.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\PricingBundle\Tests\Unit\Formatter; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use PHPUnit\Framework\TestCase; diff --git a/src/Marello/Bundle/PricingBundle/Tests/Unit/Provider/ChannelPriceProviderTest.php b/src/Marello/Bundle/PricingBundle/Tests/Unit/Provider/ChannelPriceProviderTest.php index 319a27163..e0e030e0c 100644 --- a/src/Marello/Bundle/PricingBundle/Tests/Unit/Provider/ChannelPriceProviderTest.php +++ b/src/Marello/Bundle/PricingBundle/Tests/Unit/Provider/ChannelPriceProviderTest.php @@ -4,26 +4,21 @@ use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\ORM\EntityRepository; - -use PHPUnit\Framework\TestCase; - -use Symfony\Component\Form\FormInterface; - -use Oro\Component\Testing\Unit\EntityTrait; - -use Marello\Bundle\OrderBundle\Entity\Order; -use Marello\Bundle\ProductBundle\Entity\Product; -use Marello\Bundle\SalesBundle\Entity\SalesChannel; -use Marello\Bundle\PricingBundle\Entity\ProductPrice; use Marello\Bundle\LayoutBundle\Context\FormChangeContext; +use Marello\Bundle\LayoutBundle\Context\FormChangeContextInterface; +use Marello\Bundle\OrderBundle\Entity\Order; +use Marello\Bundle\OrderBundle\Provider\OrderItem\OrderItemFormChangesProvider; +use Marello\Bundle\PricingBundle\Entity\AssembledChannelPriceList; use Marello\Bundle\PricingBundle\Entity\AssembledPriceList; use Marello\Bundle\PricingBundle\Entity\ProductChannelPrice; +use Marello\Bundle\PricingBundle\Entity\ProductPrice; use Marello\Bundle\PricingBundle\Provider\ChannelPriceProvider; -use Marello\Bundle\PricingBundle\Entity\AssembledChannelPriceList; -use Marello\Bundle\LayoutBundle\Context\FormChangeContextInterface; +use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\ProductBundle\Entity\Repository\ProductRepository; -use Marello\Bundle\OrderBundle\Provider\OrderItem\OrderItemFormChangesProvider; -use Marello\Bundle\PricingBundle\Entity\Repository\ProductChannelPriceRepository; +use Marello\Bundle\SalesBundle\Entity\SalesChannel; +use Oro\Component\Testing\Unit\EntityTrait; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\FormInterface; class ChannelPriceProviderTest extends TestCase { diff --git a/src/Marello/Bundle/PricingBundle/Tests/Unit/Subtotal/Provider/CompositeSubtotalProviderTest.php b/src/Marello/Bundle/PricingBundle/Tests/Unit/Subtotal/Provider/CompositeSubtotalProviderTest.php index b8e301e3d..8cabfafd5 100644 --- a/src/Marello/Bundle/PricingBundle/Tests/Unit/Subtotal/Provider/CompositeSubtotalProviderTest.php +++ b/src/Marello/Bundle/PricingBundle/Tests/Unit/Subtotal/Provider/CompositeSubtotalProviderTest.php @@ -4,7 +4,7 @@ use Doctrine\Common\Collections\ArrayCollection; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use PHPUnit\Framework\TestCase; diff --git a/src/Marello/Bundle/PricingBundle/Twig/PricingExtension.php b/src/Marello/Bundle/PricingBundle/Twig/PricingExtension.php index e3c2d0581..7d02b1f0c 100644 --- a/src/Marello/Bundle/PricingBundle/Twig/PricingExtension.php +++ b/src/Marello/Bundle/PricingBundle/Twig/PricingExtension.php @@ -4,8 +4,10 @@ use Marello\Bundle\PricingBundle\Formatter\LabelVATAwareFormatter; use Marello\Bundle\PricingBundle\Provider\CurrencyProvider; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; -class PricingExtension extends \Twig_Extension +class PricingExtension extends AbstractExtension { const NAME = 'marello_pricing'; @@ -47,11 +49,11 @@ public function getName() public function getFunctions() { return [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_pricing_get_currency_data', [$this, 'getCurrencyData'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_pricing_vat_aware_label', [$this->vatLabelFormatter, 'getFormattedLabel'] ), diff --git a/src/Marello/Bundle/ProductBundle/Async/ProductsAssignSalesChannelsProcessor.php b/src/Marello/Bundle/ProductBundle/Async/ProductsAssignSalesChannelsProcessor.php index 5da959200..7b42f6c94 100644 --- a/src/Marello/Bundle/ProductBundle/Async/ProductsAssignSalesChannelsProcessor.php +++ b/src/Marello/Bundle/ProductBundle/Async/ProductsAssignSalesChannelsProcessor.php @@ -3,20 +3,24 @@ namespace Marello\Bundle\ProductBundle\Async; use Doctrine\ORM\EntityManagerInterface; -use Marello\Bundle\ProductBundle\Entity\Product; -use Marello\Bundle\SalesBundle\Entity\SalesChannel; -use Oro\Bundle\DataGridBundle\Datagrid\Manager; -use Oro\Bundle\DataGridBundle\Datasource\Orm\OrmDatasource; -use Oro\Bundle\EmailBundle\Form\Model\Factory; -use Oro\Bundle\EmailBundle\Mailer\Processor; + +use Psr\Log\LoggerInterface; + +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; + use Oro\Bundle\UserBundle\Entity\User; -use Oro\Component\MessageQueue\Client\TopicSubscriberInterface; -use Oro\Component\MessageQueue\Consumption\MessageProcessorInterface; +use Oro\Component\MessageQueue\Util\JSON; +use Oro\Bundle\EmailBundle\Mailer\Processor; +use Oro\Bundle\EmailBundle\Form\Model\Factory; +use Oro\Bundle\DataGridBundle\Datagrid\Manager; use Oro\Component\MessageQueue\Transport\MessageInterface; use Oro\Component\MessageQueue\Transport\SessionInterface; -use Oro\Component\MessageQueue\Util\JSON; -use Psr\Log\LoggerInterface; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Oro\Component\MessageQueue\Client\TopicSubscriberInterface; +use Oro\Component\MessageQueue\Consumption\MessageProcessorInterface; + +use Marello\Bundle\ProductBundle\Entity\Product; +use Marello\Bundle\SalesBundle\Entity\SalesChannel; +use Marello\Bundle\ProductBundle\Entity\Repository\ProductRepository; class ProductsAssignSalesChannelsProcessor implements MessageProcessorInterface, TopicSubscriberInterface { @@ -81,53 +85,33 @@ public function __construct( /** * {@inheritdoc} */ - public static function getSubscribedTopics() + public static function getSubscribedTopics(): array { return [self::TOPIC]; } /** - * {@inheritdoc} + * @param MessageInterface $message + * @param SessionInterface $session + * @return string */ - public function process(MessageInterface $message, SessionInterface $session) + public function process(MessageInterface $message, SessionInterface $session): string { $data = JSON::decode($message->getBody()); - $inset = $data['inset']; $products = $data['products']; - $filters = $data['filters']; $salesChannels = $data['salesChannels']; + $salesChannels = $this->entityManager->getRepository(SalesChannel::class)->findBy(['id' => $salesChannels]); - $isAllSelected = $inset === '0'; - $salesChannels = $this->entityManager->getRepository(SalesChannel::class)->findBy(['code' => $salesChannels]); - - if (!empty($products) || $isAllSelected) { - $grid = $this->datagridManager->getDatagridByRequestParams( - 'marello-products-grid', - ['_filter' => $filters] - ); - /** @var OrmDatasource $dataSource */ - $dataSource = $grid->getAcceptedDatasource(); - $queryBuilder = $dataSource->getQueryBuilder(); - - if (!$isAllSelected) { - $queryBuilder->andWhere($queryBuilder->expr()->in('p.id', $products)); - } elseif ($products) { - $queryBuilder->andWhere($queryBuilder->expr()->notIn('p.id', $products)); - } - - $result = $queryBuilder - ->getQuery() - ->setFirstResult(0) - ->setMaxResults(null) - ->getResult(); - + if (!empty($products) && !empty($salesChannels)) { + /** @var ProductRepository $productRepo */ + $productRepo = $this->entityManager->getRepository(Product::class); + $products = $productRepo->findBy(['id' => $products]); try { $iteration = 1; $modifiedProducts = []; - foreach ($result as $entity) { - /** @var Product $entity */ - $entity = $entity[0]; + /** @var Product $entity */ + foreach ($products as $entity) { $addedChannels = 0; foreach ($salesChannels as $salesChannel) { if (!$entity->hasChannel($salesChannel)) { @@ -143,9 +127,6 @@ public function process(MessageInterface $message, SessionInterface $session) if (($iteration % self::FLUSH_BATCH_SIZE) === 0) { $this->entityManager->flush(); } - if ($addedChannels > 0) { - $iteration++; - } } $this->entityManager->flush(); $this->sendMail($salesChannels, $modifiedProducts); @@ -165,10 +146,11 @@ public function process(MessageInterface $message, SessionInterface $session) } /** - * @param SalesChannel[] $assignedSalesChannels - * @param Product[] $modifiedProducts + * @param array $assignedSalesChannels + * @param array $modifiedProducts + * @throws \Swift_SwiftException */ - private function sendMail(array $assignedSalesChannels, array $modifiedProducts) + private function sendMail(array $assignedSalesChannels, array $modifiedProducts): void { /** @var User $currentUser */ $currentUser = $this->tokenStorage->getToken()->getUser(); diff --git a/src/Marello/Bundle/ProductBundle/Controller/ProductController.php b/src/Marello/Bundle/ProductBundle/Controller/ProductController.php index dc322e3a5..bc9f34b06 100644 --- a/src/Marello/Bundle/ProductBundle/Controller/ProductController.php +++ b/src/Marello/Bundle/ProductBundle/Controller/ProductController.php @@ -10,22 +10,26 @@ use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeFamily; use Oro\Bundle\SecurityBundle\Annotation as Security; use Oro\Bundle\UIBundle\Route\Router; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; -class ProductController extends Controller +class ProductController extends AbstractController { const ACTION_SAVE_AND_DUPLICATE = 'save_and_duplicate'; /** - * @Config\Route("/", name="marello_product_index") + * @Route( + * path="/", + * name="marello_product_index" + * ) * @AclAncestor("marello_product_view") - * @Config\Template + * @Template */ public function indexAction() { @@ -33,9 +37,12 @@ public function indexAction() } /** - * @Config\Route("/create", name="marello_product_create") + * @Route( + * path="/create", + * name="marello_product_create" + * ) * @AclAncestor("marello_product_create") - * @Config\Template("MarelloProductBundle:Product:createStepOne.html.twig") + * @Template("MarelloProductBundle:Product:createStepOne.html.twig") * * @param Request $request * @@ -55,11 +62,15 @@ protected function createStepOne(Request $request) $form = $this->createForm(ProductStepOneType::class); $handler = new ProductCreateStepOneHandler($form, $request); $productTypesProvider = $this->get('marello_product.provider.product_types'); - + $em = $this->get('doctrine.orm.entity_manager'); + /** @var AttributeFamily $attributeFamily */ + $attributeFamilies = $em + ->getRepository(AttributeFamily::class) + ->findBy(['entityClass' => Product::class]); if ($handler->process()) { return $this->forward('MarelloProductBundle:Product:createStepTwo'); } - if (count($productTypesProvider->getProductTypes()) === 1) { + if (count($productTypesProvider->getProductTypes()) <= 1 && count($attributeFamilies) <= 1) { $request->setMethod('POST'); $request->request->set('input_action', 'marello_product_create'); $request->request->set('single_product_type', true); @@ -73,9 +84,12 @@ protected function createStepOne(Request $request) } /** - * @Config\Route("/create/step-two", name="marello_product_create_step_two") + * @Route( + * path="/create/step-two", + * name="marello_product_create_step_two" + * ) * - * @Config\Template("MarelloProductBundle:Product:createStepTwo.html.twig") + * @Template("MarelloProductBundle:Product:createStepTwo.html.twig") * * @AclAncestor("marello_product_create") * @@ -96,43 +110,40 @@ protected function createStepTwo(Request $request, Product $product) { if ($request->get('input_action') === 'marello_product_create') { $formStepOne = $this->createForm(ProductStepOneType::class, $product); + $em = $this->get('doctrine.orm.entity_manager'); if ($request->get('single_product_type')) { $type = Product::DEFAULT_PRODUCT_TYPE; + $attributeFamily = $em + ->getRepository(AttributeFamily::class) + ->findOneBy(['entityClass' => Product::class]); } else { $formStepOne->handleRequest($request); $type = $formStepOne->get('type')->getData(); + $attributeFamily = $formStepOne->get('attributeFamily')->getData(); } $product->setType($type); - $productTypesProvider = $this->get('marello_product.provider.product_types'); - $em = $this->get('doctrine.orm.entity_manager'); - $productType = $productTypesProvider->getProductType($type); - if ($productType) { - /** @var AttributeFamily $attributeFamily */ - $attributeFamily = $em - ->getRepository(AttributeFamily::class) - ->findOneBy(['code' => $productType->getAttributeFamilyCode()]); - $product->setAttributeFamily($attributeFamily); + $product->setAttributeFamily($attributeFamily); + + $form = $this->createForm(ProductType::class, $product); - $form = $this->createForm(ProductType::class, $product); - $form->get('type')->setData($type); - $form->get('attributeFamily')->setData($attributeFamily->getId()); - } return [ 'form' => $form->createView(), 'entity' => $product, 'isWidgetContext' => (bool)$request->get('_wid', false) ]; } - //$form = $this->createForm(ProductStepOneType::class, $product, ['validation_groups'=> false]); - //$form->submit($request->request->get(ProductType::BLOCK_PREFIX)); return $this->update($product, $request); } /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_product_update") + * @Route( + * path="/update/{id}", + * requirements={"id"="\d+"}, + * name="marello_product_update" + * ) * @AclAncestor("marello_product_update") - * @Config\Template + * @Template * * @param Product $product * @param Request $request @@ -200,9 +211,13 @@ protected function update(Product $product, Request $request) } /** - * @Config\Route("/view/{id}", requirements={"id"="\d+"}, name="marello_product_view") + * @Route( + * path="/view/{id}", + * requirements={"id"="\d+"}, + * name="marello_product_view" + * ) * @AclAncestor("marello_product_view") - * @Config\Template("MarelloProductBundle:Product:view.html.twig") + * @Template("MarelloProductBundle:Product:view.html.twig") * * @param Product $product * @@ -216,9 +231,13 @@ public function viewAction(Product $product) } /** - * @Config\Route("/widget/info/{id}", name="marello_product_widget_info", requirements={"id"="\d+"}) + * @Route( + * path="/widget/info/{id}", + * name="marello_product_widget_info", + * requirements={"id"="\d+"} + * ) * @AclAncestor("marello_product_view") - * @Config\Template + * @Template("MarelloProductBundle:Product/widget:info.html.twig") * * @param Product $product * @@ -232,9 +251,13 @@ public function infoAction(Product $product) } /** - * @Config\Route("/widget/price/{id}", name="marello_product_widget_price", requirements={"id"="\d+"}) + * @Route( + * path="/widget/price/{id}", + * name="marello_product_widget_price", + * requirements={"id"="\d+"} + * ) * @AclAncestor("marello_product_view") - * @Config\Template + * @Template("MarelloProductBundle:Product/widget:price.html.twig") * * @param Product $product * @@ -248,9 +271,12 @@ public function priceAction(Product $product) } /** - * @Config\Route("/assign-sales-channels", name="marello_product_assign_sales_channels") + * @Route( + * path="/assign-sales-channels", + * name="marello_product_assign_sales_channels" + * ) * @AclAncestor("marello_product_update") - * @Config\Template + * @Template * * @return array */ diff --git a/src/Marello/Bundle/ProductBundle/Controller/VariantController.php b/src/Marello/Bundle/ProductBundle/Controller/VariantController.php index 9b3d52e0b..78de2c514 100644 --- a/src/Marello/Bundle/ProductBundle/Controller/VariantController.php +++ b/src/Marello/Bundle/ProductBundle/Controller/VariantController.php @@ -2,25 +2,29 @@ namespace Marello\Bundle\ProductBundle\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; +use Symfony\Component\Routing\Annotation\Route; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\ProductBundle\Entity\Variant; -class VariantController extends Controller +class VariantController extends AbstractController { /** - * @Config\Route("/create/parent/{id}", requirements={"id"="\d+"}, name="marello_product_create_variant") - * @Config\Method({"GET", "POST"}) + * @Route( + * path="/create/parent/{id}", + * methods={"GET", "POST"}, + * requirements={"id"="\d+"}, + * name="marello_product_create_variant" + * ) * @AclAncestor("marello_product_create_variant") - * @Config\Template("MarelloProductBundle:Variant:update.html.twig") + * @Template("MarelloProductBundle:Variant:update.html.twig") * * @param Product $product * @return array @@ -31,12 +35,12 @@ public function createVariantAction(Product $product) } /** - * @Config\Route( - * "/add/{id}/parent/{parentId}", + * @Route( + * path="/add/{id}/parent/{parentId}", * requirements={"id"="\d+","parentId"="\d+"}, name="marello_product_add_variant" * ) * @AclAncestor("marello_product_add_variant") - * @Config\Template("MarelloProductBundle:Variant:update.html.twig") + * @Template("MarelloProductBundle:Variant:update.html.twig") * * @param Request $request * @param Variant $variant @@ -54,7 +58,7 @@ public function updateVariantAction(Request $request, Variant $variant) $entityClass = $this->get('oro_entity.routing_helper')->resolveEntityClass($entityClass); $parentId = $request->get('parentId'); - if ($parentId && $entityClass === $this->container->getParameter('marello_product.entity.class')) { + if ($parentId && $entityClass === Product::class) { $repository = $this->getDoctrine()->getRepository($entityClass); /** @var Product $parent */ $parent = $repository->find($parentId); @@ -108,9 +112,9 @@ protected function updateVariant(Product $product, Variant $variant) } /** - * @Config\Route("/widget/info/{id}", name="marello_product_variant_widget_info", requirements={"id"="\d+"}) + * @Route(path="/widget/info/{id}", name="marello_product_variant_widget_info", requirements={"id"="\d+"}) * @AclAncestor("marello_product_view") - * @Config\Template + * @Template * * @param Product $product * @return array diff --git a/src/Marello/Bundle/ProductBundle/Duplicator/ProductDuplicator.php b/src/Marello/Bundle/ProductBundle/Duplicator/ProductDuplicator.php index 9b6eef8d0..56d8a95ec 100755 --- a/src/Marello/Bundle/ProductBundle/Duplicator/ProductDuplicator.php +++ b/src/Marello/Bundle/ProductBundle/Duplicator/ProductDuplicator.php @@ -103,10 +103,10 @@ protected function createProductCopy(Product $product) $productCopy = clone $product; $baseSku = $this->defineBaseValue($product->getSku(), 'sku'); $newSku = $this->skuIncrementor->increment($baseSku); - $baseName = $this->defineBaseValue($product->getName(), 'name'); + $baseName = $this->defineBaseValue($product->getDenormalizedDefaultName(), 'denormalizedDefaultName'); $productCopy ->setSku($newSku) - ->setName(sprintf('%s-%s', $baseName, substr($newSku, strlen($baseSku) + 1))); + ->setDefaultName(sprintf('%s-%s', $baseName, substr($newSku, strlen($baseSku) + 1))); $disabledStatus = $this->doctrineHelper ->getEntityManagerForClass(ProductStatus::class) ->getRepository(ProductStatus::class) diff --git a/src/Marello/Bundle/ProductBundle/Entity/Manager/ProductApiEntityManager.php b/src/Marello/Bundle/ProductBundle/Entity/Manager/ProductApiEntityManager.php index e1f0b79c1..fad3afb87 100644 --- a/src/Marello/Bundle/ProductBundle/Entity/Manager/ProductApiEntityManager.php +++ b/src/Marello/Bundle/ProductBundle/Entity/Manager/ProductApiEntityManager.php @@ -12,7 +12,7 @@ protected function getSerializationConfig() 'exclusion_policy' => 'all', 'fields' => [ 'id' => null, - 'name' => null, + 'names' => null, 'sku' => null, 'status' => [ 'exclusion_policy' => 'all', diff --git a/src/Marello/Bundle/ProductBundle/Entity/Product.php b/src/Marello/Bundle/ProductBundle/Entity/Product.php index 067c68151..e9eb2a9bb 100644 --- a/src/Marello/Bundle/ProductBundle/Entity/Product.php +++ b/src/Marello/Bundle/ProductBundle/Entity/Product.php @@ -5,14 +5,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; - -use Oro\Bundle\EntityConfigBundle\Metadata\Annotation as Oro; -use Oro\Bundle\OrganizationBundle\Entity\Organization; -use Oro\Bundle\OrganizationBundle\Entity\OrganizationAwareInterface; -use Oro\Bundle\OrganizationBundle\Entity\OrganizationInterface; -use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeFamily; -use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeFamilyAwareInterface; - use Marello\Bundle\CatalogBundle\Entity\Category; use Marello\Bundle\InventoryBundle\Entity\InventoryItem; use Marello\Bundle\InventoryBundle\Model\InventoryItemAwareInterface; @@ -22,13 +14,18 @@ use Marello\Bundle\PricingBundle\Model\PricingAwareInterface; use Marello\Bundle\ProductBundle\Model\ExtendProduct; use Marello\Bundle\SalesBundle\Entity\SalesChannel; -use Marello\Bundle\SalesBundle\Model\SalesChannelAwareInterface; +use Marello\Bundle\SalesBundle\Model\SalesChannelsAwareInterface; use Marello\Bundle\SupplierBundle\Entity\Supplier; use Marello\Bundle\TaxBundle\Entity\TaxCode; +use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeFamily; +use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeFamilyAwareInterface; +use Oro\Bundle\EntityConfigBundle\Metadata\Annotation as Oro; +use Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue; +use Oro\Bundle\OrganizationBundle\Entity\Organization; +use Oro\Bundle\OrganizationBundle\Entity\OrganizationAwareInterface; +use Oro\Bundle\OrganizationBundle\Entity\OrganizationInterface; /** - * Represents a Marello Product - * * @ORM\Entity(repositoryClass="Marello\Bundle\ProductBundle\Entity\Repository\ProductRepository") * @ORM\Table( * name="marello_product_product", @@ -69,7 +66,7 @@ */ class Product extends ExtendProduct implements ProductInterface, - SalesChannelAwareInterface, + SalesChannelsAwareInterface, PricingAwareInterface, OrganizationAwareInterface, InventoryItemAwareInterface, @@ -92,23 +89,62 @@ class Product extends ExtendProduct implements * ) */ protected $id; - + /** + * This is a mirror field for performance reasons only. + * It mirrors getDefaultName()->getString(). + * * @var string * - * @ORM\Column(name="name", type="string", nullable=false) + * @ORM\Column(name="name", type="string", length=255, nullable=false) + * @Oro\ConfigField( + * defaultValues={ + * "importexport"={ + * "excluded"=true + * } + * }, + * mode="hidden" + * ) + */ + protected $denormalizedDefaultName; + + /** + * @var Collection|LocalizedFallbackValue[] + * + * @ORM\ManyToMany( + * targetEntity="Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue", + * cascade={"ALL"}, + * orphanRemoval=true + * ) + * @ORM\JoinTable( + * name="marello_product_product_name", + * joinColumns={ + * @ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE") + * }, + * inverseJoinColumns={ + * @ORM\JoinColumn(name="localized_value_id", referencedColumnName="id", onDelete="CASCADE", unique=true) + * } + * ) * @Oro\ConfigField( * defaultValues={ * "dataaudit"={ * "auditable"=true * }, * "importexport"={ - * "excluded"=true + * "order"=20, + * "full"=true, + * "fallback_field"="string" + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="System" * } * } * ) */ - protected $name; + protected $names; /** * @var string @@ -123,6 +159,12 @@ class Product extends ExtendProduct implements * "order"=10, * "header"="SKU", * "identity"=true, + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="System" * } * } * ) @@ -140,6 +182,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="Custom" * } * } * ) @@ -158,6 +206,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="System" * } * } * ) @@ -181,23 +235,6 @@ class Product extends ExtendProduct implements */ protected $type; - /** - * @var double - * - * @ORM\Column(name="cost", type="money", nullable=true) - * @Oro\ConfigField( - * defaultValues={ - * "dataaudit"={ - * "auditable"=true - * }, - * "importexport"={ - * "excluded"=true - * } - * } - * ) - */ - protected $cost; - /** * @ORM\Column(type="float", nullable=true) * @Oro\ConfigField( @@ -207,6 +244,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="Custom" * } * } * ) @@ -226,6 +269,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="Custom" * } * } * ) @@ -267,6 +316,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="System" * } * } * ) @@ -290,6 +345,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="System" * } * } * ) @@ -310,6 +371,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="System" * } * } * ) @@ -387,6 +454,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="Custom" * } * } * ) @@ -422,6 +495,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="System" * } * } * ) @@ -444,6 +523,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="System" * } * } * ) @@ -466,6 +551,12 @@ class Product extends ExtendProduct implements * }, * "importexport"={ * "excluded"=true + * }, + * "attribute"={ + * "is_attribute"=true + * }, + * "extend"={ + * "owner"="Custom" * } * } * ) @@ -490,8 +581,37 @@ class Product extends ExtendProduct implements */ protected $attributeFamily; + /** + * @var \DateTime + * + * @ORM\Column(name="created_at", type="datetime") + * @Oro\ConfigField( + * defaultValues={ + * "entity"={ + * "label"="oro.ui.created_at" + * } + * } + * ) + */ + protected $createdAt; + + /** + * @var \DateTime + * + * @ORM\Column(name="updated_at", type="datetime", nullable=true) + * @Oro\ConfigField( + * defaultValues={ + * "entity"={ + * "label"="oro.ui.updated_at" + * } + * } + * ) + */ + protected $updatedAt; + public function __construct() { + $this->names = new ArrayCollection(); $this->prices = new ArrayCollection(); $this->channelPrices = new ArrayCollection(); $this->channels = new ArrayCollection(); @@ -505,6 +625,7 @@ public function __clone() { if ($this->id) { $this->id = null; + $this->names = new ArrayCollection(); $this->prices = new ArrayCollection(); $this->channelPrices = new ArrayCollection(); $this->channels = new ArrayCollection(); @@ -523,21 +644,53 @@ public function getId() } /** - * @return string + * @param array|LocalizedFallbackValue[] $names + * + * @return $this + */ + public function setNames(array $names = []) + { + $this->names->clear(); + + foreach ($names as $name) { + $this->addName($name); + } + + return $this; + } + + /** + * @return Collection|LocalizedFallbackValue[] */ - public function getName() + public function getNames() { - return $this->name; + return $this->names; } /** - * @param string $name + * @param LocalizedFallbackValue $name * - * @return Product + * @return $this + */ + public function addName(LocalizedFallbackValue $name) + { + if (!$this->names->contains($name)) { + $this->names->add($name); + } + + return $this; + } + + /** + * @param LocalizedFallbackValue $name + * + * @return $this */ - public function setName($name) + public function removeName(LocalizedFallbackValue $name) { - $this->name = $name; + if ($this->names->contains($name)) { + $this->names->removeElement($name); + } return $this; } @@ -835,13 +988,21 @@ public function getData() { return $this->data; } - + /** * @return string */ public function __toString() { - return $this->name; + try { + if ($this->getDefaultName()) { + return (string) $this->getDefaultName(); + } else { + return (string) $this->sku; + } + } catch (\LogicException $e) { + return (string) $this->sku; + } } /** @@ -989,22 +1150,6 @@ public function setPreferredSupplier(Supplier $preferredSupplier) return $this; } - /** - * @ORM\PreUpdate - */ - public function preUpdateTimestamp() - { - $this->updatedAt = new \DateTime('now', new \DateTimeZone('UTC')); - } - - /** - * @ORM\PrePersist - */ - public function prePersistTimestamp() - { - $this->createdAt = $this->updatedAt = new \DateTime('now', new \DateTimeZone('UTC')); - } - /** * Set taxCode * @@ -1198,4 +1343,82 @@ public function setType($type) return $this; } + + /** + * This field is read-only, updated automatically prior to persisting. + * + * @return string + */ + public function getDenormalizedDefaultName() + { + return $this->denormalizedDefaultName; + } + + /** + * Set createdAt + * + * @param \DateTime $createdAt + * + * @return mixed + */ + public function setCreatedAt($createdAt) + { + $this->createdAt = $createdAt; + + return $this; + } + + /** + * @return \Datetime + */ + public function getCreatedAt() + { + return $this->createdAt; + } + + /** + * Set updatedAt + * + * @param \DateTime $updatedAt + * + * @return mixed + */ + public function setUpdatedAt($updatedAt) + { + $this->updatedAt = $updatedAt; + + return $this; + } + + /** + * @return \Datetime + */ + public function getUpdatedAt() + { + return $this->updatedAt; + } + + /** + * @ORM\PrePersist + */ + public function prePersist() + { + $this->createdAt = new \DateTime('now', new \DateTimeZone('UTC')); + if (!$this->getDefaultName()) { + throw new \RuntimeException('Product has to have a default name'); + } + $this->denormalizedDefaultName = $this->getDefaultName()->getString(); + } + + /** + * @ORM\PreUpdate + */ + public function preUpdate() + { + $this->updatedAt = new \DateTime('now', new \DateTimeZone('UTC')); + if (!$this->getDefaultName()) { + throw new \RuntimeException('Product has to have a default name'); + } + $this->denormalizedDefaultName = $this->getDefaultName()->getString(); + } } diff --git a/src/Marello/Bundle/ProductBundle/Entity/ProductChannelTaxRelation.php b/src/Marello/Bundle/ProductBundle/Entity/ProductChannelTaxRelation.php index 290f52e98..3bb27dcf0 100644 --- a/src/Marello/Bundle/ProductBundle/Entity/ProductChannelTaxRelation.php +++ b/src/Marello/Bundle/ProductBundle/Entity/ProductChannelTaxRelation.php @@ -3,10 +3,13 @@ namespace Marello\Bundle\ProductBundle\Entity; use Doctrine\ORM\Mapping as ORM; -use Marello\Bundle\SalesBundle\Entity\SalesChannel; -use Marello\Bundle\TaxBundle\Entity\TaxCode; + use Oro\Bundle\EntityConfigBundle\Metadata\Annotation as Oro; +use Marello\Bundle\TaxBundle\Entity\TaxCode; +use Marello\Bundle\SalesBundle\Entity\SalesChannel; +use Marello\Bundle\SalesBundle\Model\SalesChannelAwareInterface; + /** * TaxCode * @@ -22,7 +25,7 @@ * ) * @Oro\Config() */ -class ProductChannelTaxRelation +class ProductChannelTaxRelation implements SalesChannelAwareInterface { /** * @var integer diff --git a/src/Marello/Bundle/ProductBundle/EventListener/AttributeFormViewListener.php b/src/Marello/Bundle/ProductBundle/EventListener/AttributeFormViewListener.php new file mode 100644 index 000000000..64c7fb877 --- /dev/null +++ b/src/Marello/Bundle/ProductBundle/EventListener/AttributeFormViewListener.php @@ -0,0 +1,298 @@ +attributeManager = $attributeManager; + } + + /** + * @param BeforeListRenderEvent $event + */ + public function onEdit(BeforeListRenderEvent $event) + { + $entity = $event->getEntity(); + + if (!$entity instanceof AttributeFamilyAwareInterface) { + return; + } + + $scrollData = $event->getScrollData(); + $formView = $event->getFormView(); + $groupsData = $this->attributeManager->getGroupsWithAttributes($entity->getAttributeFamily()); + $this->filterGroupAttributes($groupsData, 'form', 'is_enabled'); + $this->addNotEmptyGroupBlocks($scrollData, $groupsData); + + foreach ($groupsData as $groupsDatum) { + /** @var AttributeGroup $group */ + $group = $groupsDatum['group']; + /** @var FieldConfigModel $attribute */ + foreach ($groupsDatum['attributes'] as $attribute) { + $fieldId = $attribute->getFieldName(); + if (in_array($fieldId, $this->getRestrictedToMoveFields(), true)) { + continue; + } + $attributeView = $formView->offsetGet($fieldId); + + if (!$attributeView->isRendered()) { + $html = $event->getEnvironment()->render('OroEntityConfigBundle:Attribute:row.html.twig', [ + 'child' => $attributeView, + ]); + + $subblockId = $scrollData->addSubBlock($group->getCode()); + $scrollData->addSubBlockData($group->getCode(), $subblockId, $html, $fieldId); + } else { + $this->moveFieldToBlock($scrollData, $attribute->getFieldName(), $group->getCode()); + } + } + } + + $this->combineGroupBlocks($scrollData); + $this->removeEmptyGroupBlocks($scrollData); + } + + /** + * @param ScrollData $scrollData + */ + private function combineGroupBlocks(ScrollData $scrollData) + { + $data = $scrollData->getData(); + if (empty($data[ScrollData::DATA_BLOCKS])) { + return; + } + $notAttributesGroupBlocksByIds = []; + $notAttributesGroupBlocksByTitles = []; + foreach ($data[ScrollData::DATA_BLOCKS] as $blockId => $blockData) { + if (!is_string($blockId)) { + $notAttributesGroupBlocksByIds[$blockId] = $blockData; + $notAttributesGroupBlocksByTitles[$blockData[ScrollData::TITLE]] = $blockId; + } + } + foreach ($data[ScrollData::DATA_BLOCKS] as $blockId => $data) { + if (!is_string($blockId)) { + continue; + } + $isEmpty = true; + if (!empty($data[ScrollData::SUB_BLOCKS])) { + if (isset($notAttributesGroupBlocksByTitles[$data[ScrollData::TITLE]])) { + foreach ($data[ScrollData::SUB_BLOCKS] as $subblockId => $subblockData) { + if (!empty($subblockData[ScrollData::DATA])) { + foreach ($subblockData[ScrollData::DATA] as $fieldName => $fieldData) { + $this->moveFieldToBlock( + $scrollData, + $fieldName, + $notAttributesGroupBlocksByTitles[$data[ScrollData::TITLE]] + ); + } + } + } + } else { + $isEmpty = false; + } + } + + if ($isEmpty) { + $scrollData->removeNamedBlock($blockId); + } + } + } + + /** + * @param ScrollData $scrollData + */ + private function removeEmptyGroupBlocks(ScrollData $scrollData) + { + $data = $scrollData->getData(); + if (empty($data[ScrollData::DATA_BLOCKS])) { + return; + } + + foreach ($data[ScrollData::DATA_BLOCKS] as $blockId => $data) { + if (!is_string($blockId)) { + continue; + } + $isEmpty = true; + if (!empty($data[ScrollData::SUB_BLOCKS])) { + foreach ($data[ScrollData::SUB_BLOCKS] as $subblockId => $subblockData) { + if (!empty($subblockData[ScrollData::DATA])) { + $isEmpty = false; + } + } + } + + if ($isEmpty) { + $scrollData->removeNamedBlock($blockId); + } + } + } + + /** + * @param ScrollData $scrollData + * @param array $groups + */ + private function addNotEmptyGroupBlocks(ScrollData $scrollData, array $groups) + { + foreach ($groups as $group) { + if (!empty($group['attributes'])) { + /** @var AttributeGroup $currentGroup */ + $currentGroup = $group['group']; + $scrollData->addNamedBlock($currentGroup->getCode(), $currentGroup->getLabel()->getString()); + } + } + } + + /** + * @param BeforeListRenderEvent $event + */ + public function onViewList(BeforeListRenderEvent $event) + { + $entity = $event->getEntity(); + + if (!$entity instanceof AttributeFamilyAwareInterface) { + return; + } + + $groups = $this->attributeManager->getGroupsWithAttributes($entity->getAttributeFamily()); + $scrollData = $event->getScrollData(); + $this->filterGroupAttributes($groups, 'view', 'is_displayable'); + $this->addNotEmptyGroupBlocks($scrollData, $groups); + + /** @var AttributeGroup $group */ + foreach ($groups as $groupData) { + /** @var AttributeGroup $group */ + $group = $groupData['group']; + + /** @var FieldConfigModel $attribute */ + foreach ($groupData['attributes'] as $attribute) { + $fieldName = $attribute->getFieldName(); + if (in_array($fieldName, $this->getRestrictedToMoveFields(), true)) { + continue; + } + if ($scrollData->hasNamedField($fieldName)) { + $this->moveFieldToBlock($scrollData, $fieldName, $group->getCode()); + continue; + } + + $html = $event->getEnvironment()->render( + 'OroEntityConfigBundle:Attribute:attributeView.html.twig', + [ + 'entity' => $entity, + 'field' => $attribute, + ] + ); + + $subblockId = $scrollData->addSubBlock($group->getCode()); + $scrollData->addSubBlockData($group->getCode(), $subblockId, $html, $fieldName); + } + } + + $this->combineGroupBlocks($scrollData); + $this->removeEmptyGroupBlocks($scrollData); + } + + /** + * @param ScrollData $scrollData + * @param string $fieldId + * @param string $blockId + */ + protected function moveFieldToBlock(ScrollData $scrollData, $fieldId, $blockId) + { + if (in_array($fieldId, $this->getRestrictedToMoveFields(), true)) { + return; + } + + $data = $scrollData->getData(); + if (!isset($data[ScrollData::DATA_BLOCKS][$blockId])) { + return; + } + + foreach ($data[ScrollData::DATA_BLOCKS] as $currentBlockId => &$blockData) { + foreach ($blockData[ScrollData::SUB_BLOCKS] as $subblockId => &$subblock) { + if (isset($subblock[ScrollData::DATA][$fieldId])) { + $fieldData = $subblock[ScrollData::DATA][$fieldId]; + + if ($blockId !== $currentBlockId) { + unset($subblock[ScrollData::DATA][$fieldId]); + + $subblockIds = $scrollData->getSubblockIds($blockId); + if (empty($subblockIds)) { + $subblockId = $scrollData->addSubBlock($blockId); + } else { + $subblockId = reset($subblockIds); + } + + $scrollData->addSubBlockData($blockId, $subblockId, $fieldData, $fieldId); + break; + } + } + } + } + } + + /** + * @param array $groups + * @param string $scope + * @param string $option + */ + private function filterGroupAttributes(array &$groups, $scope, $option) + { + foreach ($groups as &$group) { + $group['attributes'] = array_filter( + $group['attributes'], + function (FieldConfigModel $attribute = null) use ($scope, $option) { + if ($attribute) { + $attributeScopedConfig = $attribute->toArray($scope); + return !empty($attributeScopedConfig[$option]); + } + + return false; + } + ); + } + } + + /** + * @return array + */ + protected function getRestrictedToMoveFields() + { + return $this->fieldsRestrictedToMove; + } +} diff --git a/src/Marello/Bundle/ProductBundle/EventListener/Datagrid/ProductGridListener.php b/src/Marello/Bundle/ProductBundle/EventListener/Datagrid/ProductGridListener.php index 3be15ae08..d7f1e3530 100644 --- a/src/Marello/Bundle/ProductBundle/EventListener/Datagrid/ProductGridListener.php +++ b/src/Marello/Bundle/ProductBundle/EventListener/Datagrid/ProductGridListener.php @@ -5,14 +5,74 @@ use Doctrine\ORM\Query; use Doctrine\ORM\Query\Expr\GroupBy; +use Marello\Bundle\ProductBundle\Entity\ProductStatus; use Oro\Bundle\DataGridBundle\Datasource\Orm\OrmDatasource; use Oro\Bundle\DataGridBundle\Event\BuildAfter; +use Oro\Bundle\DataGridBundle\Event\BuildBefore; use Oro\Bundle\DataGridBundle\Event\OrmResultBefore; use Marello\Bundle\ProductBundle\Datagrid\ORM\Query\ProductsGridSqlWalker; class ProductGridListener { + /** + * @param BuildBefore $event + */ + public function onBuildBefore(BuildBefore $event) + { + $config = $event->getConfig(); + $ormQuery = $config->getOrmQuery(); + $ormQuery + ->addSelect('i.id as image') + ->addSelect('(CASE WHEN p.image IS NOT NULL THEN true ELSE false END) as hasImage') + ->addSelect('s.label as status') + ->addLeftJoin('p.image', 'i') + ->addLeftJoin('p.status', 's') + ->addGroupBy('s.label'); + $columns = $config->offsetGetByPath('[columns]'); + $columns = array_merge( + [ + 'image' => [ + 'label' => 'marello.product.image.label', + 'type' => 'twig', + 'frontend_type' => 'html', + 'template' => 'MarelloProductBundle:Product/Datagrid/Property:image.html.twig', + ], + 'status' => [ + 'label' => 'marello.product.status.label', + 'frontend_type' => 'string', + ] + ], + $columns + ); + $config->offsetSetByPath('[columns]', $columns); + $config + ->offsetSetByPath( + '[sorters][columns][status]', + ['data_name' => 's.label'] + ) + ->offsetSetByPath( + '[filters][columns][image]', + [ + 'type' => 'boolean', + 'data_name' => 'hasImage', + ] + ) + ->offsetSetByPath( + '[filters][columns][status]', + [ + 'type' => 'entity', + 'data_name' => 's', + 'options' => [ + 'field_options' => [ + 'multiple' => true, + 'class' => ProductStatus::class + ] + ] + ] + ); + } + /** * @param OrmResultBefore $event */ diff --git a/src/Marello/Bundle/ProductBundle/EventListener/Doctrine/ProductAttributeFamilyEventListener.php b/src/Marello/Bundle/ProductBundle/EventListener/Doctrine/ProductAttributeFamilyEventListener.php deleted file mode 100644 index 47a9b07fb..000000000 --- a/src/Marello/Bundle/ProductBundle/EventListener/Doctrine/ProductAttributeFamilyEventListener.php +++ /dev/null @@ -1,44 +0,0 @@ -productTypesProvider = $productTypesProvider; - } - - /** - * @param LifecycleEventArgs $args - */ - public function prePersist(LifecycleEventArgs $args) - { - $em = $args->getEntityManager(); - $entity = $args->getEntity(); - if ($entity instanceof Product && $entity->getType() && !$entity->getAttributeFamily()) { - $productType = $this->productTypesProvider->getProductType($entity->getType()); - if ($productType && $productType->getAttributeFamilyCode()) { - $attributeFamily = $em - ->getRepository(AttributeFamily::class) - ->findOneBy(['code' => $productType->getAttributeFamilyCode()]); - if ($attributeFamily) { - $entity->setAttributeFamily($attributeFamily); - } - } - } - } -} diff --git a/src/Marello/Bundle/ProductBundle/EventListener/Doctrine/ProductDropshipEventListener.php b/src/Marello/Bundle/ProductBundle/EventListener/Doctrine/ProductDropshipEventListener.php index 87b1dd40b..7f0f17a39 100644 --- a/src/Marello/Bundle/ProductBundle/EventListener/Doctrine/ProductDropshipEventListener.php +++ b/src/Marello/Bundle/ProductBundle/EventListener/Doctrine/ProductDropshipEventListener.php @@ -59,6 +59,17 @@ public function preUpdate(PreUpdateEventArgs $args) } } + /** + * @param LifecycleEventArgs $args + */ + public function preRemove(LifecycleEventArgs $args) + { + $entity = $args->getEntity(); + if ($entity instanceof ProductSupplierRelation && $entity->getCanDropship() === true) { + $this->event= new ProductDropshipEvent($entity, false); + } + } + public function postFlush() { if ($this->event !== null) { diff --git a/src/Marello/Bundle/ProductBundle/Form/EventListener/AttributeFamilySubscriber.php b/src/Marello/Bundle/ProductBundle/Form/EventListener/AttributeFamilySubscriber.php deleted file mode 100644 index 1187e916c..000000000 --- a/src/Marello/Bundle/ProductBundle/Form/EventListener/AttributeFamilySubscriber.php +++ /dev/null @@ -1,90 +0,0 @@ -em = $em; - $this->productTypesProvider = $productTypesProvider; - } - - /** - * Get subscribed events - * @return array - */ - public static function getSubscribedEvents() - { - return [ - FormEvents::POST_SET_DATA => 'postSetData', - FormEvents::PRE_SUBMIT => 'preSubmit' - ]; - } - - /** - * @param FormEvent $event - */ - public function postSetData(FormEvent $event) - { - /** @var Product $product */ - $product = $event->getData(); - $form = $event->getForm(); - $type = $form->get('type')->getData(); - - if (!$product instanceof ProductInterface) { - return; - } - - if (!$product->getAttributeFamily() && $type) { - $productType = $this->productTypesProvider->getProductType($type); - if ($productType) { - $attributeFamily = $this->em - ->getRepository(AttributeFamily::class) - ->findOneBy(['code' => $productType->getAttributeFamilyCode()]); - $product->setAttributeFamily($attributeFamily); - } - } - - $event->setData($product); - } - - public function preSubmit(FormEvent $event) - { - $data = $event->getData(); - if ($data['type']) { - $productType = $this->productTypesProvider->getProductType($data['type']); - if ($productType) { - $attributeFamily = $this->em - ->getRepository(AttributeFamily::class) - ->findOneBy(['code' => $productType->getAttributeFamilyCode()]); - $data['attributeFamily'] = $attributeFamily; - } - - $event->setData($data); - } - } -} diff --git a/src/Marello/Bundle/ProductBundle/Form/Handler/ProductHandler.php b/src/Marello/Bundle/ProductBundle/Form/Handler/ProductHandler.php index 75386e678..e184f16a9 100644 --- a/src/Marello/Bundle/ProductBundle/Form/Handler/ProductHandler.php +++ b/src/Marello/Bundle/ProductBundle/Form/Handler/ProductHandler.php @@ -8,7 +8,6 @@ use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\ProductBundle\Entity\ProductChannelTaxRelation; use Marello\Bundle\ProductBundle\Entity\ProductSupplierRelation; -use Marello\Bundle\ProductBundle\Form\Type\ProductType; use Marello\Bundle\SalesBundle\Entity\SalesChannel; use Marello\Bundle\SupplierBundle\Entity\Supplier; use Oro\Bundle\FormBundle\Form\Handler\RequestHandlerTrait; diff --git a/src/Marello/Bundle/ProductBundle/Form/Handler/ProductsSalesChannelsAssignHandler.php b/src/Marello/Bundle/ProductBundle/Form/Handler/ProductsSalesChannelsAssignHandler.php index 5b7eccfec..8ff7d9874 100644 --- a/src/Marello/Bundle/ProductBundle/Form/Handler/ProductsSalesChannelsAssignHandler.php +++ b/src/Marello/Bundle/ProductBundle/Form/Handler/ProductsSalesChannelsAssignHandler.php @@ -3,24 +3,32 @@ namespace Marello\Bundle\ProductBundle\Form\Handler; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\QueryBuilder; -use Marello\Bundle\ProductBundle\Async\ProductsAssignSalesChannelsProcessor; -use Marello\Bundle\ProductBundle\Entity\Product; -use Marello\Bundle\SalesBundle\Entity\SalesChannel; + +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +use Oro\Component\MessageQueue\Util\JSON; use Oro\Bundle\DataGridBundle\Datagrid\Manager; use Oro\Bundle\DataGridBundle\Datasource\Orm\OrmDatasource; use Oro\Bundle\FormBundle\Form\Handler\RequestHandlerTrait; use Oro\Component\MessageQueue\Client\MessageProducerInterface; -use Symfony\Component\Form\FormInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; + +use Marello\Bundle\ProductBundle\Entity\Product; +use Marello\Bundle\SalesBundle\Entity\SalesChannel; +use Marello\Bundle\ProductBundle\Async\ProductsAssignSalesChannelsProcessor; class ProductsSalesChannelsAssignHandler { use RequestHandlerTrait; + /** @var int size that will determine wether the products should be saved immediately or send to the queue */ const FLUSH_BATCH_SIZE = 100; + /** @var int max size of product ids per message to prevent having a single big message */ + const MESSAGE_PRODUCT_ID_SIZE = 1000; + /** * @var FormInterface */ @@ -68,15 +76,20 @@ public function __construct( } /** - * @return bool + * @return array * @throws \Exception */ - public function process() + public function process(): array { + $filtersFromRequest = null; + if (null !== $this->request->query->get('filters')) { + $filtersFromRequest = JSON::encode($this->request->query->get('filters')); + } + $this->form->setData([ 'inset' => $this->request->query->get('inset'), 'products' => $this->request->query->get('values'), - 'filters' => json_encode($this->request->query->get('filters')), + 'filters' => $filtersFromRequest ]); if (in_array($this->request->getMethod(), ['POST', 'PUT'])) { @@ -85,9 +98,11 @@ public function process() if ($this->form->isValid()) { $inset = $this->form->get('inset')->getData(); $products = $this->form->get('products')->getData(); - $filters = json_decode($this->form->get('filters')->getData(), true); $addChannels = $this->form->get('addSalesChannels')->getData(); - + $filters = null; + if (null !== $this->form->get('filters')->getData()) { + JSON::decode($this->form->get('filters')->getData()); + } return $this->onSuccess( $addChannels, $inset, @@ -101,13 +116,14 @@ public function process() } /** - * @param array $addChannels - * @param string $inset - * @param string|null $products - * @param array|null $filters + * @param $addChannels + * @param $inset + * @param null $products + * @param null $filters * @return array + * @throws \Oro\Component\MessageQueue\Transport\Exception\Exception */ - private function onSuccess($addChannels, $inset, $products = null, $filters = null) + private function onSuccess($addChannels, $inset, $products = null, $filters = null): array { $isAllSelected = $this->isAllSelected($inset); $productIds = explode(',', $products); @@ -127,32 +143,28 @@ private function onSuccess($addChannels, $inset, $products = null, $filters = nu $queryBuilder->andWhere($queryBuilder->expr()->notIn('p.id', $productIds)); } - $countQueryBuilder = clone $queryBuilder; - - $result = $countQueryBuilder - ->resetDQLParts(['select', 'groupBy']) - ->select(['COUNT(DISTINCT p.id) AS count']) + $queryResult = $queryBuilder ->getQuery() ->setFirstResult(0) ->setMaxResults(null) - ->getOneOrNullResult(); - if ((int)$result['count'] <= self::FLUSH_BATCH_SIZE) { - $this->processSmallData($queryBuilder, $addChannels); + ->getArrayResult(); + + if ((int)count($queryResult) <= self::FLUSH_BATCH_SIZE) { + $this->processSmallData($queryResult, $addChannels); return [ 'success' => true, 'type' => 'success', 'message' => 'marello.product.messages.success.sales_channels.assignment' ]; - } else { - $this->processBigData($inset, $productIds, $filters, $addChannels); - - return [ - 'success' => true, - 'type' => 'info', - 'message' => 'marello.product.messages.success.sales_channels.assignment_started' - ]; } + + $this->processBigData($queryResult, $addChannels); + return [ + 'success' => true, + 'type' => 'info', + 'message' => 'marello.product.messages.success.sales_channels.assignment_started' + ]; } return ['success' => false, 'message' => 'marello.product.messages.error.sales_channels.assignment']; @@ -162,36 +174,33 @@ private function onSuccess($addChannels, $inset, $products = null, $filters = nu * @param string $inset * @return bool */ - protected function isAllSelected($inset) + protected function isAllSelected($inset): bool { return $inset === '0'; } /** - * @param QueryBuilder $queryBuilder + * @param array $queryResult * @param SalesChannel[] $salesChannels */ - private function processSmallData($queryBuilder, array $salesChannels) + private function processSmallData(array $queryResult, array $salesChannels): void { - $result = $queryBuilder - ->getQuery() - ->setFirstResult(0) - ->setMaxResults(null) - ->getResult(); + $productIds = $this->getProductIdsFromResult($queryResult); + $products = $this->entityManager + ->getRepository(Product::class) + ->findBy(['id' => $productIds]); + $iteration = 1; - foreach ($result as $entity) { - /** @var Product $entity */ - $entity = $entity[0]; + /** @var Product $product */ + foreach ($products as $product) { $addedChannels = 0; foreach ($salesChannels as $salesChannel) { - if (!$entity->hasChannel($salesChannel)) { - $entity->addChannel($salesChannel); + if (!$product->hasChannel($salesChannel)) { + $product->addChannel($salesChannel); + $this->entityManager->persist($product); $addedChannels++; } } - if ($addedChannels > 0) { - $this->entityManager->persist($entity); - } if (($iteration % self::FLUSH_BATCH_SIZE) === 0) { $this->entityManager->flush(); @@ -200,27 +209,46 @@ private function processSmallData($queryBuilder, array $salesChannels) $iteration++; } } + $this->entityManager->flush(); } /** - * @param int $inset - * @param array|null $products - * @param array|null $filters - * @param SalesChannel[]|null $salesChannels + * @param array $queryResult + * @param SalesChannel[] $salesChannels + * @throws \Oro\Component\MessageQueue\Transport\Exception\Exception */ - private function processBigData($inset, array $products = null, array $filters = null, array $salesChannels = null) + private function processBigData(array $queryResult, array $salesChannels): void { - $channelIds = []; - foreach ($salesChannels as $salesChannel) { - $channelIds[] = $salesChannel->getCode(); + $channelIds = array_map( + static function (SalesChannel $channel) { + return $channel->getId(); + }, + $salesChannels + ); + + $productIds = $this->getProductIdsFromResult($queryResult); + if (count($productIds) > self::MESSAGE_PRODUCT_ID_SIZE) { + $chunks = array_chunk($productIds, self::MESSAGE_PRODUCT_ID_SIZE); + foreach ($chunks as $chunk) { + $this->sendProductsToMessageQueue($chunk, $channelIds); + } + } else { + $this->sendProductsToMessageQueue($productIds, $channelIds); } + } + + /** + * @param array $productIds + * @param array $channelIds + * @throws \Oro\Component\MessageQueue\Transport\Exception\Exception + */ + private function sendProductsToMessageQueue(array $productIds, array $channelIds) + { $this->messageProducer->send( ProductsAssignSalesChannelsProcessor::TOPIC, [ - 'inset' => $inset, - 'products' => $products, - 'filters' => $filters, + 'products' => $productIds, 'salesChannels' => $channelIds, 'jobId' => md5(rand(1, 5)) ] @@ -228,12 +256,29 @@ private function processBigData($inset, array $products = null, array $filters = } /** - * Returns form instance + * Returns form view instance * - * @return FormInterface + * @return FormView */ - public function getFormView() + public function getFormView(): FormView { return $this->form->createView(); } + + /** + * @param array $result + * @return array + */ + private function getProductIdsFromResult(array $result): array + { + return array_map( + static function ($entityAsArray) { + if (array_key_exists('id', $entityAsArray)) { + return $entityAsArray['id']; + } + return null; + }, + $result + ); + } } diff --git a/src/Marello/Bundle/ProductBundle/Form/Type/ProductChannelTaxRelationCollectionType.php b/src/Marello/Bundle/ProductBundle/Form/Type/ProductChannelTaxRelationCollectionType.php index 302fc3ab3..cf2f485e6 100644 --- a/src/Marello/Bundle/ProductBundle/Form/Type/ProductChannelTaxRelationCollectionType.php +++ b/src/Marello/Bundle/ProductBundle/Form/Type/ProductChannelTaxRelationCollectionType.php @@ -23,7 +23,7 @@ public function configureOptions(OptionsResolver $resolver) 'constraints' => [new Valid()], 'prototype_name' => '__nameproductchanneltaxrelation__', 'prototype' => true, - 'handle_primary' => false, + 'handle_primary' => true, ]); } diff --git a/src/Marello/Bundle/ProductBundle/Form/Type/ProductSupplierRelationType.php b/src/Marello/Bundle/ProductBundle/Form/Type/ProductSupplierRelationType.php index 4b8d5f6bf..db2115183 100644 --- a/src/Marello/Bundle/ProductBundle/Form/Type/ProductSupplierRelationType.php +++ b/src/Marello/Bundle/ProductBundle/Form/Type/ProductSupplierRelationType.php @@ -3,9 +3,13 @@ namespace Marello\Bundle\ProductBundle\Form\Type; use Marello\Bundle\ProductBundle\Entity\ProductSupplierRelation; +use Marello\Bundle\SupplierBundle\Entity\Supplier; use Marello\Bundle\SupplierBundle\Form\Type\SupplierSelectType; +use Oro\Bundle\CurrencyBundle\Model\LocaleSettings; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\Valid; @@ -13,6 +17,19 @@ class ProductSupplierRelationType extends AbstractType { const BLOCK_PREFIX = 'marello_product_supplier_relation_form'; + /** + * @var LocaleSettings + */ + protected $localeSettings; + + /** + * @param LocaleSettings $localeSettings + */ + public function __construct(LocaleSettings $localeSettings) + { + $this->localeSettings = $localeSettings; + } + /** * {@inheritdoc} */ @@ -46,6 +63,16 @@ public function configureOptions(OptionsResolver $resolver) ]); } + public function finishView(FormView $view, FormInterface $form, array $options) + { + $data = $form->get('supplier')->getData(); + if ($data instanceof Supplier) { + $view->vars['currency'] = $this->localeSettings->getCurrencySymbolByCurrency($data->getCurrency()); + } else { + $view->vars['currency'] = null; + } + } + /** * {@inheritdoc} */ diff --git a/src/Marello/Bundle/ProductBundle/Form/Type/ProductType.php b/src/Marello/Bundle/ProductBundle/Form/Type/ProductType.php index 6d195aa46..03fa864ea 100644 --- a/src/Marello/Bundle/ProductBundle/Form/Type/ProductType.php +++ b/src/Marello/Bundle/ProductBundle/Form/Type/ProductType.php @@ -6,11 +6,12 @@ use Marello\Bundle\PricingBundle\Form\EventListener\ChannelPricingSubscriber; use Marello\Bundle\PricingBundle\Form\EventListener\PricingSubscriber; use Marello\Bundle\ProductBundle\Entity\Product; -use Marello\Bundle\ProductBundle\Form\EventListener\AttributeFamilySubscriber; use Marello\Bundle\SalesBundle\Form\EventListener\DefaultSalesChannelSubscriber; use Marello\Bundle\TaxBundle\Form\Type\TaxCodeSelectType; use Oro\Bundle\AttachmentBundle\Form\Type\ImageType; +use Oro\Bundle\FormBundle\Form\Extension\StripTagsExtension; use Oro\Bundle\FormBundle\Form\Type\EntityIdentifierType; +use Oro\Bundle\LocaleBundle\Form\Type\LocalizedFallbackValueCollectionType; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\AbstractType; @@ -19,6 +20,7 @@ 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\Valid; class ProductType extends AbstractType @@ -40,32 +42,28 @@ class ProductType extends AbstractType */ protected $channelPricingSubscriber; - /** - * @var AttributeFamilySubscriber - */ - protected $attributeFamilySubscriber; - /** * @var EventSubscriberInterface */ protected $subscriptionProductSubscriber; /** + * ProductType constructor. * @param DefaultSalesChannelSubscriber $defaultSalesChannelSubscriber * @param PricingSubscriber $pricingSubscriber * @param ChannelPricingSubscriber $channelPricingSubscriber - * @param AttributeFamilySubscriber $attributeFamilySubscriber + * @param EventSubscriberInterface|null $subscriptionProductSubscriber */ public function __construct( DefaultSalesChannelSubscriber $defaultSalesChannelSubscriber, PricingSubscriber $pricingSubscriber, ChannelPricingSubscriber $channelPricingSubscriber, - AttributeFamilySubscriber $attributeFamilySubscriber + EventSubscriberInterface $subscriptionProductSubscriber = null ) { $this->defaultSalesChannelSubscriber = $defaultSalesChannelSubscriber; $this->pricingSubscriber = $pricingSubscriber; $this->channelPricingSubscriber = $channelPricingSubscriber; - $this->attributeFamilySubscriber = $attributeFamilySubscriber; + $this->subscriptionProductSubscriber = $subscriptionProductSubscriber; } /** @@ -75,13 +73,16 @@ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('type', HiddenType::class) - ->add('attributeFamily', HiddenType::class) ->add( - 'name', - TextType::class, + 'names', + LocalizedFallbackValueCollectionType::class, [ + 'label' => 'marello.product.names.label', 'required' => true, - 'label' => 'marello.product.name.label', + 'entry_options' => [ + 'constraints' => [new NotBlank(['message' => 'marello.product.messages.error.names.blank'])], + StripTagsExtension::OPTION_NAME => true, + ], ] ) ->add( @@ -194,7 +195,6 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->addEventSubscriber($this->defaultSalesChannelSubscriber); $builder->addEventSubscriber($this->pricingSubscriber); $builder->addEventSubscriber($this->channelPricingSubscriber); - $builder->addEventSubscriber($this->attributeFamilySubscriber); if ($this->subscriptionProductSubscriber) { $builder->addEventSubscriber($this->subscriptionProductSubscriber); } @@ -210,6 +210,7 @@ public function configureOptions(OptionsResolver $resolver) 'intention' => 'product', 'single_form' => true, 'enable_attributes' => true, + 'enable_attribute_family' => true, 'constraints' => [new Valid()], ]); } @@ -221,14 +222,4 @@ public function getBlockPrefix() { return self::BLOCK_PREFIX; } - - /** - * Added because of keeping BC - * @deprecated will be removed in 3.0 - * @param EventSubscriberInterface|null $subscriptionProductSubscriber - */ - public function setEventSubscriberInterface(EventSubscriberInterface $subscriptionProductSubscriber = null) - { - $this->subscriptionProductSubscriber = $subscriptionProductSubscriber; - } } diff --git a/src/Marello/Bundle/ProductBundle/MarelloProductBundle.php b/src/Marello/Bundle/ProductBundle/MarelloProductBundle.php index 904026624..9ef1989da 100644 --- a/src/Marello/Bundle/ProductBundle/MarelloProductBundle.php +++ b/src/Marello/Bundle/ProductBundle/MarelloProductBundle.php @@ -3,6 +3,8 @@ namespace Marello\Bundle\ProductBundle; use Marello\Bundle\ProductBundle\DependencyInjection\Compiler\ProductTypesPass; +use Marello\Bundle\ProductBundle\Entity\Product; +use Oro\Bundle\LocaleBundle\DependencyInjection\Compiler\DefaultFallbackExtensionPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -13,8 +15,14 @@ class MarelloProductBundle extends Bundle */ public function build(ContainerBuilder $container) { - $container->addCompilerPass(new ProductTypesPass()); - parent::build($container); + + $container + ->addCompilerPass(new ProductTypesPass()) + ->addCompilerPass(new DefaultFallbackExtensionPass([ + Product::class => [ + 'name' => 'names' + ] + ])); } } diff --git a/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/AssignAttributesToDefaultFamily.php b/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/AssignAttributesToDefaultFamily.php new file mode 100644 index 000000000..74b9a76ec --- /dev/null +++ b/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/AssignAttributesToDefaultFamily.php @@ -0,0 +1,117 @@ + [ + 'searchable' => true, + 'filterable' => true, + 'filter_by' => 'exact_value', + 'sortable' => true, + ], + 'names' => [ + 'searchable' => true, + 'filterable' => true, + 'filter_by' => 'exact_value', + 'sortable' => true, + ], + 'channels' => [ + 'visible' => true + ], + 'status' => [ + 'visible' => true + ], + 'prices' => [ + 'visible' => true + ], + 'channelPrices' => [ + 'visible' => true + ], + 'taxCode' => [ + 'visible' => true + ], + 'salesChannelTaxCodes' => [ + 'visible' => true + ], + 'weight' => [ + 'visible' => true + ], + 'manufacturingCode' => [ + 'visible' => true + ], + 'warranty' => [ + 'visible' => true + ], + 'suppliers' => [ + 'visible' => true + ], + 'categories' => [ + 'visible' => true + ], + 'image' => [ + 'visible' => true + ], + ]; + + /** + * {@inheritdoc} + */ + public function load(ObjectManager $manager) + { + $this->updateProductAttributes(self::ATTRIBUTES); + $this->addGroup($manager); + } + + /** + * @param ObjectManager $manager + */ + private function addGroup(ObjectManager $manager) + { + $attributeFamilyRepository = $manager->getRepository(AttributeFamily::class); + + $defaultFamily = + $attributeFamilyRepository->findOneBy([ + 'code' => LoadDefaultAttributeFamilyData::DEFAULT_FAMILY_CODE + ]); + + $attributeGroup = $defaultFamily->getAttributeGroup(LoadDefaultAttributeFamilyData::GENERAL_GROUP_CODE); + + $configManager = $this->getConfigManager(); + foreach (self::ATTRIBUTES as $attribute => $data) { + $fieldConfigModel = $configManager->getConfigFieldModel(Product::class, $attribute); + $attributeGroupRelation = new AttributeGroupRelation(); + $attributeGroupRelation->setEntityConfigFieldId($fieldConfigModel->getId()); + $attributeGroup->addAttributeRelation($attributeGroupRelation); + } + + $manager->persist($attributeGroup); + $manager->flush(); + } + + /** + * @inheritdoc + */ + public function getDependencies() + { + return [ + LoadDefaultAttributeFamilyData::class + ]; + } +} diff --git a/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/LoadDefaultAttributeFamilyData.php b/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/LoadDefaultAttributeFamilyData.php index c7412a979..7b45dfebf 100644 --- a/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/LoadDefaultAttributeFamilyData.php +++ b/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/LoadDefaultAttributeFamilyData.php @@ -5,20 +5,19 @@ use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Common\Persistence\ObjectManager; - -use Oro\Bundle\UserBundle\Entity\User; -use Oro\Bundle\OrganizationBundle\Entity\Organization; -use Oro\Bundle\UserBundle\Migrations\Data\ORM\LoadAdminUserData; -use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeGroup; +use Marello\Bundle\ProductBundle\Entity\Product; use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeFamily; +use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeGroup; +use Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue; +use Oro\Bundle\OrganizationBundle\Entity\Organization; use Oro\Bundle\OrganizationBundle\Migrations\Data\ORM\LoadOrganizationAndBusinessUnitData; - -use Marello\Bundle\ProductBundle\Entity\Product; +use Oro\Bundle\UserBundle\Migrations\Data\ORM\LoadAdminUserData; class LoadDefaultAttributeFamilyData extends AbstractFixture implements DependentFixtureInterface { const DEFAULT_FAMILY_CODE = 'marello_default'; const GENERAL_GROUP_CODE = 'general'; + const GENERAL_GROUP_LABEL = 'General'; /** @var ObjectManager $manager */ protected $manager; @@ -28,7 +27,7 @@ class LoadDefaultAttributeFamilyData extends AbstractFixture implements Dependen */ protected $data = [ [ - 'groupLabel' => 'General', + 'groupLabel' => self::GENERAL_GROUP_LABEL, 'groupCode' => self::GENERAL_GROUP_CODE, 'attributes' => [], 'groupVisibility' => false @@ -50,16 +49,12 @@ public function getDependencies() */ public function load(ObjectManager $manager) { - /** @var User $user */ - $user = $manager - ->getRepository(User::class)->findOneBy(['username' => 'admin']); $organization = $this->getOrganization($manager); $attributeFamily = new AttributeFamily(); $attributeFamily->setCode(self::DEFAULT_FAMILY_CODE); $attributeFamily->setEntityClass(Product::class); - $attributeFamily->setDefaultLabel('Default'); - $attributeFamily->setOrganization($organization); - $attributeFamily->setOwner($user); + $attributeFamily->addLabel((new LocalizedFallbackValue())->setString('Default')); + $attributeFamily->setOwner($organization); $this->setReference(self::DEFAULT_FAMILY_CODE, $attributeFamily); @@ -95,7 +90,7 @@ protected function addGroupsWithAttributesToFamily( ) { foreach ($groupsData as $groupData) { $attributeGroup = new AttributeGroup(); - $attributeGroup->setDefaultLabel($groupData['groupLabel']); + $attributeGroup->addLabel((new LocalizedFallbackValue())->setString($groupData['groupLabel'])); $attributeGroup->setIsVisible($groupData['groupVisibility']); $attributeGroup->setCode($groupData['groupCode']); $attributeFamily->addAttributeGroup($attributeGroup); diff --git a/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/MakeProductAttributesTrait.php b/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/MakeProductAttributesTrait.php index fbd1e033c..e242a5599 100644 --- a/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/MakeProductAttributesTrait.php +++ b/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/MakeProductAttributesTrait.php @@ -19,7 +19,7 @@ trait MakeProductAttributesTrait * @param array $fields * @param string $owner */ - private function makeProductAttributes(array $fields, $owner = ExtendScope::OWNER_CUSTOM) + private function makeProductAttributes(array $fields, $owner = ExtendScope::ORIGIN_SYSTEM) { $configManager = $this->getConfigManager(); $configHelper = $this->container->get('oro_entity_config.config.config_helper'); diff --git a/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/UpdateExistingProductsWithLocalizedName.php b/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/UpdateExistingProductsWithLocalizedName.php new file mode 100644 index 000000000..f02872d12 --- /dev/null +++ b/src/Marello/Bundle/ProductBundle/Migrations/Data/ORM/UpdateExistingProductsWithLocalizedName.php @@ -0,0 +1,46 @@ +getRepository(Product::class) + ->createQueryBuilder('p') + ->leftJoin('p.names', 'n') + ->having('COUNT(n.id) = 0') + ->groupBy('p.id') + ->getQuery() + ->getResult(); + + if (count($products) === 0) { + return; + } + + foreach ($products as $product) { + $product->setDefaultName($product->getDenormalizedDefaultName()); + $manager->persist($product); + } + + $manager->flush(); + } +} diff --git a/src/Marello/Bundle/ProductBundle/Migrations/Schema/MarelloProductBundleInstaller.php b/src/Marello/Bundle/ProductBundle/Migrations/Schema/MarelloProductBundleInstaller.php index 8a5a36551..11e693e07 100644 --- a/src/Marello/Bundle/ProductBundle/Migrations/Schema/MarelloProductBundleInstaller.php +++ b/src/Marello/Bundle/ProductBundle/Migrations/Schema/MarelloProductBundleInstaller.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Schema\Schema; +use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope; use Oro\Bundle\MigrationBundle\Migration\QueryBag; use Oro\Bundle\MigrationBundle\Migration\Installation; use Oro\Bundle\AttachmentBundle\Migration\Extension\AttachmentExtensionAwareTrait; @@ -28,7 +29,7 @@ class MarelloProductBundleInstaller implements */ public function getMigrationVersion() { - return 'v1_7'; + return 'v1_9'; } /** @@ -38,6 +39,7 @@ public function up(Schema $schema, QueryBag $queries) { /** Tables generation **/ $this->createMarelloProductProductTable($schema); + $this->createMarelloProductProductNameTable($schema); $this->createMarelloProductProductStatusTable($schema); $this->createMarelloProductSaleschannelTable($schema); $this->createMarelloProductVariantTable($schema); @@ -46,6 +48,7 @@ public function up(Schema $schema, QueryBag $queries) /** Foreign keys generation **/ $this->addMarelloProductProductForeignKeys($schema); + $this->addMarelloProductNameForeignKeys($schema); $this->addMarelloProductSaleschannelForeignKeys($schema); $this->addMarelloProductSalesChannelTaxRelationForeignKeys($schema); $this->addMarelloProductSupplierRelationForeignKeys($schema); @@ -72,19 +75,9 @@ protected function createMarelloProductProductTable(Schema $schema) $table->addColumn('name', 'string', ['length' => 255]); $table->addColumn('sku', 'string', ['length' => 255]); $table->addColumn('manufacturing_code', 'string', ['length' => 255, 'notnull' => false]); - $table->addColumn( - 'price', - 'money', - ['notnull' => false, 'precision' => 19, 'scale' => 4, 'comment' => '(DC2Type:money)'] - ); $table->addColumn('created_at', 'datetime'); - $table->addColumn('updated_at', 'datetime'); + $table->addColumn('updated_at', 'datetime', ['notnull' => false]); $table->addColumn('type', 'string', ['notnull' => false, 'length' => 255]); - $table->addColumn( - 'cost', - 'money', - ['notnull' => false, 'precision' => 19, 'scale' => 4, 'comment' => '(DC2Type:money)'] - ); $table->addColumn('data', 'json_array', ['notnull' => false, 'comment' => '(DC2Type:json_array)']); $table->addColumn('weight', 'float', ['notnull' => false]); $table->addColumn('warranty', 'integer', ['notnull' => false]); @@ -99,6 +92,18 @@ protected function createMarelloProductProductTable(Schema $schema) $table->addIndex(['tax_code_id']); } + /** + * @param Schema $schema + */ + protected function createMarelloProductProductNameTable(Schema $schema) + { + $table = $schema->createTable('marello_product_product_name'); + $table->addColumn('product_id', 'integer', []); + $table->addColumn('localized_value_id', 'integer', []); + $table->setPrimaryKey(['product_id', 'localized_value_id']); + $table->addUniqueIndex(['localized_value_id'], 'uniq_ba57d521eb576e89'); + } + /** * Create marello_product_product_status table * @@ -233,6 +238,26 @@ protected function addMarelloProductProductForeignKeys(Schema $schema) ); } + /** + * @param Schema $schema + */ + protected function addMarelloProductNameForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_product_product_name'); + $table->addForeignKeyConstraint( + $schema->getTable('oro_fallback_localization_val'), + ['localized_value_id'], + ['id'], + ['onUpdate' => null, 'onDelete' => 'CASCADE'] + ); + $table->addForeignKeyConstraint( + $schema->getTable('marello_product_product'), + ['product_id'], + ['id'], + ['onUpdate' => null, 'onDelete' => 'CASCADE'] + ); + } + /** * Add marello_product_saleschannel foreign keys. * @@ -317,6 +342,8 @@ protected function addImageRelation(Schema $schema) 'image', [ 'importexport' => ['excluded' => true], + 'attribute' => ['is_attribute' => true], + 'extend' => ['owner' => ExtendScope::OWNER_CUSTOM], 'attachment' => [ 'acl_protected' => false ] diff --git a/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_3/MarelloProductBundle.php b/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_3/MarelloProductBundle.php index a305cdf0b..1f925382f 100644 --- a/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_3/MarelloProductBundle.php +++ b/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_3/MarelloProductBundle.php @@ -3,9 +3,6 @@ namespace Marello\Bundle\ProductBundle\Migrations\Schema\v1_3; use Doctrine\DBAL\Schema\Schema; -use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope; -use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension; -use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface; use Oro\Bundle\MigrationBundle\Migration\Migration; use Oro\Bundle\MigrationBundle\Migration\QueryBag; diff --git a/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_8/RemovePriceAndCostColumns.php b/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_8/RemovePriceAndCostColumns.php new file mode 100644 index 000000000..7c368cd8a --- /dev/null +++ b/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_8/RemovePriceAndCostColumns.php @@ -0,0 +1,26 @@ +getTable(MarelloProductBundleInstaller::PRODUCT_TABLE); + if ($table->hasColumn('price')) { + $table->dropColumn('price'); + } + if ($table->hasColumn('cost')) { + $table->dropColumn('cost'); + } + $table->changeColumn('updated_at', ['notnull' => false]); + } +} diff --git a/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_9/AddLocalizedNamesToProduct.php b/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_9/AddLocalizedNamesToProduct.php new file mode 100644 index 000000000..a1f2502e3 --- /dev/null +++ b/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_9/AddLocalizedNamesToProduct.php @@ -0,0 +1,70 @@ +createMarelloProductProductNameTable($schema); + $this->addMarelloProductNameForeignKeys($schema); + + $dropFields = ['name', 'cost']; + $dropFieldInConfigSql = <<addSql( + $dropFieldInConfigSql, + ['field_name' => $field, 'class_name' => Product::class], + ['field_name' => Type::STRING, 'class_name' => Type::STRING] + ); + $queries->addPostQuery($dropFieldInConfigQuery); + } + } + + /** + * @param Schema $schema + */ + protected function createMarelloProductProductNameTable(Schema $schema) + { + $table = $schema->createTable('marello_product_product_name'); + $table->addColumn('product_id', 'integer', []); + $table->addColumn('localized_value_id', 'integer', []); + $table->setPrimaryKey(['product_id', 'localized_value_id']); + $table->addUniqueIndex(['localized_value_id'], 'uniq_ba57d521eb576e89'); + } + + /** + * @param Schema $schema + */ + protected function addMarelloProductNameForeignKeys(Schema $schema) + { + $table = $schema->getTable('marello_product_product_name'); + $table->addForeignKeyConstraint( + $schema->getTable('oro_fallback_localization_val'), + ['localized_value_id'], + ['id'], + ['onUpdate' => null, 'onDelete' => 'CASCADE'] + ); + $table->addForeignKeyConstraint( + $schema->getTable('marello_product_product'), + ['product_id'], + ['id'], + ['onUpdate' => null, 'onDelete' => 'CASCADE'] + ); + } +} diff --git a/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_9/UpdateAttachmentFieldConfigForProductImage.php b/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_9/UpdateAttachmentFieldConfigForProductImage.php new file mode 100644 index 000000000..9e7551101 --- /dev/null +++ b/src/Marello/Bundle/ProductBundle/Migrations/Schema/v1_9/UpdateAttachmentFieldConfigForProductImage.php @@ -0,0 +1,28 @@ +addPostQuery( + new UpdateEntityConfigFieldValueQuery(Product::class, 'image', 'attribute', 'is_attribute', true) + ); + $queries->addPostQuery( + new UpdateEntityConfigFieldValueQuery(Product::class, 'image', 'extend', 'owner', ExtendScope::OWNER_CUSTOM) + ); + } +} diff --git a/src/Marello/Bundle/ProductBundle/Model/ExtendProduct.php b/src/Marello/Bundle/ProductBundle/Model/ExtendProduct.php index f21c31d32..99461d6e9 100644 --- a/src/Marello/Bundle/ProductBundle/Model/ExtendProduct.php +++ b/src/Marello/Bundle/ProductBundle/Model/ExtendProduct.php @@ -2,28 +2,17 @@ namespace Marello\Bundle\ProductBundle\Model; -use Oro\Bundle\BusinessEntitiesBundle\Entity\BaseProduct; use Oro\Bundle\EntityConfigBundle\Metadata\Annotation as Oro; +use Oro\Bundle\LocaleBundle\Entity\Localization; +use Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue; /** - * Class ExtendProduct - * @package Marello\Bundle\ProductBundle\Model - * + * @method LocalizedFallbackValue getName(Localization $localization = null) + * @method LocalizedFallbackValue getDefaultName() + * @method setDefaultName(string $value) */ -class ExtendProduct extends BaseProduct +class ExtendProduct { - /** - * @Oro\ConfigField( - * defaultValues={ - * "importexport"={ - * "excluded"=true - * } - * } - * ) - */ - protected $price; - - public function __construct() { } diff --git a/src/Marello/Bundle/ProductBundle/Model/ProductType.php b/src/Marello/Bundle/ProductBundle/Model/ProductType.php index 9c0844519..5b2ddbc94 100644 --- a/src/Marello/Bundle/ProductBundle/Model/ProductType.php +++ b/src/Marello/Bundle/ProductBundle/Model/ProductType.php @@ -8,7 +8,6 @@ class ProductType extends ParameterBag implements ProductTypeInterface { const NAME_FIELD = 'name'; const LABEL_FIELD = 'label'; - const ATTRIBUTE_FAMILY_CODE = 'attribute_family_code'; /** * @inheritDoc @@ -25,12 +24,4 @@ public function getLabel() { return $this->get(self::LABEL_FIELD); } - - /** - * @inheritDoc - */ - public function getAttributeFamilyCode() - { - return $this->get(self::ATTRIBUTE_FAMILY_CODE); - } } diff --git a/src/Marello/Bundle/ProductBundle/Model/ProductTypeInterface.php b/src/Marello/Bundle/ProductBundle/Model/ProductTypeInterface.php index 51b7bc882..391bd5bc3 100644 --- a/src/Marello/Bundle/ProductBundle/Model/ProductTypeInterface.php +++ b/src/Marello/Bundle/ProductBundle/Model/ProductTypeInterface.php @@ -13,9 +13,4 @@ public function getName(); * @return string */ public function getLabel(); - - /** - * @return string - */ - public function getAttributeFamilyCode(); } diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/form.yml b/src/Marello/Bundle/ProductBundle/Resources/config/form.yml index 952a18f61..15572ded7 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/config/form.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/config/form.yml @@ -1,73 +1,54 @@ -parameters: - ## form types - marello_product.product_form.type.product_step_one.class: Marello\Bundle\ProductBundle\Form\Type\ProductStepOneType - marello_product.product_form.type.class: Marello\Bundle\ProductBundle\Form\Type\ProductType - marello_product.product_form.type.product_select.class: Marello\Bundle\ProductBundle\Form\Type\ProductSelectType - marello_product.product_form.type.product_type_select.class: Marello\Bundle\ProductBundle\Form\Type\ProductTypeSelectType - marello_product.product_form.type.product_select_collection.class: Marello\Bundle\ProductBundle\Form\Type\ProductSelectCollectionType - marello_product.product_form.type.product_sales_channel_aware_select.class: Marello\Bundle\ProductBundle\Form\Type\ProductSalesChannelAwareSelectType - marello_product.form.type.product_collection.class: Marello\Bundle\ProductBundle\Form\Type\ProductCollectionType - marello_product.product_variant_form.type.class: Marello\Bundle\ProductBundle\Form\Type\ProductVariantType - marello_product.form.type.product_supplier_select.class: Marello\Bundle\ProductBundle\Form\Type\ProductSupplierSelectType - marello_product.form.type.products_assign_sales_channels.class: Marello\Bundle\ProductBundle\Form\Type\ProductsAssignSalesChannelsType - - ## form handlers - marello_product.product_form.handler.class: Marello\Bundle\ProductBundle\Form\Handler\ProductHandler - marello_product.product_variant_form.handler.class: Marello\Bundle\ProductBundle\Form\Handler\ProductVariantHandler - services: ## form types marello_product.product_form.type.product_step_one: - class: '%marello_product.product_form.type.product_step_one.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductStepOneType' tags: - { name: form.type } marello_product.product_form.type: - class: '%marello_product.product_form.type.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductType' arguments: - '@marello_sales.form.event_listener.default_sales_channel_subscriber' - '@marello_productprice.pricing.form.event_listener.pricing_subscriber' - '@marello_productprice.pricing.form.event_listener.channel_pricing_subscriber' - - '@marello_product.product.form.event_listener.attribute_family' - calls: - - ['setEventSubscriberInterface', ['@?marello_subscription.form.event_listener.subscription_product']] + - '@?marello_subscription.form.event_listener.subscription_product' tags: - { name: form.type } marello_product.product_form.type.product_select: - class: '%marello_product.product_form.type.product_select.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductSelectType' tags: - { name: form.type } marello_product.product_form.type.product_select_collection: - class: '%marello_product.product_form.type.product_select_collection.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductSelectCollectionType' tags: - { name: form.type } marello_product.product_form.type.product_type_select: - class: '%marello_product.product_form.type.product_type_select.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductTypeSelectType' arguments: - '@marello_product.provider.product_types' tags: - { name: form.type } marello_product.product_form.type.product_sales_channel_aware_select: - class: '%marello_product.product_form.type.product_sales_channel_aware_select.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductSalesChannelAwareSelectType' tags: - { name: form.type } marello_product.form.type.product_supplier_select: - class: '%marello_product.form.type.product_supplier_select.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductSupplierSelectType' tags: - { name: form.type } marello_product.form.type.product_collection: - class: '%marello_product.form.type.product_collection.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductCollectionType' tags: - { name: form.type } marello_product.product_variant_form.type: - class: '%marello_product.product_variant_form.type.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductVariantType' tags: - { name: form.type } @@ -83,6 +64,8 @@ services: marello_product.form.type.product_supplier_relation: class: Marello\Bundle\ProductBundle\Form\Type\ProductSupplierRelationType + arguments: + - '@oro_currency.locale_settings' tags: - { name: form.type } @@ -92,7 +75,7 @@ services: - { name: form.type } marello_product.form.type.products_assign_sales_channels: - class: '%marello_product.form.type.products_assign_sales_channels.class%' + class: 'Marello\Bundle\ProductBundle\Form\Type\ProductsAssignSalesChannelsType' tags: - { name: form.type } ## forms @@ -123,16 +106,16 @@ services: ## form handlers marello_product.product_form.handler: - class: '%marello_product.product_form.handler.class%' - scope: request + class: 'Marello\Bundle\ProductBundle\Form\Handler\ProductHandler' + public: true arguments: - '@marello_product.product.form' - '@request_stack' - '@doctrine.orm.entity_manager' marello_product.product_variant_form.handler: - class: '%marello_product.product_variant_form.handler.class%' - scope: request + class: 'Marello\Bundle\ProductBundle\Form\Handler\ProductVariantHandler' + public: true arguments: - '@marello_product.product_variant.form' - '@request_stack' @@ -141,6 +124,7 @@ services: marello_product.sales_channels_assign.handler: class: 'Marello\Bundle\ProductBundle\Form\Handler\ProductsSalesChannelsAssignHandler' + public: true arguments: - '@marello_product.products_assign_sales_channels.form' - '@request_stack' diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/jsmodules.yml b/src/Marello/Bundle/ProductBundle/Resources/config/jsmodules.yml new file mode 100755 index 000000000..cfee855c2 --- /dev/null +++ b/src/Marello/Bundle/ProductBundle/Resources/config/jsmodules.yml @@ -0,0 +1,11 @@ +aliases: + oro/select2-autocomplete-sales-channel-aware-component$: marelloproduct/js/app/components/select2-autocomplete-sales-channel-aware-component +dynamic-imports: + marelloproduct: + - marelloproduct/js/app/components/select-create-inline-type-component + - marelloproduct/js/app/components/select-create-inline-type-async-component + - marelloproduct/js/app/components/select2-autocomplete-sales-channel-aware-component + - marelloproduct/js/app/views/product-channel-taxcode-view + - marelloproduct/js/app/views/product-channel-taxcodes-view + commons: + - oro/select2-autocomplete-sales-channel-aware-component \ No newline at end of file diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/oro/actions.yml b/src/Marello/Bundle/ProductBundle/Resources/config/oro/actions.yml index 77d0a2017..0dd0ca1bb 100755 --- a/src/Marello/Bundle/ProductBundle/Resources/config/oro/actions.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/config/oro/actions.yml @@ -18,8 +18,12 @@ operations: parameters_mapping: data: $.data - '@call_service_method': + attribute: $.flashBag + service: session + method: getFlashBag + - '@call_method': attribute: $.hasSuccessMessage - service: session.flash_bag + object: $.flashBag method: has method_parameters: ['success'] - '@flash_message': diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/oro/api.yml b/src/Marello/Bundle/ProductBundle/Resources/config/oro/api.yml index 36ac2c98e..6a966356c 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/config/oro/api.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/config/oro/api.yml @@ -19,8 +19,6 @@ api: property_path: taxCode productstatus: property_path: status - price: - exclude: true createdAt: exclude: true updatedAt: diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/ProductBundle/Resources/config/oro/datagrids.yml index e69fcd692..ab9a04267 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/config/oro/datagrids.yml @@ -9,45 +9,14 @@ datagrids: - p.id - p.sku - p.type - - IDENTITY(p.image) as image - - p.name - p.weight - - p.name - p.manufacturingCode - p.createdAt - p.updatedAt - - tc.code as taxCode - - (CASE WHEN p.image IS NOT NULL THEN true ELSE false END) as hasImage - - s.label as status - - count(sc.code) as channelsCount - - count(cat.id) as categoriesCount - - sum(defpr.value) as defaultPricesSum - - sum(sppr.value) as specialPricesSum - - sum(mspr.value) as msrpPricesSum - - sum(defcpr.value) as defaultChannelPricesSum - - sum(spcpr.value) as specialChannelPricesSum from: - { table: MarelloProductBundle:Product, alias: p } - join: - left: - - { join: p.status, alias: s } - - { join: p.taxCode, alias: tc } - - { join: p.channels, alias: sc } - - { join: p.categories, alias: cat } - - { join: p.prices, alias: pr } - - { join: pr.defaultPrice, alias: defpr } - - { join: pr.specialPrice, alias: sppr } - - { join: pr.msrpPrice, alias: mspr } - - { join: p.channelPrices, alias: cpr } - - { join: cpr.defaultPrice, alias: defcpr } - - { join: cpr.specialPrice, alias: spcpr } - groupBy: p.id, s.label + groupBy: p.id columns: - image: - label: marello.product.image.label - type: twig - frontend_type: html - template: MarelloProductBundle:Product/Datagrid/Property:image.html.twig type: label: marello.product.type.label frontend_type: string @@ -55,14 +24,7 @@ datagrids: label: marello.product.sku.label frontend_type: string name: - label: marello.product.name.label - frontend_type: string - status: - label: marello.product.status.label - frontend_type: string - taxCode: - label: marello.product.tax_code.label - frontend_type: string + label: marello.product.names.label manufacturingCode: label: marello.product.manufacturing_code.label frontend_type: string @@ -71,53 +33,6 @@ datagrids: label: marello.product.weight.label frontend_type: number renderable: false - defaultPrices: - label: marello.pricing.assembledpricelist.default_price.plural_label - type: twig - frontend_type: html - template: MarelloProductBundle:Product/Datagrid/Property:defaultPrices.html.twig - renderable: false - align: right - specialPrices: - label: marello.pricing.assembledpricelist.special_price.plural_label - type: twig - frontend_type: html - template: MarelloProductBundle:Product/Datagrid/Property:specialPrices.html.twig - renderable: false - align: right - msrpPrices: - label: marello.pricing.assembledpricelist.msrp_price.plural_label - type: twig - frontend_type: html - template: MarelloProductBundle:Product/Datagrid/Property:msrpPrices.html.twig - renderable: false - align: right - defaultChannelPrices: - label: marello.pricing.assembledchannelpricelist.default_price.plural_label - type: twig - frontend_type: html - template: MarelloProductBundle:Product/Datagrid/Property:defaultChannelPrices.html.twig - renderable: false - align: right - specialChannelPrices: - label: marello.pricing.assembledchannelpricelist.special_price.plural_label - type: twig - frontend_type: html - template: MarelloProductBundle:Product/Datagrid/Property:specialChannelPrices.html.twig - renderable: false - align: right - channels: - label: marello.product.channels.label - type: twig - frontend_type: html - template: MarelloProductBundle:Product/Datagrid/Property:channels.html.twig - renderable: false - categories: - label: marello.product.categories.label - type: twig - frontend_type: html - template: MarelloProductBundle:Product/Datagrid/Property:categories.html.twig - renderable: false createdAt: label: oro.ui.created_at frontend_type: datetime @@ -127,6 +42,9 @@ datagrids: renderable: false properties: id: ~ + name: + type: localized_value + data_name: names view_link: type: url route: marello_product_view @@ -139,26 +57,14 @@ datagrids: columns: type: { data_name: p.type } sku: { data_name: p.sku } - name: { data_name: p.name } - status: { data_name: s.label } + name: { data_name: name } weight: { data_name: p.weight } - taxCode: { data_name: p.taxCode } - channels: { data_name: channelsCount } - categories: { data_name: categoriesCount } - defaultPrices: { data_name: defaultPricesSum } - specialPrices: { data_name: specialPricesSum } - defaultChannelPrices: { data_name: defaultChannelPricesSum } - specialChannelPrices: { data_name: specialChannelPricesSum } - msrpPrices: { data_name: msrpPricesSum } createdAt: { data_name: p.createdAt } updatedAt: { data_name: p.updatedAt } default: - name: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' + name: 'DESC' filters: columns: - image: - type: boolean - data_name: hasImage type: type: string data_name: p.type @@ -167,65 +73,16 @@ datagrids: data_name: p.sku name: type: string - data_name: p.name - status: - type: entity - data_name: s - options: - field_options: - class: Marello\Bundle\ProductBundle\Entity\ProductStatus + data_name: name manufacturingCode: data_name: p.manufacturingCode type: string enabled: false - defaultPrices: - data_name: defpr.value - type: number - enabled: false - specialPrices: - data_name: sppr.value - type: number - enabled: false - msrpPrices: - data_name: mspr.value - type: number - enabled: false - defaultChannelPrices: - data_name: defcpr.value - type: number - enabled: false - specialChannelPrices: - data_name: spcpr.value - type: number - enabled: false - taxCode: - type: choice - data_name: tc.id - options: - field_options: - multiple: true - choices: '@marello_tax.provider.tax_codes_choices->getTaxCodes()' - channels: - type: choice - data_name: sc.code - options: - field_options: - multiple: true - choices: '@marello_sales.provider.basic_sales_channels_choices->getChannels()' - enabled: false - categories: - type: choice - data_name: cat.code - options: - field_options: - multiple: true - choices: '@marello_catalog.provider.categories_choices->getCategories()' - enabled: false weight: data_name: p.weight type: number options: - data_type: '%oro_filter.form.type.filter.number.class%::DATA_DECIMAL' + data_type: 'Oro\Bundle\FilterBundle\Form\Type\Filter\NumberFilterType::DATA_DECIMAL' enabled: false actions: view: @@ -271,6 +128,7 @@ datagrids: - channelId mass_actions: ~ options: + entityHint: marello.product.entity_plural_label export: false entity_pagination: false @@ -285,7 +143,6 @@ datagrids: acl_resource: marello_product_view query: select: - - p.name - p.sku - s.label as status - COALESCE(SUM(il.inventory), 0) AS inventoryQty @@ -309,8 +166,7 @@ datagrids: label: marello.product.sku.label frontend_type: string name: - label: marello.product.name.label - frontend_type: string + label: marello.product.names.label inventoryQty: label: marello.inventory.inventorylevel.inventory.label frontend_type: number @@ -318,13 +174,17 @@ datagrids: status: label: marello.product.status.label frontend_type: string + properties: + name: + type: localized_value + data_name: names sorters: columns: - name: { data_name: p.name } + name: { data_name: name } sku: { data_name: p.sku } inventoryQty: { data_name: inventoryQty } default: - name: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC" + name: "DESC" options: entityHint: product toolbarOptions: @@ -347,12 +207,11 @@ datagrids: ELSE false END) as hasVariant - - p.name - p.sku - s.label as status - COALESCE(SUM(il.inventory), 0) AS inventoryQty from: - - { table: Marello\Bundle\ProductBundle\Entity\Product, alias: p } + - { table: MarelloProductBundle:Product, alias: p } join: left: - { join: p.status, alias: s } @@ -380,8 +239,7 @@ datagrids: label: marello.product.sku.label frontend_type: string name: - label: marello.product.name.label - frontend_type: string + label: marello.product.names.label inventoryQty: label: marello.inventory.inventorylevel.inventory.label frontend_type: number @@ -396,22 +254,25 @@ datagrids: type: boolean data_name: hasVariant name: - label: marello.product.name.label + label: marello.product.names.label type: string - data_name: p.name + data_name: name sku: label: marello.product.sku.label type: string data_name: p.sku sorters: columns: - name: { data_name: p.name } + name: { data_name: name } sku: { data_name: p.sku } hasVariant: { data_name: hasVariant } default: - hasVariant: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC" + hasVariant: "DESC" properties: id: ~ + name: + type: localized_value + data_name: names options: entityHint: product rowSelection: @@ -526,7 +387,7 @@ datagrids: sorters: columns: hasChannel: { data_name: hasChannel } - default: { hasChannel: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' } + default: { hasChannel: 'DESC' } filters: columns: hasChannel: @@ -604,7 +465,7 @@ datagrids: select: - p.id - p.sku - - p.name + - p.denormalizedDefaultName as productName - p.manufacturingCode from: - { table: MarelloProductBundle:Product, alias: p } @@ -612,9 +473,10 @@ datagrids: sku: label: marello.product.sku.label frontend_type: string - name: + productName: label: marello.product.name.label frontend_type: string + data_name: productName manufacturingCode: label: marello.product.manufacturing_code.label frontend_type: string @@ -624,10 +486,10 @@ datagrids: label: marello.product.sku.label type: string data_name: p.sku - name: + productName: label: marello.product.name.label type: string - data_name: p.name + data_name: productName manufacturingCode: label: marello.product.manufacturing_code.label type: string @@ -635,10 +497,10 @@ datagrids: sorters: columns: sku: { data_name: p.sku } - name: { data_name: p.name } + productName: { data_name: productName } manufacturingCode: { data_name: p.manufacturingCode } default: - name: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC' + name: 'ASC' properties: id: ~ options: @@ -646,7 +508,7 @@ datagrids: marello-product-categories-base-grid: acl_resource: marello_category_update - extended_entity_name: '%marello_catalog.category.entity.class%' + extended_entity_name: Marello\Bundle\CatalogBundle\Entity\Category source: type: orm query: @@ -657,7 +519,7 @@ datagrids: - category.createdAt - category.updatedAt from: - - { table: '%marello_catalog.category.entity.class%', alias: category } + - { table: MarelloCatalogBundle:Category, alias: category } columns: code: label: marello.catalog.category.code.label @@ -710,7 +572,7 @@ datagrids: - categories sorters: default: - name: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' + name: 'DESC' marello-product-categories-extend-grid: extends: marello-product-categories-base-grid @@ -732,7 +594,7 @@ datagrids: join: left: - - join: '%marello_product.entity.class%' + join: Marello\Bundle\ProductBundle\Entity\Product alias: categoryProduct conditionType: WITH condition: 'categoryProduct = :product_id' @@ -751,7 +613,7 @@ datagrids: inCategory: data_name: inCategory default: - inCategory: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' + inCategory: 'DESC' filters: columns: inCategory: diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/oro/placeholders.yml b/src/Marello/Bundle/ProductBundle/Resources/config/oro/placeholders.yml index 3c250683e..9a31d596b 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/config/oro/placeholders.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/config/oro/placeholders.yml @@ -2,9 +2,9 @@ placeholders: items: marello_add_variant_link: template: MarelloProductBundle:Variant:addVariantLink.html.twig - applicable: '@oro_ui.placeholder.filter->isInstanceOf($entity$, %marello_product.entity.class%)' + applicable: '@oro_ui.placeholder.filter->isInstanceOf($entity$, Marello\Bundle\ProductBundle\Entity\Product)' acl: marello_product_create_variant marello_add_variant_button: template: MarelloProductBundle:Variant:addVariantButton.html.twig - applicable: '@oro_ui.placeholder.filter->isInstanceOf($entity$, %marello_product.entity.class%)' + applicable: '@oro_ui.placeholder.filter->isInstanceOf($entity$, Marello\Bundle\ProductBundle\Entity\Product)' acl: marello_product_create_variant \ No newline at end of file diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/oro/search.yml b/src/Marello/Bundle/ProductBundle/Resources/config/oro/search.yml index 5c4e72ef8..ea79dc6c8 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/config/oro/search.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/config/oro/search.yml @@ -2,7 +2,7 @@ search: Marello\Bundle\ProductBundle\Entity\Product: alias: marello_product label: marello.product.entity_plural_label - title_fields: [name] + title_fields: [denormalizedDefaultName] route: name: marello_product_view parameters: @@ -10,9 +10,17 @@ search: search_template: MarelloProductBundle:Product:searchResult.html.twig fields: - - name: name + name: denormalizedDefaultName target_type: text - target_fields: [name] + target_fields: [denormalizedDefaultName] + - + name: names + relation_type: many-to-many + relation_fields: + - + name: string + target_type: text + target_fields: [names] - name: sku target_type: text diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/requirejs.yml b/src/Marello/Bundle/ProductBundle/Resources/config/requirejs.yml deleted file mode 100755 index a05d39024..000000000 --- a/src/Marello/Bundle/ProductBundle/Resources/config/requirejs.yml +++ /dev/null @@ -1,5 +0,0 @@ -config: - paths: - 'marelloproduct/js/app/components/select-create-inline-type-component': 'bundles/marelloproduct/js/app/components/select-create-inline-type-component.js' - 'marelloproduct/js/app/components/select-create-inline-type-async-component': 'bundles/marelloproduct/js/app/components/select-create-inline-type-async-component.js' - 'oro/select2-autocomplete-sales-channel-aware-component': 'bundles/marelloproduct/js/app/components/select2-autocomplete-sales-channel-aware-component.js' diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/services.yml b/src/Marello/Bundle/ProductBundle/Resources/config/services.yml index 46d0385c0..8a3810381 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/config/services.yml @@ -1,13 +1,10 @@ -parameters: - marello_product.entity.class: Marello\Bundle\ProductBundle\Entity\Product - services: # Autocomplete search handler marello_product.form.autocomplete.product.search_handler: parent: oro_form.autocomplete.search_handler arguments: - - '%marello_product.entity.class%' - - ["name", "sku"] + - 'Marello\Bundle\ProductBundle\Entity\Product' + - ["denormalizedDefaultName", "sku"] tags: - { name: oro_form.autocomplete.search_handler, alias: products, acl_resource: marello_product_view } @@ -15,8 +12,8 @@ services: class: 'Marello\Bundle\ProductBundle\Autocomplete\SalesChannelProductHandler' parent: oro_form.autocomplete.search_handler arguments: - - '%marello_product.entity.class%' - - ["name", "sku"] + - 'Marello\Bundle\ProductBundle\Entity\Product' + - ["denormalizedDefaultName", "sku"] tags: - { name: oro_form.autocomplete.search_handler, alias: sales_channel_products, acl_resource: marello_product_view } @@ -28,6 +25,13 @@ services: tags: - { name: oro_ui.view_action_provider, group: product } + marello_product.twig.dynamic_fields_extension: + class: Marello\Bundle\ProductBundle\Twig\DynamicFieldsExtension + arguments: + - '@oro_entity_config.manager.attribute_manager' + tags: + - { name: twig.extension } + marello_product.twig.product_extension: class: Marello\Bundle\ProductBundle\Twig\ProductExtension arguments: @@ -62,16 +66,10 @@ services: marello_product.listener.datagrid.product_grid_listener: class: Marello\Bundle\ProductBundle\EventListener\Datagrid\ProductGridListener tags: + - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before.marello-products-grid, method: onBuildBefore } - { name: kernel.event_listener, event: oro_datagrid.orm_datasource.result.before.marello-products-grid, method: onResultBefore } - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.after.marello-products-grid, method: buildAfter } - marello_product.listener.doctrine.product_attribute_family: - class: Marello\Bundle\ProductBundle\EventListener\Doctrine\ProductAttributeFamilyEventListener - arguments: - - '@marello_product.provider.product_types' - tags: - - { name: doctrine.event_listener, event: prePersist } - marello_product.listener.doctrine.product_dropship: class: Marello\Bundle\ProductBundle\EventListener\Doctrine\ProductDropshipEventListener arguments: @@ -79,6 +77,7 @@ services: tags: - { name: doctrine.event_listener, event: prePersist } - { name: doctrine.event_listener, event: preUpdate } + - { name: doctrine.event_listener, event: preRemove } - { name: doctrine.event_listener, event: postFlush } marello_product.product_search_listener: @@ -106,6 +105,7 @@ services: marello_product.service.duplicator: class: 'Marello\Bundle\ProductBundle\Duplicator\ProductDuplicator' + public: true arguments: - "@oro_entity.doctrine_helper" - "@event_dispatcher" @@ -119,7 +119,7 @@ services: public: false arguments: - "@oro_entity.doctrine_helper" - - '%marello_product.entity.class%' + - 'Marello\Bundle\ProductBundle\Entity\Product' marello_product.virtual_fields.decorator_factory: class: 'Marello\Bundle\ProductBundle\VirtualFields\VirtualFieldsProductDecoratorFactory' @@ -143,6 +143,7 @@ services: class: 'Marello\Bundle\ProductBundle\Datagrid\Extension\MassAction\SalesChannelsAssignMassAction' arguments: - 'marello_product_assign_sales_channels' + public: true shared: false tags: - { name: oro_datagrid.extension.mass_action.type, type: sales_channels_assign } @@ -159,26 +160,23 @@ services: tags: - { name: 'oro_message_queue.client.message_processor' } + marello_product.event_listener.attribute_family_form_view: parent: oro_entity_config.event_listener.attribute_family_form_view.abstract tags: - - { name: kernel.event_listener, event: oro_ui.scroll_data.before.marello-product-create-step-two, method: onEdit, priority: -255 } - - { name: kernel.event_listener, event: oro_ui.scroll_data.before.marello-product-update, method: onEdit } + - { name: kernel.event_listener, event: oro_ui.scroll_data.before.marello-product-create-step-one, method: onEdit } marello_product.event_listener.attributes_form_view: - parent: oro_entity_config.event_listener.attributes_form_view.abstract + class: Marello\Bundle\ProductBundle\EventListener\AttributeFormViewListener + arguments: + - '@oro_entity_config.manager.attribute_manager' tags: - { name: kernel.event_listener, event: oro_ui.scroll_data.before.marello-product-create-step-two, method: onEdit, priority: -255 } - { name: kernel.event_listener, event: oro_ui.scroll_data.before.marello-product-update, method: onEdit, priority: -255 } - { name: kernel.event_listener, event: oro_ui.scroll_data.before.marello-product-view, method: onViewList, priority: -255 } - marello_product.product.form.event_listener.attribute_family: - class: Marello\Bundle\ProductBundle\Form\EventListener\AttributeFamilySubscriber - arguments: - - '@doctrine.orm.entity_manager' - - '@marello_product.provider.product_types' - marello_product.provider.product_types: + public: true class: Marello\Bundle\ProductBundle\Provider\ProductTypesProvider marello_product.product_type.simple: @@ -186,6 +184,12 @@ services: arguments: - name: 'simple' label: 'Simple' - attribute_family_code: 'marello_default' tags: - - {name: marello_product.product_type} \ No newline at end of file + - {name: marello_product.product_type} + + marello_product.form.product_supplier_relations_dropship_validator: + class: 'Marello\Bundle\ProductBundle\Validator\ProductSupplierRelationsDropshipValidator' + arguments: + - '@doctrine' + tags: + - { name: validator.constraint_validator, alias: marello_product.product_supplier_relations_dropship_validator } diff --git a/src/Marello/Bundle/ProductBundle/Resources/config/validation.yml b/src/Marello/Bundle/ProductBundle/Resources/config/validation.yml index ff3665e42..31ad2a288 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/config/validation.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/config/validation.yml @@ -1,13 +1,12 @@ Marello\Bundle\ProductBundle\Entity\Product: constraints: + - Marello\Bundle\ProductBundle\Validator\Constraints\ProductSupplierRelationsDropship: ~ - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: fields: sku message: 'marello.product.messages.error.sku' properties: - name: - - NotBlank: ~ - - Length: - max: 255 + names: + - Oro\Bundle\PlatformBundle\Validator\Constraints\ValidLoadedItems: ~ sku: - NotBlank: ~ prices: diff --git a/src/Marello/Bundle/ProductBundle/Resources/doc/api/product.md b/src/Marello/Bundle/ProductBundle/Resources/doc/api/product.md index 2219c21ee..0bbdd4351 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/doc/api/product.md +++ b/src/Marello/Bundle/ProductBundle/Resources/doc/api/product.md @@ -32,15 +32,20 @@ Example: "id": "100909", "attributes": { "productId": 848, - "name": "9'4\" Super Magnum", + "denormalizedDefaultName": "9'4\" Super Magnum", "manufacturingCode": "TZH-529-udz-090", "productType": null, - "cost": null, "weight": 12, "warranty": null, "data": [] }, "relationships": { + "names": { + "data": { + "type": "localizedfallbackvalues", + "id": 1 + } + }, "status": { "data": { "type": "productstatuses", diff --git a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/channel-aware-select-create-component-mixin.js b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/channel-aware-select-create-component-mixin.js index 9e068492b..49159a3a7 100755 --- a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/channel-aware-select-create-component-mixin.js +++ b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/channel-aware-select-create-component-mixin.js @@ -1,11 +1,11 @@ define(function(require) { 'use strict'; - var selectCreateComponentMixin, + const $ = require('jquery'), mediator = require('oroui/js/mediator'); - selectCreateComponentMixin = { + const selectCreateComponentMixin = { options: { salesChannelDataContainer: '.marello-sales-channel-select-container', @@ -21,6 +21,8 @@ define(function(require) { var channelData = $(this.options.salesChannelDataContainer).data(this.options.attribute); if (channelData !== undefined) { this.saveData(channelData.id); + } else { + this.saveData(0); } mediator.on('marello_sales:channel:changed', this.onSalesChannelChange, this); }, diff --git a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select-create-inline-type-async-component.js b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select-create-inline-type-async-component.js index 4939587d8..ed514c5c1 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select-create-inline-type-async-component.js +++ b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select-create-inline-type-async-component.js @@ -1,11 +1,10 @@ define(function(require) { 'use strict'; - var SelectCreateInlineTypeAsyncComponent; - var _ = require('underscore'); - var ParentComponent = require('oroform/js/app/components/select-create-inline-type-async-component'); - var mixin = require('./channel-aware-select-create-component-mixin'); - SelectCreateInlineTypeAsyncComponent = ParentComponent.extend(_.extend({}, mixin, { + const _ = require('underscore'); + const ParentComponent = require('oroform/js/app/components/select-create-inline-type-async-component'); + const mixin = require('./channel-aware-select-create-component-mixin'); + const SelectCreateInlineTypeAsyncComponent = ParentComponent.extend(_.extend({}, mixin, { _super: function() { return SelectCreateInlineTypeAsyncComponent.__super__; } diff --git a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select-create-inline-type-component.js b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select-create-inline-type-component.js index 6df1958e3..c1aed6ffe 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select-create-inline-type-component.js +++ b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select-create-inline-type-component.js @@ -1,11 +1,10 @@ define(function(require) { 'use strict'; - var SelectCreateInlineTypeComponent; - var _ = require('underscore'); - var ParentComponent = require('oroform/js/app/components/select-create-inline-type-component'); - var mixin = require('./channel-aware-select-create-component-mixin'); - SelectCreateInlineTypeComponent = ParentComponent.extend(_.extend({}, mixin, { + const _ = require('underscore'); + const ParentComponent = require('oroform/js/app/components/select-create-inline-type-component'); + const mixin = require('./channel-aware-select-create-component-mixin'); + const SelectCreateInlineTypeComponent = ParentComponent.extend(_.extend({}, mixin, { _super: function() { return SelectCreateInlineTypeComponent.__super__; } diff --git a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select2-autocomplete-sales-channel-aware-component.js b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select2-autocomplete-sales-channel-aware-component.js index 0db1b6dbd..ec6ebe40a 100755 --- a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select2-autocomplete-sales-channel-aware-component.js +++ b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/components/select2-autocomplete-sales-channel-aware-component.js @@ -1,9 +1,9 @@ define(function (require) { 'use strict'; - var Select2AutocompleteChannelAwareComponent, + const mediator = require('oroui/js/mediator'), Select2AutocompleteComponent = require('oro/select2-autocomplete-component'); - Select2AutocompleteChannelAwareComponent = Select2AutocompleteComponent.extend({ + const Select2AutocompleteChannelAwareComponent = Select2AutocompleteComponent.extend({ /** * @property {Object} diff --git a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/views/product-channel-taxcode-view.js b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/views/product-channel-taxcode-view.js index c1c7f6385..3f52b5ead 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/views/product-channel-taxcode-view.js +++ b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/views/product-channel-taxcode-view.js @@ -1,7 +1,7 @@ define(function(require) { 'use strict'; - var ProductChannelTaxView, + const $ = require('jquery'), _ = require('underscore'), mediator = require('oroui/js/mediator'), @@ -12,7 +12,7 @@ define(function(require) { * @extends marellolayout.app.views.AbstractItemView * @class marelloproduct.app.views.ProductChannelTaxView */ - ProductChannelTaxView = AbstractItemView.extend({ + const ProductChannelTaxView = AbstractItemView.extend({ options: { priority: 0, canDropship: false diff --git a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/views/product-channel-taxcodes-view.js b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/views/product-channel-taxcodes-view.js index 65c70afcd..e1f48a240 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/public/js/app/views/product-channel-taxcodes-view.js +++ b/src/Marello/Bundle/ProductBundle/Resources/public/js/app/views/product-channel-taxcodes-view.js @@ -1,7 +1,7 @@ define(function(require) { 'use strict'; - var ProductChannelTaxCodesView, + const $ = require('jquery'), _ = require('underscore'), routing = require('routing'), @@ -13,7 +13,7 @@ define(function(require) { * @extends marellolayout.app.views.AbstractItemsView * @class marelloproduct.app.views.ProductChannelTaxCodesView */ - ProductChannelTaxCodesView = AbstractItemsView.extend({ + const ProductChannelTaxCodesView = AbstractItemsView.extend({ /** * @property {Object} */ diff --git a/src/Marello/Bundle/ProductBundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/ProductBundle/Resources/translations/messages.en.yml index 97ef709e7..5313e61e7 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/translations/messages.en.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/translations/messages.en.yml @@ -4,12 +4,11 @@ marello: entity_plural_label: Products entity_label: Product name.label: Name + names.label: Names sku.label: SKU weight.label: Weight (in Kg) warranty.label: Product Warranty - price.label: Default price status.label: Status - cost.label: Cost data.label: Data type.label: Product Type preferred_supplier.label: Preferred Supplier @@ -72,7 +71,7 @@ marello: assignment: There was failure during Sales Channels assignment sections: - general: General Information + general: General additional: Additional form.choose_product: Select Product diff --git a/src/Marello/Bundle/ProductBundle/Resources/translations/validators.en.yml b/src/Marello/Bundle/ProductBundle/Resources/translations/validators.en.yml index 5d07c1d61..eb5b44137 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/translations/validators.en.yml +++ b/src/Marello/Bundle/ProductBundle/Resources/translations/validators.en.yml @@ -2,8 +2,10 @@ marello: product: messages: error: + names.blank: Product Name should not be blank. sku: SKU already exists, please provide a unique SKU channels: Please select a Sales Channel. There should be at least one Sales Channel per product. + suppliers.dropship: It is forbidden to remove the ProductSupplierRelation with dropship enabled or disable dropship if it has inventory in external warehouse. Please allocate or remove inventory before deletion productchanneltaxrelation: messages: diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Form/fields.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Form/fields.html.twig index cf8d3dd78..f78b4276c 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Form/fields.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Form/fields.html.twig @@ -13,7 +13,7 @@ {% endblock %} -{% macro marello_product_channel_tax_relation_collection_form_item_prototype(widget) %} +{% macro marello_product_channel_tax_relation_collection_form_item_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -32,7 +32,7 @@ {% set page_component_options = { 'disabled': not allow_delete } %} - {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_product_channel_tax_relation_collection_form_item_prototype(child) }} + {{ fields.marello_product_channel_tax_relation_collection_form_item_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} @@ -97,7 +100,7 @@ {% block oro_entity_create_or_select_inline_js_sales_channel_aware %} {% import 'OroUIBundle::macros.html.twig' as UI %} - {% if form.vars.configs.async_dialogs|default(false) is sameas(true) %} + {% if form.vars.configs.async_dialogs|default(false) is same as(true) %} {% set asyncNameSegment = 'async-' %} {% endif %} {% set urlParts = { @@ -111,7 +114,7 @@ } } %} - {% if form.vars.create_enabled|default(false) is sameas(true) %} + {% if form.vars.create_enabled|default(false) is same as(true) %} {% set urlParts = urlParts|merge({ create: { route: form.vars.create_form_route, @@ -147,7 +150,7 @@
- {{ form_widget(form.cost) }} + {{ form_widget(form.cost) }}{{ form.vars.currency }}
{{ form_errors(form.cost) }} @@ -165,7 +168,7 @@ {% endblock %} -{% macro marello_product_supplier_relation_collection_form_item_prototype(widget) %} +{% macro marello_product_supplier_relation_collection_form_item_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -184,7 +187,7 @@ {% set page_component_options = { 'disabled': not allow_delete } %} - {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_product_supplier_relation_collection_form_item_prototype(child) }} + {{ fields.marello_product_supplier_relation_collection_form_item_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} @@ -284,9 +290,12 @@ {% endmacro %} {% block marello_product_select_collection_form_widget %} + {% import _self as fields %} + {% spaceless %} + {% set widgetContainerAttributes = block('widget_container_attributes') %} {% if prototype is defined %} - {% set prototype_html = _self.marello_product_select_collection_form_item_prototype(form) %} + {% set prototype_html = fields.marello_product_select_collection_form_item_prototype(form) %} {% endif %} {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'marello-item-collection grid-container' }) %} {% set id = id ~ '_collection' %} @@ -305,7 +314,7 @@ {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_product_select_collection_form_item_prototype(child) }} + {{ fields.marello_product_select_collection_form_item_prototype(child) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Autocomplete/result.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/Autocomplete/result.html.twig index c2625e5dc..262875e96 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Autocomplete/result.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Product/Autocomplete/result.html.twig @@ -1 +1 @@ -<%= highlight(_.escape(sku)) %> - <%= highlight(_.escape(name)) %> +<%= highlight(_.escape(sku)) %> - <%= highlight(_.escape(denormalizedDefaultName)) %> diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Autocomplete/selection.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/Autocomplete/selection.html.twig index c2625e5dc..262875e96 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Autocomplete/selection.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Product/Autocomplete/selection.html.twig @@ -1 +1 @@ -<%= highlight(_.escape(sku)) %> - <%= highlight(_.escape(name)) %> +<%= highlight(_.escape(sku)) %> - <%= highlight(_.escape(denormalizedDefaultName)) %> diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/defaultChannelPrices.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/defaultChannelPrices.html.twig deleted file mode 100644 index 3d72c7762..000000000 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/defaultChannelPrices.html.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% for index, assembledChannelPriceList in record.getValue('channelPrices') %} - {% if assembledChannelPriceList.defaultPrice %} - {{ assembledChannelPriceList.channel.name }}: {{ assembledChannelPriceList.defaultPrice.value|oro_format_currency({'currency': assembledChannelPriceList.currency}) }}
- {% endif %} -{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/defaultPrices.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/defaultPrices.html.twig deleted file mode 100644 index d6c0d8f6d..000000000 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/defaultPrices.html.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% for index, assembledPriceList in record.getValue('prices') %} - {% if assembledPriceList.defaultPrice %} - {{ assembledPriceList.defaultPrice.value|oro_format_currency({'currency': assembledPriceList.currency}) }}
- {% endif %} -{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/msrpPrices.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/msrpPrices.html.twig deleted file mode 100644 index 91a84cf7f..000000000 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/msrpPrices.html.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% for index, assembledPriceList in record.getValue('prices') %} - {% if assembledPriceList.msrpPrice %} - {{ assembledPriceList.msrpPrice.value|oro_format_currency({'currency': assembledPriceList.currency}) }}
- {% endif %} -{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/specialChannelPrices.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/specialChannelPrices.html.twig deleted file mode 100644 index dba1a9095..000000000 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/specialChannelPrices.html.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% for index, assembledChannelPriceList in record.getValue('channelPrices') %} - {% if assembledChannelPriceList.specialPrice %} - {{ assembledChannelPriceList.channel.name }}: {{ assembledChannelPriceList.specialPrice.value|oro_format_currency({'currency': assembledChannelPriceList.currency}) }}
- {% endif %} -{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/specialPrices.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/specialPrices.html.twig deleted file mode 100644 index be2724117..000000000 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/specialPrices.html.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% for index, assembledPriceList in record.getValue('prices') %} - {% if assembledPriceList.specialPrice %} - {{ assembledPriceList.specialPrice.value|oro_format_currency({'currency': assembledPriceList.currency}) }}
- {% endif %} -{% endfor %} \ No newline at end of file diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/assignSalesChannels.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/assignSalesChannels.html.twig index f10bfb82d..d2305ee8b 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/assignSalesChannels.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Product/assignSalesChannels.html.twig @@ -1,6 +1,7 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% set gridName = 'marello-products-assign-saleschannels-grid' %} {% set formAttr = formAttr|default({})|merge({ 'data-collect': 'true' diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/createStepOne.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/createStepOne.html.twig index 55faf1e72..6b59d21b8 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/createStepOne.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Product/createStepOne.html.twig @@ -1,5 +1,6 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% oro_title_set({params : {} }) %} diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/createStepTwo.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/createStepTwo.html.twig index c51af809a..22dc56023 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/createStepTwo.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Product/createStepTwo.html.twig @@ -1,10 +1,8 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} -{% form_theme form - 'MarelloPricingBundle:Form:fields.html.twig' - 'MarelloSupplierBundle:Form:fields.html.twig' - 'MarelloProductBundle:Form:fields.html.twig' -%} + {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} +{% import 'OroUIBundle::macros.html.twig' as UI %} + {% set gridName = 'marello-product-saleschannel-extended-grid' %} {% set entity = form.vars.value %} @@ -107,36 +105,47 @@ {% endset %} - {% set suppliers %} -
- {{ form_widget(form.suppliers) }} - {{ form_errors(form.suppliers) }} -
- {% endset %} + {% if marello_entity_has_attribute(entity, 'suppliers') %} + {% set suppliers %} +
+ {{ form_widget(form.suppliers) }} + {{ form_errors(form.suppliers) }} +
+ {% endset %} + {% endif %} {% set channelIds = marello_sales_get_saleschannel_ids(entity)|length > 0 ? marello_sales_get_saleschannel_ids(entity) : 0 %} + {% set generalData = [ + form_row(form.names), + form_row(form.sku), + form_row(form.status), + ] %} + {% if marello_entity_has_attribute(entity, 'weight') %} + {% set generalData = generalData|merge([form_row(form.weight)]) %} + {% endif %} + {% if marello_entity_has_attribute(entity, 'warranty') %} + {% set generalData = generalData|merge([form_row(form.warranty)]) %} + {% endif %} + {% if marello_entity_has_attribute(entity, 'manufacturingCode') %} + {% set generalData = generalData|merge([form_row(form.manufacturingCode)]) %} + {% endif %} + {% set generalData = generalData|merge([form_row(form.type)]) %} + {% if marello_entity_has_attribute(entity, 'image') %} + {% set generalData = generalData|merge([form_row(form.image)]) %} + {% endif %} + {% set dataBlocks = [ { 'title': 'marello.product.sections.general'|trans, 'subblocks': [{ 'title': 'General', - 'data': [ - form_row(form.name), - form_row(form.sku), - form_row(form.status), - form_row(form.weight), - form_row(form.warranty), - form_row(form.manufacturingCode), - form_row(form.type), - form_row(form.attributeFamily), - form_row(form.image), - ] + 'data': generalData }] }, { @@ -172,30 +181,38 @@ dataGrid.renderGrid(gridName, { product: form.vars.value.id ? : null, channels: channelIds }) ] }] - }, - { - 'title': 'marello.supplier.entity_plural_label'|trans, - 'class': 'active', - 'subblocks': [{ - 'title': '', - 'data': [ - suppliers - ] - }] - }, - { - 'title' : 'marello.catalog.category.entity_plural_label'|trans, - 'subblocks': [{ - 'title' : null, - 'useSpan': false, - 'data' : [ - form_widget(form.appendCategories, {'id': 'productAppendCategories'}), - form_widget(form.removeCategories, {'id': 'productRemoveCategories'}), - dataGrid.renderGrid('marello-product-categories-extend-grid', { product_id: entity.id ? entity.id : 0 }, { cssClass: 'inner-grid' }) - ] - }] } ] %} + {% if marello_entity_has_attribute(entity, 'suppliers') %} + {% set dataBlocks = dataBlocks|merge([ + { + 'title': 'marello.supplier.entity_plural_label'|trans, + 'class': 'active', + 'subblocks': [{ + 'title': '', + 'data': { + 'suppliers': suppliers + } + }] + } + ]) %} + {% endif %} + {% if marello_entity_has_attribute(entity, 'categories') %} + {% set dataBlocks = dataBlocks|merge([ + { + 'title' : 'marello.catalog.category.entity_plural_label'|trans, + 'subblocks': [{ + 'title' : null, + 'useSpan': false, + 'data' : [ + form_widget(form.appendCategories, {'id': 'productAppendCategories'}), + form_widget(form.removeCategories, {'id': 'productRemoveCategories'}), + dataGrid.renderGrid('marello-product-categories-extend-grid', { product_id: entity.id ? entity.id : 0 }, { cssClass: 'inner-grid' }) + ] + }] + } + ]) %} + {% endif %} {% set additionalData = [] %} {% for child in form.children if child.vars.extra_field is defined and child.vars.extra_field %} diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/index.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/index.html.twig index 34191cfde..2e4c14f9d 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/index.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Product/index.html.twig @@ -5,7 +5,7 @@ {% set gridName = 'marello-products-grid' %} {% block navButtons %} - {% if resource_granted('marello_product_create') %} + {% if is_granted('marello_product_create') %} {{ UI.addButton({ 'path': path('marello_product_create'), 'entity_label': 'marello.product.entity_label'|trans diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/update.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/update.html.twig index 79c052c91..a8a5c94e5 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/update.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Product/update.html.twig @@ -1,10 +1,7 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} -{% form_theme form - 'MarelloPricingBundle:Form:fields.html.twig' - 'MarelloSupplierBundle:Form:fields.html.twig' - 'MarelloProductBundle:Form:fields.html.twig' -%} + {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% set gridName = 'marello-product-saleschannel-extended-grid' %} {% set entity = form.vars.value %} @@ -107,35 +104,47 @@ {% endset %} - {% set suppliers %} -
- {{ form_widget(form.suppliers) }} - {{ form_errors(form.suppliers) }} -
- {% endset %} + {% if marello_entity_has_attribute(entity, 'suppliers') %} + {% set suppliers %} +
+ {{ form_widget(form.suppliers) }} + {{ form_errors(form.suppliers) }} +
+ {% endset %} + {% endif %} {% set channelIds = marello_sales_get_saleschannel_ids(entity)|length > 0 ? marello_sales_get_saleschannel_ids(entity) : 0 %} + {% set generalData = [ + form_row(form.names), + form_row(form.sku), + form_row(form.status), + ] %} + {% if marello_entity_has_attribute(entity, 'weight') %} + {% set generalData = generalData|merge([form_row(form.weight)]) %} + {% endif %} + {% if marello_entity_has_attribute(entity, 'warranty') %} + {% set generalData = generalData|merge([form_row(form.warranty)]) %} + {% endif %} + {% if marello_entity_has_attribute(entity, 'manufacturingCode') %} + {% set generalData = generalData|merge([form_row(form.manufacturingCode)]) %} + {% endif %} + {% set generalData = generalData|merge([form_row(form.type)]) %} + {% if marello_entity_has_attribute(entity, 'image') %} + {% set generalData = generalData|merge([form_row(form.image)]) %} + {% endif %} + {% set dataBlocks = [ { 'title': 'marello.product.sections.general'|trans, 'subblocks': [{ 'title': 'General', - 'data': [ - form_row(form.name), - form_row(form.sku), - form_row(form.status), - form_row(form.weight), - form_row(form.warranty), - form_row(form.manufacturingCode), - form_row(form.type), - form_row(form.image), - ] + 'data': generalData }] }, { @@ -171,30 +180,38 @@ dataGrid.renderGrid(gridName, { product: form.vars.value.id ? : null, channels: channelIds }) ] }] - }, - { - 'title': 'marello.supplier.entity_plural_label'|trans, - 'class': 'active', - 'subblocks': [{ - 'title': '', - 'data': [ - suppliers - ] - }] - }, - { - 'title' : 'marello.catalog.category.entity_plural_label'|trans, - 'subblocks': [{ - 'title' : null, - 'useSpan': false, - 'data' : [ - form_widget(form.appendCategories, {'id': 'productAppendCategories'}), - form_widget(form.removeCategories, {'id': 'productRemoveCategories'}), - dataGrid.renderGrid('marello-product-categories-extend-grid', { product_id: entity.id ? entity.id : 0 }, { cssClass: 'inner-grid' }) - ] - }] } ] %} + {% if marello_entity_has_attribute(entity, 'suppliers') %} + {% set dataBlocks = dataBlocks|merge([ + { + 'title': 'marello.supplier.entity_plural_label'|trans, + 'class': 'active', + 'subblocks': [{ + 'title': '', + 'data': { + 'suppliers': suppliers + } + }] + } + ]) %} + {% endif %} + {% if marello_entity_has_attribute(entity, 'categories') %} + {% set dataBlocks = dataBlocks|merge([ + { + 'title' : 'marello.catalog.category.entity_plural_label'|trans, + 'subblocks': [{ + 'title' : null, + 'useSpan': false, + 'data' : [ + form_widget(form.appendCategories, {'id': 'productAppendCategories'}), + form_widget(form.removeCategories, {'id': 'productRemoveCategories'}), + dataGrid.renderGrid('marello-product-categories-extend-grid', { product_id: entity.id ? entity.id : 0 }, { cssClass: 'inner-grid' }) + ] + }] + } + ]) %} + {% endif %} {% set additionalData = [] %} {% for child in form.children if child.vars.extra_field is defined and child.vars.extra_field %} diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/view.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/view.html.twig index 7c2ba0fd7..2de6d4cdf 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/view.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Product/view.html.twig @@ -1,8 +1,9 @@ {% extends 'OroUIBundle:actions:view.html.twig' %} {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% import 'OroEntityConfigBundle::macros.html.twig' as entityConfig %} +{% import 'OroUIBundle::macros.html.twig' as UI %} -{% oro_title_set({params : {"%name%": entity.name } }) %} +{% oro_title_set({params : {"%name%": entity.defaultName.string|default('N/A'|trans) } }) %} {% set gridName = 'marello-products-grid' %} {% block pageHeader %} @@ -10,7 +11,7 @@ 'entity': entity, 'indexPath': path('marello_product_index'), 'indexLabel': 'marello.product.entity_plural_label'|trans, - 'entityTitle': entity.name|default('N/A') + 'entityTitle': entity.defaultName.string|default('N/A'|trans) } %} {{ parent() }} @@ -18,7 +19,7 @@ {% block navButtons %} {{ parent() }} - {% if resource_granted('EDIT', entity) %} + {% if is_granted('EDIT', entity) %} {{ UI.editButton({ 'path': path('marello_product_update', {'id': entity.id}), 'entity_label': 'marello.product.entity_label'|trans @@ -36,24 +37,27 @@ 'title': '' }) }} {% endset %} + {% set generalSubblocks = generalSubblocks|merge([{'data' : [productInformationWidget]}]) %} - {% set productImage = 'bundles/marelloproduct/img/no_image_available.jpg' | imagine_filter('product_view') %} - {% if entity.image is not null %} - {% set productImage = filtered_image_url(entity.image, 'product_view') %} - {% endif %} + {% if marello_entity_has_attribute(entity, 'image') %} + {% set productImage = 'bundles/marelloproduct/img/no_image_available.jpg' | imagine_filter('product_view') %} + {% if entity.image is not null %} + {% set productImage = filtered_image_url(entity.image, 'product_view') %} + {% endif %} - {% set productImageWidget %} -
-
-
-
- {{ entity.name }} + {% set productImageWidget %} +
+
+
+
+ {{ entity.name }} +
-
- {% endset %} - {% set generalSubblocks = generalSubblocks|merge([{'data' : [productInformationWidget] }, {'data' : [productImageWidget] }]) %} + {% endset %} + {% set generalSubblocks = generalSubblocks|merge([{'data' : [productImageWidget]}]) %} + {% endif %} {% set dataBlocks = [{ 'title': 'General'|trans, @@ -99,25 +103,29 @@
{% endset %} - {% set productSupplierWidget %} -
-
-
- {{ dataGrid.renderGrid('marello-product-suppliers-grid', {'suppliers': marello_supplier_get_supplier_ids(entity)}) }} + {% if marello_entity_has_attribute(entity, 'suppliers') %} + {% set productSupplierWidget %} +
+
+
+ {{ dataGrid.renderGrid('marello-product-suppliers-grid', {'suppliers': marello_supplier_get_supplier_ids(entity)}) }} +
-
- {% endset %} + {% endset %} + {% endif %} - {% set productCategoriesWidget %} -
-
-
- {{ dataGrid.renderGrid('marello-product-categories-grid', {'categories': marello_product_get_categories_ids(entity)}) }} + {% if marello_entity_has_attribute(entity, 'categories') %} + {% set productCategoriesWidget %} +
+
+
+ {{ dataGrid.renderGrid('marello-product-categories-grid', {'categories': marello_product_get_categories_ids(entity)}) }} +
-
- {% endset %} + {% endset %} + {% endif %} {% set dataBlocks = dataBlocks|merge([{ 'title' : 'marello.pricing.header.label'|trans, @@ -169,27 +177,31 @@ }] }]) %} - {% set dataBlocks = dataBlocks|merge([{ - 'title' : 'marello.supplier.entity_plural_label'|trans, - 'subblocks': [{ - 'title' : null, - 'useSpan': false, - 'data' : [ - productSupplierWidget - ] - }] - }]) %} + {% if marello_entity_has_attribute(entity, 'suppliers') %} + {% set dataBlocks = dataBlocks|merge([{ + 'title' : 'marello.supplier.entity_plural_label'|trans, + 'subblocks': [{ + 'title' : null, + 'useSpan': false, + 'data' : [ + productSupplierWidget + ] + }] + }]) %} + {% endif %} - {% set dataBlocks = dataBlocks|merge([{ - 'title' : 'marello.catalog.category.entity_plural_label'|trans, - 'subblocks': [{ - 'title' : null, - 'useSpan': false, - 'data' : [ - productCategoriesWidget - ] - }] - }]) %} + {% if marello_entity_has_attribute(entity, 'categories') %} + {% set dataBlocks = dataBlocks|merge([{ + 'title' : 'marello.catalog.category.entity_plural_label'|trans, + 'subblocks': [{ + 'title' : null, + 'useSpan': false, + 'data' : [ + productCategoriesWidget + ] + }] + }]) %} + {% endif %} {% set additionalData %}
diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/widget/info.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Product/widget/info.html.twig index f6e3475cb..c539276f6 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Product/widget/info.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Product/widget/info.html.twig @@ -4,11 +4,17 @@
{{ UI.renderProperty('marello.product.sku.label'|trans, product.sku) }} - {{ UI.renderProperty('marello.product.name.label'|trans, product.name) }} + {{ UI.renderProperty('marello.product.names.label'|trans, product.defaultName) }} {{ UI.renderProperty('marello.product.status.label'|trans, product.status) }} - {{ UI.renderProperty('marello.product.weight.label'|trans, product.weight) }} - {{ UI.renderProperty('marello.product.warranty.label'|trans, product.warranty) }} - {{ UI.renderProperty('marello.product.manufacturing_code.label'|trans, product.manufacturingCode) }} + {% if marello_entity_has_attribute(product, 'weight') %} + {{ UI.renderProperty('marello.product.weight.label'|trans, product.weight) }} + {% endif %} + {% if marello_entity_has_attribute(product, 'warranty') %} + {{ UI.renderProperty('marello.product.warranty.label'|trans, product.warranty) }} + {% endif %} + {% if marello_entity_has_attribute(product, 'manufacturingCode') %} + {{ UI.renderProperty('marello.product.manufacturing_code.label'|trans, product.manufacturingCode) }} + {% endif %}
diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Variant/Form/fields.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Variant/Form/fields.html.twig index ab847b3d4..fbcfb77ec 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Variant/Form/fields.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Variant/Form/fields.html.twig @@ -19,7 +19,7 @@ {% endblock %} -{% macro marello_product_collection_item_prototype(widget) %} +{% macro marello_product_collection_item_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -35,7 +35,7 @@ {% set name = widget.vars.full_name %} {% set disabled = widget.parent.vars.disabled %} {% endif %} - @@ -51,9 +51,12 @@ {% endmacro %} {% block marello_product_collection_widget %} + {% import _self as fields %} + {% spaceless %} + {% set widgetContainerAttributes = block('widget_container_attributes') %} {% if prototype is defined %} - {% set prototype_html = _self.marello_product_collection_item_prototype(form) %} + {% set prototype_html = fields.marello_product_collection_item_prototype(form, widgetContainerAttributes) %} {% endif %} {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'marello-item-collection grid-container' }) %} {% set id = id ~ '_collection' %} @@ -74,7 +77,7 @@ {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_product_collection_item_prototype(child) }} + {{ fields.marello_product_collection_item_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} @@ -87,7 +90,7 @@ {% endif %}
{% if handle_primary and (prototype is not defined or prototype.primary is defined) %} - {{ _self.oro_collection_validate_primary_js(_context) }} + {{ fields.oro_collection_validate_primary_js(_context) }} {% endif %} {% endspaceless %} {% endblock %} diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Variant/update.html.twig b/src/Marello/Bundle/ProductBundle/Resources/views/Variant/update.html.twig index 00f84e83a..1f2b55b23 100644 --- a/src/Marello/Bundle/ProductBundle/Resources/views/Variant/update.html.twig +++ b/src/Marello/Bundle/ProductBundle/Resources/views/Variant/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% set gridName = 'marello-product-variant-extended-grid' %} diff --git a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/ProductJsonApiTest.php b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/ProductJsonApiTest.php index 42f4f1873..9075b1ce1 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/ProductJsonApiTest.php +++ b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/ProductJsonApiTest.php @@ -82,7 +82,10 @@ public function testCreateNewProduct() /** @var Product $product */ $productRepo = $this->getEntityManager()->getRepository(Product::class); $product = $productRepo->findOneBySku($responseContent->data->id); - $this->assertEquals($product->getName(), $responseContent->data->attributes->name); + $this->assertEquals( + $product->getDenormalizedDefaultName(), + $responseContent->data->attributes->denormalizedDefaultName + ); } /** @@ -106,6 +109,10 @@ public function testUpdateProduct() /** @var Product $product */ $productRepo = $this->getEntityManager()->getRepository(Product::class); $product = $productRepo->findOneBySku($responseContent->data->id); - $this->assertEquals($product->getName(), $responseContent->data->attributes->name); + $this->assertEquals($product->getManufacturingCode(), $responseContent->data->attributes->manufacturingCode); + $this->assertEquals( + $product->getDenormalizedDefaultName(), + $responseContent->data->attributes->denormalizedDefaultName + ); } } diff --git a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/requests/product_create.yml b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/requests/product_create.yml index 307f4a199..5d782c412 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/requests/product_create.yml +++ b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/requests/product_create.yml @@ -2,12 +2,18 @@ data: type: marelloproducts id: 'my-sku' attributes: - cost: 10 manufacturingCode: '1234567890987654321' - name: 'My name' warranty: 10 weight: 10.000 relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'names-1' + - + type: localizedfallbackvalues + id: 'names-2' saleschannels: data: - @@ -27,3 +33,26 @@ data: data: type: marellotaxcodes id: 'taxCode->code)>' +included: + - + type: localizedfallbackvalues + id: 'names-1' + attributes: + fallback: null + string: 'Test product' + text: null + relationships: + localization: + data: null + - + type: localizedfallbackvalues + id: 'names-2' + attributes: + fallback: null + string: 'Product in spanish' + text: null + relationships: + localization: + data: + type: localizations + id: 'getId())>' \ No newline at end of file diff --git a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/requests/product_update.yml b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/requests/product_update.yml index 033cc6b34..d232ed4b4 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/requests/product_update.yml +++ b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/requests/product_update.yml @@ -2,4 +2,26 @@ data: type: marelloproducts id: 'sku)>' attributes: - name: 'new_name' + manufacturingCode: 'new code' + relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'getId())>' + - + type: localizedfallbackvalues + id: 'names-new' +included: + - + type: localizedfallbackvalues + id: 'names-new' + attributes: + fallback: null + string: 'Product in spanish' + text: null + relationships: + localization: + data: + type: localizations + id: 'getId())>' \ No newline at end of file diff --git a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/cget_product_list.yml b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/cget_product_list.yml index 349d59640..369e61649 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/cget_product_list.yml +++ b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/cget_product_list.yml @@ -4,14 +4,18 @@ data: id: p1 attributes: productId: '@product1->id' - name: product1 + denormalizedDefaultName: 'product1' manufacturingCode: null productType: null - cost: null weight: 1 warranty: null data: null relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'id)>' productstatus: data: type: marelloproductstatuses @@ -45,14 +49,18 @@ data: id: p2 attributes: productId: '@product2->id' - name: 'product 2' + denormalizedDefaultName: 'product2' manufacturingCode: null productType: null - cost: null weight: 2 warranty: null data: null relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'id)>' productstatus: data: type: marelloproductstatuses @@ -92,14 +100,18 @@ data: id: p3 attributes: productId: '@product3->id' - name: 'product 3' + denormalizedDefaultName: 'product3' manufacturingCode: null productType: null - cost: null weight: 5 warranty: null data: null relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'id)>' productstatus: data: type: marelloproductstatuses @@ -139,14 +151,18 @@ data: id: p4 attributes: productId: '@product4->id' - name: 'product 4' + denormalizedDefaultName: 'product4' manufacturingCode: null productType: null - cost: null weight: 10 warranty: null data: null relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'id)>' productstatus: data: type: marelloproductstatuses @@ -192,14 +208,18 @@ data: id: p5 attributes: productId: '@product5->id' - name: product5 + denormalizedDefaultName: 'product5' manufacturingCode: null productType: null - cost: null weight: 1 warranty: null data: null relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'id)>' productstatus: data: type: marelloproductstatuses @@ -233,14 +253,18 @@ data: id: p6 attributes: productId: '@product6->id' - name: product6 + denormalizedDefaultName: 'product6' manufacturingCode: null productType: null - cost: null weight: 1 warranty: null data: null relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'id)>' productstatus: data: type: marelloproductstatuses diff --git a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/get_product_by_id.yml b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/get_product_by_id.yml index c948eda4c..1067f3f60 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/get_product_by_id.yml +++ b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/get_product_by_id.yml @@ -3,14 +3,18 @@ data: id: p1 attributes: productId: '@product1->id' - name: product1 + denormalizedDefaultName: product1 manufacturingCode: null productType: null - cost: null weight: 1 warranty: null data: null relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'id)>' productstatus: data: type: marelloproductstatuses diff --git a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/get_product_by_sku.yml b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/get_product_by_sku.yml index 53360383f..78a210746 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/get_product_by_sku.yml +++ b/src/Marello/Bundle/ProductBundle/Tests/Functional/Api/responses/get_product_by_sku.yml @@ -4,14 +4,18 @@ data: id: p1 attributes: productId: '@product1->id' - name: product1 + denormalizedDefaultName: product1 manufacturingCode: null productType: null - cost: null weight: 1 warranty: null data: null relationships: + names: + data: + - + type: localizedfallbackvalues + id: 'id)>' productstatus: data: type: marelloproductstatuses diff --git a/src/Marello/Bundle/ProductBundle/Tests/Functional/Controller/ProductAttributeControllerTest.php b/src/Marello/Bundle/ProductBundle/Tests/Functional/Controller/ProductAttributeControllerTest.php index 0cf8e7101..343ecc2a4 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Functional/Controller/ProductAttributeControllerTest.php +++ b/src/Marello/Bundle/ProductBundle/Tests/Functional/Controller/ProductAttributeControllerTest.php @@ -4,12 +4,9 @@ use Symfony\Component\HttpFoundation\Response; -use Oro\Bundle\EntityConfigBundle\Tests\Functional\Controller\AttributeControllerTest; +use Oro\Bundle\EntityExtendBundle\Tests\Functional\AbstractConfigControllerTest; -/** - * @outputBuffering enabled - */ -class ProductAttributeControllerTest extends AttributeControllerTest +class ProductAttributeControllerTest extends AbstractConfigControllerTest { const PRODUCT_ENTITY_ALIAS = 'marelloproduct'; @@ -30,26 +27,4 @@ protected function getTestEntityAlias() { return self::PRODUCT_ENTITY_ALIAS; } - - /** - * {@inheritdoc} - */ - public function testCreateImage() - { - // skip test during hardcoded entity classname in dependent functions - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } - - /** - * {@inheritdoc} - */ - public function testCreateFile() - { - // skip test during hardcoded entity classname in dependent functions - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } } diff --git a/src/Marello/Bundle/ProductBundle/Tests/Functional/Controller/ProductControllerTest.php b/src/Marello/Bundle/ProductBundle/Tests/Functional/Controller/ProductControllerTest.php index 27bd5e7a9..e1bdf1b7c 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Functional/Controller/ProductControllerTest.php +++ b/src/Marello/Bundle/ProductBundle/Tests/Functional/Controller/ProductControllerTest.php @@ -3,12 +3,13 @@ namespace Marello\Bundle\ProductBundle\Tests\Functional\Controller; use Marello\Bundle\ProductBundle\Entity\Product; -use Marello\Bundle\ProductBundle\Model\ProductType; +use Marello\Bundle\ProductBundle\Migrations\Data\ORM\LoadDefaultAttributeFamilyData; use Marello\Bundle\ProductBundle\Provider\ProductTypesProvider; use Marello\Bundle\ProductBundle\Tests\Functional\DataFixtures\LoadProductData; use Marello\Bundle\SalesBundle\Tests\Functional\DataFixtures\LoadSalesData; use Marello\Bundle\SupplierBundle\Tests\Functional\DataFixtures\LoadSupplierData; use Marello\Bundle\TaxBundle\Tests\Functional\DataFixtures\LoadTaxCodeData; +use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeFamily; use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase; use Symfony\Component\DomCrawler\Form; use Symfony\Component\HttpFoundation\Response; @@ -52,10 +53,16 @@ public function testCreateProduct() $productTypesProvider = $this->getContainer()->get('marello_product.provider.product_types'); if (count($productTypesProvider->getProductTypes()) > 1) { $defaultProductType = $productTypesProvider->getProductType('simple')->getName(); + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + /** @var AttributeFamily $attributeFamily */ + $attributeFamily = $em + ->getRepository(AttributeFamily::class) + ->findOneBy(['code' => LoadDefaultAttributeFamilyData::DEFAULT_FAMILY_CODE]); $form = $crawler->selectButton('Continue')->form(); $formValues = $form->getPhpValues(); $formValues['input_action'] = 'marello_product_create'; $formValues['marello_product_step_one']['type'] = $defaultProductType; + $formValues['marello_product_step_one']['attributeFamily'] = $attributeFamily->getId(); $this->client->followRedirects(true); $crawler = $this->client->request( 'POST', @@ -69,7 +76,7 @@ public function testCreateProduct() $form = $crawler->selectButton('Save and Close')->form(); $formValues = $form->getPhpValues(); - $formValues['marello_product_form']['name'] = $name; + $formValues['marello_product_form']['names']['values']['default'] = $name; $formValues['marello_product_form']['sku'] = $sku; $formValues['marello_product_form']['status'] = 'enabled'; $formValues['marello_product_form']['addSalesChannels'] @@ -137,7 +144,7 @@ public function testUpdateProductSuppliers() 'input_action' => 'save_and_stay', 'marello_product_form' => [ '_token' => $form['marello_product_form[_token]']->getValue(), - 'name' => $form['marello_product_form[name]']->getValue(), + 'names' => ['values' => ['default' => $form['marello_product_form[names][values][default]']->getValue()]], 'sku' => $form['marello_product_form[sku]']->getValue(), 'status' => $form['marello_product_form[status]']->getValue(), 'suppliers' => $productSuppliers @@ -185,7 +192,7 @@ public function testUpdateProductTaxCodes() 'input_action' => 'save_and_stay', 'marello_product_form' => [ '_token' => $form['marello_product_form[_token]']->getValue(), - 'name' => $form['marello_product_form[name]']->getValue(), + 'names' => ['values' => ['default' => $form['marello_product_form[names][values][default]']->getValue()]], 'sku' => $form['marello_product_form[sku]']->getValue(), 'status' => $form['marello_product_form[status]']->getValue(), 'taxCode' => $taxCode, @@ -230,7 +237,7 @@ public function testUpdateProduct($name) /** @var Form $form */ $form = $crawler->selectButton('Save and Close')->form(); $name = 'name' . self::generateRandomString(); - $form['marello_product_form[name]'] = $name; + $form['marello_product_form[names][values][default]'] = $name; $form['marello_product_form[removeSalesChannels]'] = $this->getReference(LoadSalesData::CHANNEL_1_REF)->getId(); $form['marello_product_form[addSalesChannels]'] diff --git a/src/Marello/Bundle/ProductBundle/Tests/Functional/DataFixtures/LoadProductData.php b/src/Marello/Bundle/ProductBundle/Tests/Functional/DataFixtures/LoadProductData.php index c279e0c7f..6adc549f4 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Functional/DataFixtures/LoadProductData.php +++ b/src/Marello/Bundle/ProductBundle/Tests/Functional/DataFixtures/LoadProductData.php @@ -20,6 +20,10 @@ use Marello\Bundle\SalesBundle\Tests\Functional\DataFixtures\LoadSalesData; use Marello\Bundle\SupplierBundle\Tests\Functional\DataFixtures\LoadSupplierData; use Oro\Bundle\EntityConfigBundle\Attribute\Entity\AttributeFamily; +use Oro\Bundle\EntityConfigBundle\Tests\Functional\DataFixtures\LoadAttributeFamilyData; +use Oro\Bundle\LocaleBundle\Entity\Localization; +use Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue; +use Oro\Bundle\LocaleBundle\Tests\Functional\DataFixtures\LoadLocalizationData; class LoadProductData extends AbstractFixture implements DependentFixtureInterface { @@ -41,7 +45,9 @@ class LoadProductData extends AbstractFixture implements DependentFixtureInterfa /** @var array $data */ protected $data = [ self::PRODUCT_1_REF => [ - 'name' => 'product1', + 'names' => [ + ['reference' => 'product1.names.default', 'string' => 'product1'] + ], 'sku' => 'p1', 'price' => 10, 'weight' => 1.00, @@ -58,7 +64,9 @@ class LoadProductData extends AbstractFixture implements DependentFixtureInterfa ] ], self::PRODUCT_2_REF => [ - 'name' => 'product 2', + 'names' => [ + ['reference' => 'product2.names.default', 'string' => 'product2'] + ], 'sku' => 'p2', 'price' => 25, 'weight' => 2.00, @@ -82,7 +90,9 @@ class LoadProductData extends AbstractFixture implements DependentFixtureInterfa ] ], self::PRODUCT_3_REF => [ - 'name' => 'product 3', + 'names' => [ + ['reference' => 'product3.names.default', 'string' => 'product3'] + ], 'sku' => 'p3', 'price' => 50, 'weight' => 5.00, @@ -106,7 +116,9 @@ class LoadProductData extends AbstractFixture implements DependentFixtureInterfa ] ], self::PRODUCT_4_REF => [ - 'name' => 'product 4', + 'names' => [ + ['reference' => 'product4.names.default', 'string' => 'product4'] + ], 'sku' => 'p4', 'price' => 100, 'weight' => 10.00, @@ -127,7 +139,9 @@ class LoadProductData extends AbstractFixture implements DependentFixtureInterfa ] ], self::PRODUCT_5_REF => [ - 'name' => 'product5', + 'names' => [ + ['reference' => 'product5.names.default', 'string' => 'product5'] + ], 'sku' => 'p5', 'price' => 10, 'weight' => 1.00, @@ -144,7 +158,9 @@ class LoadProductData extends AbstractFixture implements DependentFixtureInterfa ] ], self::PRODUCT_6_REF => [ - 'name' => 'product6', + 'names' => [ + ['reference' => 'product6.names.default', 'string' => 'product6'] + ], 'sku' => 'p6', 'price' => 10, 'weight' => 1.00, @@ -165,6 +181,8 @@ class LoadProductData extends AbstractFixture implements DependentFixtureInterfa public function getDependencies() { return [ + LoadLocalizationData::class, + LoadAttributeFamilyData::class, LoadSalesData::class, LoadSupplierData::class, LoadTaxCodeData::class @@ -210,7 +228,11 @@ private function createProduct(array $data) { $product = new Product(); $product->setSku($data['sku']); - $product->setName($data['name']); + if (!empty($data['names'])) { + foreach ($data['names'] as $name) { + $product->addName($this->createLocalizedValue($name)); + } + } $product->setOrganization($this->defaultOrganization); $product->setWeight($data['weight']); @@ -354,4 +376,30 @@ protected function addProductSuppliers(Product $product, array $data) $product->setPreferredSupplier($preferredSupplier); } } + + /** + * @param array $name + * @return LocalizedFallbackValue + */ + protected function createLocalizedValue(array $name) + { + $value = new LocalizedFallbackValue(); + if (array_key_exists('localization', $name)) { + /** @var Localization $localization */ + $localization = $this->getReference($name['localization']); + $value->setLocalization($localization); + } + if (array_key_exists('fallback', $name)) { + $value->setFallback($name['fallback']); + } + if (array_key_exists('string', $name)) { + $value->setString($name['string']); + } + if (array_key_exists('text', $name)) { + $value->setText($name['text']); + } + $this->setReference($name['reference'], $value); + + return $value; + } } diff --git a/src/Marello/Bundle/ProductBundle/Tests/Unit/Entity/ProductTest.php b/src/Marello/Bundle/ProductBundle/Tests/Unit/Entity/ProductTest.php index 171c63191..25619d778 100644 --- a/src/Marello/Bundle/ProductBundle/Tests/Unit/Entity/ProductTest.php +++ b/src/Marello/Bundle/ProductBundle/Tests/Unit/Entity/ProductTest.php @@ -2,6 +2,7 @@ namespace Marello\Bundle\ProductBundle\Tests\Unit\Entity; +use Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue; use PHPUnit\Framework\TestCase; use Oro\Component\Testing\Unit\EntityTrait; @@ -39,12 +40,10 @@ public function testAccessors() { $this->assertPropertyAccessors(new Product(), [ ['id', 42], - ['name', 'some string'], ['sku', 'some string'], ['manufacturingCode', 'some string'], ['status', new ProductStatus('active')], ['type', 'some string'], - ['cost', 'some string'], ['weight', 3.1415926], ['warranty', 42], ['organization', new Organization()], @@ -57,6 +56,7 @@ public function testAccessors() ['updatedAt', new \DateTime()] ]); $this->assertPropertyCollections(new Product(), [ + ['names', new LocalizedFallbackValue()], ['prices', new AssembledPriceList()], ['channels', new SalesChannel()], ['channelPrices', new AssembledChannelPriceList()], diff --git a/src/Marello/Bundle/ProductBundle/Twig/DynamicFieldsExtension.php b/src/Marello/Bundle/ProductBundle/Twig/DynamicFieldsExtension.php new file mode 100644 index 000000000..eea7753c8 --- /dev/null +++ b/src/Marello/Bundle/ProductBundle/Twig/DynamicFieldsExtension.php @@ -0,0 +1,75 @@ +attributeManager = $attributeManager; + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return self::NAME; + } + + /** + * Returns a list of functions to add to the existing list. + * + * @return array An array of functions + */ + public function getFunctions() + { + return [ + new TwigFunction( + 'marello_entity_has_attribute', + [$this, 'hasAttribute'] + ) + ]; + } + + /** + * @param AttributeFamilyAwareInterface $entity + * @param string $fieldName + * @return bool + */ + public function hasAttribute(AttributeFamilyAwareInterface $entity, $fieldName) + { + $groupsData = $this->attributeManager->getGroupsWithAttributes($entity->getAttributeFamily()); + foreach ($groupsData as $groupsDatum) { + /** @var FieldConfigModel $attribute */ + foreach ($groupsDatum['attributes'] as $attribute) { + if ($attribute) { + $field = $attribute->getFieldName(); + if ($field === $fieldName) { + return true; + } + } + } + } + + return false; + } +} diff --git a/src/Marello/Bundle/ProductBundle/Twig/ProductExtension.php b/src/Marello/Bundle/ProductBundle/Twig/ProductExtension.php index 19bc803f5..f07ae8e2c 100644 --- a/src/Marello/Bundle/ProductBundle/Twig/ProductExtension.php +++ b/src/Marello/Bundle/ProductBundle/Twig/ProductExtension.php @@ -2,28 +2,31 @@ namespace Marello\Bundle\ProductBundle\Twig; -use Marello\Bundle\ProductBundle\Entity\Repository\ProductRepository; -use Oro\Bundle\EntityBundle\ORM\DoctrineHelper; - +use Marello\Bundle\CatalogBundle\Provider\CategoriesIdsProvider; use Marello\Bundle\ProductBundle\Entity\Product; +use Marello\Bundle\ProductBundle\Entity\Repository\ProductRepository; use Marello\Bundle\SalesBundle\Provider\ChannelProvider; -use Marello\Bundle\CatalogBundle\Provider\CategoriesIdsProvider; +use Oro\Bundle\EntityBundle\ORM\DoctrineHelper; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; -class ProductExtension extends \Twig_Extension +class ProductExtension extends AbstractExtension { const NAME = 'marello_product'; /** - * @var ChannelProvider $channelProvider + * @var ChannelProvider */ protected $channelProvider; /** - * @var CategoriesIdsProvider $categoriesIdsProvider + * @var CategoriesIdsProvider */ protected $categoriesIdsProvider; - /** @var DoctrineHelper $doctrineHelper */ + /** + * @var DoctrineHelper + */ private $doctrineHelper; /** @@ -54,15 +57,15 @@ public function getName() public function getFunctions() { return [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_sales_get_saleschannel_ids', [$this, 'getSalesChannelsIds'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_product_get_categories_ids', [$this, 'getCategoriesIds'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_get_product_by_sku', [$this, 'getProductBySku'] ) diff --git a/src/Marello/Bundle/ProductBundle/Validator/Constraints/ProductSupplierRelationsDropship.php b/src/Marello/Bundle/ProductBundle/Validator/Constraints/ProductSupplierRelationsDropship.php new file mode 100644 index 000000000..ae6eda3fc --- /dev/null +++ b/src/Marello/Bundle/ProductBundle/Validator/Constraints/ProductSupplierRelationsDropship.php @@ -0,0 +1,27 @@ +doctrine = $doctrine; + } + + /** + * Checks if the passed entity is unique in collection. + * @param mixed $entity + * @param Constraint $constraint + * @throws UnexpectedTypeException + */ + public function validate($entity, Constraint $constraint) + { + if ($entity instanceof Product && $entity->getId()) { + $existingSupplierRelationsCollection = $this->doctrine + ->getManagerForClass(ProductSupplierRelation::class) + ->getRepository(ProductSupplierRelation::class) + ->findBy(['product' => $entity->getId()]); + $existingSupplierRelations = $this->makeSupplierRelationsByIdsArray($existingSupplierRelationsCollection); + $supplierRelations = $this->makeSupplierRelationsByIdsArray($entity->getSuppliers()->toArray()); + foreach ($existingSupplierRelations as $id => $existingSupplierRelation) { + if ((!isset($supplierRelations[$id]) && $existingSupplierRelation->getCanDropship()) || + (isset($supplierRelations[$id]) && !$supplierRelations[$id]->getCanDropship())) { + $this->onProductDropshipOff($existingSupplierRelation, $constraint); + } + } + } + } + + /** + * @param array $supplierRelations + * @return array + */ + private function makeSupplierRelationsByIdsArray(array $supplierRelations) + { + $supplierRelationsByIds = []; + foreach ($supplierRelations as $supplierRelation) { + if ($id = $supplierRelation->getId()) { + $supplierRelationsByIds[$id] = $supplierRelation; + } + } + + return $supplierRelationsByIds; + } + + /** + * @param ProductSupplierRelation $productSupplierRelation + * @param Constraint $constraint + */ + private function onProductDropshipOff(ProductSupplierRelation $productSupplierRelation, Constraint $constraint) + { + $warehouse = $this->getWarehouse($productSupplierRelation->getSupplier()); + if ($warehouse) { + $inventoryItem = $this->doctrine + ->getManagerForClass(InventoryItem::class) + ->getRepository(InventoryItem::class) + ->findOneByProduct($productSupplierRelation->getProduct()); + + $inventoryLevel = $inventoryItem->getInventoryLevel($warehouse); + if ($inventoryLevel) { + if ($inventoryLevel->getInventoryQty() > 0 || $inventoryLevel->getAllocatedInventoryQty() > 0) { + $this->context->buildViolation($constraint->message) + ->atPath('suppliers') + ->addViolation(); + } + } + } + } + + /** + * @param Supplier $supplier + * @return Warehouse + */ + private function getWarehouse(Supplier $supplier) + { + $warehouseType = $this->doctrine + ->getManagerForClass(WarehouseType::class) + ->getRepository(WarehouseType::class) + ->find(WarehouseTypeProviderInterface::WAREHOUSE_TYPE_EXTERNAL); + $warehouse = $this->doctrine + ->getManagerForClass(Warehouse::class) + ->getRepository(Warehouse::class) + ->findOneBy([ + 'code' => sprintf('%s_external_warehouse', str_replace(' ', '_', strtolower($supplier->getName()))), + 'warehouseType' => $warehouseType + ]); + + return $warehouse; + } +} diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Controller/PurchaseOrderController.php b/src/Marello/Bundle/PurchaseOrderBundle/Controller/PurchaseOrderController.php index 3d8c2d989..a15c6e4fa 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Controller/PurchaseOrderController.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Controller/PurchaseOrderController.php @@ -9,20 +9,22 @@ use Marello\Bundle\PurchaseOrderBundle\Form\Type\PurchaseOrderCreateStepOneType; use Marello\Bundle\PurchaseOrderBundle\Form\Type\PurchaseOrderCreateStepTwoType; use Marello\Bundle\SupplierBundle\Entity\Supplier; - +use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; -use Oro\Bundle\SecurityBundle\Annotation\Acl; +use Symfony\Component\Routing\Annotation\Route; -class PurchaseOrderController extends Controller +class PurchaseOrderController extends AbstractController { /** - * @Config\Route("/", name="marello_purchaseorder_purchaseorder_index") - * @Config\Template + * @Route( + * path="/", + * name="marello_purchaseorder_purchaseorder_index" + * ) + * @Config\Template("MarelloPurchaseOrderBundle:PurchaseOrder:index.html.twig") * @AclAncestor("marello_purchase_order_view") */ public function indexAction() @@ -31,8 +33,12 @@ public function indexAction() } /** - * @Config\Route("/view/{id}", requirements={"id"="\d+"}, name="marello_purchaseorder_purchaseorder_view") - * @Config\Template + * @Route( + * path="/view/{id}", + * requirements={"id"="\d+"}, + * name="marello_purchaseorder_purchaseorder_view" + * ) + * @Config\Template("MarelloPurchaseOrderBundle:PurchaseOrder:view.html.twig") * @AclAncestor("marello_purchase_order_view") * * @param PurchaseOrder $purchaseOrder @@ -47,7 +53,10 @@ public function viewAction(PurchaseOrder $purchaseOrder) } /** - * @Config\Route("/select-products", name="marello_purchaseorder_purchaseorder_selectproducts") + * @Route( + * path="/select-products", + * name="marello_purchaseorder_purchaseorder_selectproducts" + * ) * @Config\Template * @AclAncestor("marello_purchase_order_create") */ @@ -57,7 +66,10 @@ public function selectProductsAction() } /** - * @Config\Route("/create", name="marello_purchaseorder_purchaseorder_create") + * @Route( + * path="/create", + * name="marello_purchaseorder_purchaseorder_create" + * ) * @Config\Template("MarelloPurchaseOrderBundle:PurchaseOrder:createStepOne.html.twig") * @AclAncestor("marello_purchase_order_create") * @@ -71,9 +83,13 @@ public function createAction(Request $request) } /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_purchaseorder_purchaseorder_update") + * @Route( + * path="/update/{id}", + * requirements={"id"="\d+"}, + * name="marello_purchaseorder_purchaseorder_update" + * ) * @AclAncestor("marello_purchase_order_update") - * @Config\Template + * @Config\Template("MarelloPurchaseOrderBundle:PurchaseOrder:update.html.twig") * * @param PurchaseOrder $purchaseOrder * @@ -85,7 +101,10 @@ public function updateAction(PurchaseOrder $purchaseOrder) } /** - * @Config\Route("/create/step-two", name="marello_purchaseorder_purchaseorder_create_step_two") + * @Route( + * path="/create/step-two", + * name="marello_purchaseorder_purchaseorder_create_step_two" + * ) * @Config\Template("MarelloPurchaseOrderBundle:PurchaseOrder:createStepTwo.html.twig") * @AclAncestor("marello_purchase_order_create") * @@ -219,14 +238,16 @@ protected function update(PurchaseOrder $purchaseOrder) } /** - * @Config\Route( - * "/widget/products/{id}", + * @param PurchaseOrder|null $purchaseOrder + * @Route( + * path="/widget/products/{id}", * name="marello_purchase_order_widget_products_by_supplier", * requirements={"id"="\d+"}, * defaults={"id"=0} * ) * @AclAncestor("marello_product_view") - * @Config\Template() + * @Config\Template("MarelloPurchaseOrderBundle:PurchaseOrder/widget:productsBySupplier.html.twig") + * @return array */ public function productsBySupplierAction(PurchaseOrder $purchaseOrder = null) { @@ -244,10 +265,13 @@ public function productsBySupplierAction(PurchaseOrder $purchaseOrder = null) } /** - * @Config\Route("/supplier-product-price/{productId}/{supplierId}", name="marello_purchase_order_supplier_product_price") + * @Route( + * path="/supplier-product-price/{productId}/{supplierId}", + * methods={"GET"}, + * name="marello_purchase_order_supplier_product_price" + * ) * @Config\ParamConverter("product", options={"mapping": {"productId" : "id"}}) * @Config\ParamConverter("supplier", options={"mapping": {"supplierId" : "id"}}) - * @Config\Method({"GET"}) * @AclAncestor("marello_product_view") * * @param Product $product diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Cron/PurchaseOrderAdviceCommand.php b/src/Marello/Bundle/PurchaseOrderBundle/Cron/PurchaseOrderAdviceCommand.php index ebe69989b..830cba549 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Cron/PurchaseOrderAdviceCommand.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Cron/PurchaseOrderAdviceCommand.php @@ -3,7 +3,7 @@ namespace Marello\Bundle\PurchaseOrderBundle\Cron; use Marello\Bundle\NotificationBundle\Email\SendProcessor; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\PurchaseOrderBundle\Entity\PurchaseOrder; use Marello\Bundle\PurchaseOrderBundle\Entity\PurchaseOrderItem; diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Entity/PurchaseOrderItem.php b/src/Marello/Bundle/PurchaseOrderBundle/Entity/PurchaseOrderItem.php index b435024ee..ca8daf4b6 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Entity/PurchaseOrderItem.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Entity/PurchaseOrderItem.php @@ -270,14 +270,14 @@ public function getOrder() } /** - * @param ProductInterface $product + * @param ProductInterface|Product $product * * @return $this */ public function setProduct(ProductInterface $product) { $this->product = $product; - $this->productName = $this->product->getName(); + $this->productName = $this->product->getDenormalizedDefaultName(); $this->productSku = $this->product->getSku(); return $this; diff --git a/src/Marello/Bundle/PurchaseOrderBundle/EventListener/Datagrid/PurchaseOrderGridListener.php b/src/Marello/Bundle/PurchaseOrderBundle/EventListener/Datagrid/PurchaseOrderGridListener.php index 3167f01c7..a94ea0e75 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/EventListener/Datagrid/PurchaseOrderGridListener.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/EventListener/Datagrid/PurchaseOrderGridListener.php @@ -2,12 +2,10 @@ namespace Marello\Bundle\PurchaseOrderBundle\EventListener\Datagrid; +use Doctrine\ORM\EntityManager; use Marello\Bundle\PurchaseOrderBundle\Entity\PurchaseOrder; -use Oro\Bundle\DataGridBundle\Event\BuildAfter; use Oro\Bundle\DataGridBundle\Event\BuildBefore; -use Oro\Bundle\FilterBundle\Form\Type\Filter\TextFilterType; use Oro\Bundle\WorkflowBundle\Model\WorkflowManager; -use Doctrine\ORM\EntityManager; class PurchaseOrderGridListener { diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Form/EventListener/PurchaseOrderItemSubscriber.php b/src/Marello/Bundle/PurchaseOrderBundle/Form/EventListener/PurchaseOrderItemSubscriber.php index c71e67a18..2f6910b49 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Form/EventListener/PurchaseOrderItemSubscriber.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Form/EventListener/PurchaseOrderItemSubscriber.php @@ -6,7 +6,7 @@ use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormError; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; class PurchaseOrderItemSubscriber implements EventSubscriberInterface { diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderCreateHandler.php b/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderCreateHandler.php index 288ffad58..093f66d60 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderCreateHandler.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderCreateHandler.php @@ -6,6 +6,7 @@ use Marello\Bundle\PurchaseOrderBundle\Entity\PurchaseOrder; use Oro\Bundle\FormBundle\Form\Handler\RequestHandlerTrait; use Oro\Bundle\OrganizationBundle\Entity\Organization; +use Oro\Bundle\SecurityBundle\Authentication\Token\OrganizationAwareTokenInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -56,9 +57,13 @@ public function handle() /* * Get organization of currently logged in user, or use first one. */ + $organization = null; if ($token = $this->tokenStorage->getToken()) { - $organization = $token->getOrganizationContext(); - } else { + if ($token instanceof OrganizationAwareTokenInterface) { + $organization = $token->getOrganization(); + } + } + if (!$organization) { $organization = $this->doctrine->getRepository(Organization::class)->getFirst(); } diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderCreateStepOneHandler.php b/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderCreateStepOneHandler.php index bc7f28fbc..f77b99cb0 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderCreateStepOneHandler.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderCreateStepOneHandler.php @@ -2,16 +2,9 @@ namespace Marello\Bundle\PurchaseOrderBundle\Form\Handler; -use Doctrine\Bundle\DoctrineBundle\Registry; -use Marello\Bundle\ProductBundle\Entity\Product; -use Marello\Bundle\PurchaseOrderBundle\Entity\PurchaseOrder; -use Marello\Bundle\PurchaseOrderBundle\Entity\PurchaseOrderItem; -use Marello\Bundle\PurchaseOrderBundle\Form\Type\PurchaseOrderCreateStepOneType; use Oro\Bundle\FormBundle\Form\Handler\RequestHandlerTrait; -use Oro\Bundle\OrganizationBundle\Entity\Organization; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; class PurchaseOrderCreateStepOneHandler { diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderUpdateHandler.php b/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderUpdateHandler.php index 1373c0653..7d3e85ee5 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderUpdateHandler.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Form/Handler/PurchaseOrderUpdateHandler.php @@ -37,7 +37,7 @@ public function __construct( } /** - * + * @param PurchaseOrder $entity * @return bool */ public function process(PurchaseOrder $entity) diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/MarelloPurchaseOrderBundleInstaller.php b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/MarelloPurchaseOrderBundleInstaller.php index 81aaf7070..5349baf72 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/MarelloPurchaseOrderBundleInstaller.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/MarelloPurchaseOrderBundleInstaller.php @@ -24,7 +24,7 @@ class MarelloPurchaseOrderBundleInstaller implements */ public function getMigrationVersion() { - return 'v1_3_1'; + return 'v1_3_3'; } /** diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_1/MarelloPurchaseOrderBundle.php b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_1/MarelloPurchaseOrderBundle.php index 67ee1463c..bb339cf8d 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_1/MarelloPurchaseOrderBundle.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_1/MarelloPurchaseOrderBundle.php @@ -3,10 +3,8 @@ namespace Marello\Bundle\PurchaseOrderBundle\Migrations\Schema\v1_1; use Doctrine\DBAL\Schema\Schema; -use Oro\Bundle\MigrationBundle\Migration\QueryBag; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtension; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtensionAwareInterface; use Oro\Bundle\MigrationBundle\Migration\Migration; +use Oro\Bundle\MigrationBundle\Migration\QueryBag; /** * @SuppressWarnings(PHPMD.TooManyMethods) diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_1/MarelloPurchaseOrderBundle.php b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_1/MarelloPurchaseOrderBundle.php index 6dad9ce21..177eba177 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_1/MarelloPurchaseOrderBundle.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_1/MarelloPurchaseOrderBundle.php @@ -31,6 +31,7 @@ public function up(Schema $schema, QueryBag $queries) * Creates supplier column and sets the value of the current purchase orders * * @param Schema $schema + * @param QueryBag $queries */ protected function updatePurchaseOrderTable(Schema $schema, QueryBag $queries) { diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_2/MarelloPurchaseOrderBundle.php b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_2/MarelloPurchaseOrderBundle.php index 75671d169..e9177a765 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_2/MarelloPurchaseOrderBundle.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_2/MarelloPurchaseOrderBundle.php @@ -3,10 +3,8 @@ namespace Marello\Bundle\PurchaseOrderBundle\Migrations\Schema\v1_2_2; use Doctrine\DBAL\Schema\Schema; -use Oro\Bundle\MigrationBundle\Migration\QueryBag; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtension; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtensionAwareInterface; use Oro\Bundle\MigrationBundle\Migration\Migration; +use Oro\Bundle\MigrationBundle\Migration\QueryBag; /** * @SuppressWarnings(PHPMD.TooManyMethods) @@ -19,7 +17,7 @@ class MarelloPurchaseOrderBundle implements Migration */ public function up(Schema $schema, QueryBag $queries) { - $this->updatePurchaseOrderTable($schema, $queries); + $this->updatePurchaseOrderTable($schema); } /** @@ -27,7 +25,7 @@ public function up(Schema $schema, QueryBag $queries) * * @param Schema $schema */ - protected function updatePurchaseOrderTable(Schema $schema, QueryBag $queries) + protected function updatePurchaseOrderTable(Schema $schema) { $table = $schema->getTable('marello_purchase_order'); diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_3/MarelloPurchaseOrderBundle.php b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_3/MarelloPurchaseOrderBundle.php index c32a38070..debb2da8c 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_3/MarelloPurchaseOrderBundle.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_2_3/MarelloPurchaseOrderBundle.php @@ -3,10 +3,8 @@ namespace Marello\Bundle\PurchaseOrderBundle\Migrations\Schema\v1_2_3; use Doctrine\DBAL\Schema\Schema; -use Oro\Bundle\MigrationBundle\Migration\QueryBag; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtension; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtensionAwareInterface; use Oro\Bundle\MigrationBundle\Migration\Migration; +use Oro\Bundle\MigrationBundle\Migration\QueryBag; /** * @SuppressWarnings(PHPMD.TooManyMethods) @@ -19,7 +17,7 @@ class MarelloPurchaseOrderBundle implements Migration */ public function up(Schema $schema, QueryBag $queries) { - $this->updatePurchaseOrderTable($schema, $queries); + $this->updatePurchaseOrderTable($schema); } /** @@ -27,7 +25,7 @@ public function up(Schema $schema, QueryBag $queries) * * @param Schema $schema */ - protected function updatePurchaseOrderTable(Schema $schema, QueryBag $queries) + protected function updatePurchaseOrderTable(Schema $schema) { $table = $schema->getTable('marello_purchase_order'); diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_3_2/MarelloPurchaseOrderBundle.php b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_3_2/MarelloPurchaseOrderBundle.php new file mode 100644 index 000000000..72d9cb2b2 --- /dev/null +++ b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_3_2/MarelloPurchaseOrderBundle.php @@ -0,0 +1,35 @@ +updatePurchaseOrderTable($schema); + } + + /** + * @param Schema $schema + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function updatePurchaseOrderTable(Schema $schema) + { + $table = $schema->getTable('marello_purchase_order'); + if (!$table->hasColumn('data')) { + $table->addColumn('data', 'json_array', ['notnull' => false, 'comment' => '(DC2Type:json_array)']); + } + } +} diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_3_3/MarelloPurchaseOrderBundle.php b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_3_3/MarelloPurchaseOrderBundle.php new file mode 100644 index 000000000..6ad3ec5b3 --- /dev/null +++ b/src/Marello/Bundle/PurchaseOrderBundle/Migrations/Schema/v1_3_3/MarelloPurchaseOrderBundle.php @@ -0,0 +1,49 @@ +updatePurchaseOrderItemTable($schema); + } + /** + * @param Schema $schema + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function updatePurchaseOrderItemTable(Schema $schema) + { + $table = $schema->getTable('marello_purchase_order_item'); + if (!$table->hasColumn('organization_id')) { + $table->addColumn('organization_id', 'integer', ['notnull' => false]); + } + + if (!$table->hasIndex('IDX_3483BD8632C8A3DE')) { + // add index to organization column + $table->addIndex(['organization_id']); + } + + if (!$table->hasForeignKey('FK_3483BD8632C8A3DE')) { + // add foreign key constraint + $table->addForeignKeyConstraint( + $schema->getTable('oro_organization'), + ['organization_id'], + ['id'], + ['onDelete' => 'SET NULL', 'onUpdate' => null] + ); + } + } +} diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/form.yml b/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/form.yml index e828c4d2e..d2a791a4a 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/form.yml +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/form.yml @@ -50,21 +50,19 @@ services: marello_purchase_order.form.purchase_order: class: Symfony\Component\Form\Form factory: ["@form.factory", 'create'] - scope: request arguments: - Marello\Bundle\PurchaseOrderBundle\Form\Type\PurchaseOrderType marello_purchase_order.form.purchase_order_create_step_two: class: Symfony\Component\Form\Form factory: ["@form.factory", 'create'] - scope: request arguments: - Marello\Bundle\PurchaseOrderBundle\Form\Type\PurchaseOrderCreateStepTwoType # Handlers marello_purchase_order.form.handler.purchase_order_create: class: Marello\Bundle\PurchaseOrderBundle\Form\Handler\PurchaseOrderCreateHandler - scope: request + public: true arguments: - "@marello_purchase_order.form.purchase_order_create_step_two" - "@request_stack" @@ -73,7 +71,7 @@ services: marello_purchase_order.form.handler.purchase_order_update: class: Marello\Bundle\PurchaseOrderBundle\Form\Handler\PurchaseOrderUpdateHandler - scope: request + public: true arguments: - '@marello_purchase_order.form.purchase_order' - '@request_stack' diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/jsmodules.yml b/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/jsmodules.yml new file mode 100644 index 000000000..4811bbbda --- /dev/null +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/jsmodules.yml @@ -0,0 +1,11 @@ +dynamic-imports: + marellopurchaseorder: + - marellopurchaseorder/js/multiple-entity + - marellopurchaseorder/js/multiple-entity/view + - marellopurchaseorder/js/multiple-entity/model + - marellopurchaseorder/js/multiple-entity/collection + - marellopurchaseorder/js/app/components/purchaseorder-product-component + - marellopurchaseorder/js/app/views/purchaseorder-item-view + - marellopurchaseorder/js/app/views/purchaseorder-items-view + - marellopurchaseorder/js/app/views/purchaseorder-totals-view + - marellopurchaseorder/js/datagrid/marello-purchase-order-item-candidates-builder diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/oro/datagrids.yml index 09e04649c..6b5e264b1 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/oro/datagrids.yml @@ -51,7 +51,7 @@ datagrids: orderTotal: data_name: po.orderTotal default: - purchaseOrderNumber: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC" + purchaseOrderNumber: "DESC" filters: columns: purchaseOrderNumber: @@ -107,8 +107,11 @@ datagrids: order_id: order_id columns: productSku: - label: marello.product.sku.label - frontend_type: string + label: marello.product.sku.label + data_name: productSku + type: twig + frontend_type: html + template: MarelloProductBundle:Product/Datagrid:productSku.html.twig productName: label: marello.product.name.label frontend_type: string @@ -135,7 +138,7 @@ datagrids: select: - p.id - p.sku - - p.name + - p.denormalizedDefaultName as productName - p.manufacturingCode - sup.id AS preferredSupplierId - sup.name AS preferredSupplier @@ -179,9 +182,10 @@ datagrids: sku: label: marello.product.sku.label frontend_type: string - name: + productName: label: marello.product.name.label frontend_type: string + data_name: productName manufacturingCode: label: marello.product.manufacturing_code.label frontend_type: string @@ -207,7 +211,7 @@ datagrids: columns: hasProduct: { data_name: hasProduct } sku: { data_name: p.sku } - name: { data_name: p.name } + productName: { data_name: p.denormalizedDefaultName } manufacturingCode: { data_name: p.manufacturingCode } preferredSupplier: { data_name: preferredSupplier } purchaseInventory: { data_name: i.purchaseInventory } @@ -215,7 +219,7 @@ datagrids: virtualInventory: { data_name: virtualInventory } orderAmount: { data_name: orderAmount } default: - hasProduct: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' + hasProduct: 'DESC' filters: columns: hasProduct: @@ -224,9 +228,9 @@ datagrids: sku: type: string data_name: p.sku - name: + productName: type: string - data_name: p.name + data_name: productName manufacturingCode: type: string data_name: p.manufacturingCode diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/requirejs.yml b/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/requirejs.yml deleted file mode 100644 index 9f95c9259..000000000 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/requirejs.yml +++ /dev/null @@ -1,3 +0,0 @@ -config: - paths: - 'marellopurchaseorder/js/app/views/purchaseorder-totals-view': 'bundles/marellopurchaseorder/js/app/views/purchaseorder-totals-view.js' diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/services.yml b/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/services.yml index 9d2b409eb..2bfc75fb6 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/config/services.yml @@ -1,6 +1,3 @@ -parameters: - marello_purchaseorder.entity.purchaseorder.class: Marello\Bundle\PurchaseOrderBundle\Entity\PurchaseOrder - services: marello_purchaseorder.processor.note_activity_processor: class: Marello\Bundle\PurchaseOrderBundle\Processor\NoteActivityProcessor @@ -9,7 +6,7 @@ services: - '@doctrine.orm.entity_manager' marello_purchaseorder.oro_note_entity_service: - class: '%oro_note.entity.class%' + class: 'Oro\Bundle\NoteBundle\Entity\Note' marello_purchaseorder.workflow.receive_purchase_order: class: Marello\Bundle\PurchaseOrderBundle\Workflow\Action\ReceivePurchaseOrderAction diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/components/purchaseorder-product-component.js b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/components/purchaseorder-product-component.js new file mode 100644 index 000000000..f702d3fbc --- /dev/null +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/components/purchaseorder-product-component.js @@ -0,0 +1,40 @@ +define(function(require) { + 'use strict'; + + const MultipleEntityComponent = require('oroform/js/multiple-entity/component'); + const MultipleEntityModel = require('marellopurchaseorder/js/multiple-entity/model'); + + const PurchaseOrderComponent = MultipleEntityComponent.extend({ + optionNames: MultipleEntityComponent.prototype.optionNames.concat([ + 'currency' + ]), + + /** + * @inheritDoc + */ + constructor: function PurchaseOrderComponent(options) { + PurchaseOrderComponent.__super__.constructor.call(this, options); + }, + + onModelSelect: function(value, model, listener) { + const id = model.get('id'); + if (model.get(listener.columnName)) { + this.addedModels[id] = new MultipleEntityModel({ + 'id': model.get('id'), + 'label': 'product', + 'productName': model.get('productName'), + 'value': model.get('sku') + ' - ' + model.get('productName'), + 'sku': model.get('sku'), + 'orderAmount': model.get('orderAmount'), + 'purchasePrice': model.get('purchasePrice'), + 'currency': this.currency, + isDefault: false + }); + } else if (this.addedModels.hasOwnProperty(id)) { + delete this.addedModels[id]; + } + } + }); + + return PurchaseOrderComponent; +}); \ No newline at end of file diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-item-view.js b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-item-view.js index 80d5442a1..5fc5fb027 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-item-view.js +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-item-view.js @@ -1,7 +1,7 @@ define(function(require) { 'use strict'; - var PurchaseOrderItemView, + const $ = require('jquery'), _ = require('underscore'), routing = require('routing'), @@ -13,7 +13,7 @@ define(function(require) { * @extends marellolayout.app.views.AbstractItemView * @class marellopurchaseorder.app.views.PurchaseOrderItemView */ - PurchaseOrderItemView = AbstractItemView.extend({ + const PurchaseOrderItemView = AbstractItemView.extend({ /** * @property {Object} */ diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-items-view.js b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-items-view.js index 33d60a14b..fbbc48dad 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-items-view.js +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-items-view.js @@ -1,7 +1,7 @@ define(function(require) { 'use strict'; - var PurchaseOrderItemsView, + const $ = require('jquery'), _ = require('underscore'), routing = require('routing'), @@ -13,7 +13,7 @@ define(function(require) { * @extends marellolayout.app.views.AbstractItemsView * @class marellopurchaseorder.app.views.PurchaseOrderItemsView */ - PurchaseOrderItemsView = AbstractItemsView.extend({ + const PurchaseOrderItemsView = AbstractItemsView.extend({ /** * @property {Object} */ diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-totals-view.js b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-totals-view.js index 564c16909..b3e046dc7 100755 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-totals-view.js +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/app/views/purchaseorder-totals-view.js @@ -1,19 +1,18 @@ define(function(require) { 'use strict'; - var PurchaseOrderTotalsView; - var template = require('tpl!marellopurchaseorder/templates/purchaseorder/totals.html'); - var $ = require('jquery'); - var _ = require('underscore'); - var mediator = require('oroui/js/mediator'); - var BaseView = require('oroui/js/app/views/base/view'); + const template = require('tpl-loader!marellopurchaseorder/templates/purchaseorder/totals.html'); + const $ = require('jquery'); + const _ = require('underscore'); + const mediator = require('oroui/js/mediator'); + const BaseView = require('oroui/js/app/views/base/view'); /** * @export marellopurchaseorder/js/app/views/purchaseorder-totals-view * @extends oroui.app.views.base.View * @class marellopurchaseorder.app.views.PurchaseOrderTotalsView */ - PurchaseOrderTotalsView = BaseView.extend({ + const PurchaseOrderTotalsView = BaseView.extend({ /** * @property {Object} */ diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/datagrid/marello-purchase-order-item-candidates-builder.js b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/datagrid/marello-purchase-order-item-candidates-builder.js index 7de07a5a5..50c5e7dea 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/datagrid/marello-purchase-order-item-candidates-builder.js +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/datagrid/marello-purchase-order-item-candidates-builder.js @@ -1,17 +1,13 @@ define(function(require) { 'use strict'; - var mediator = require('oroui/js/mediator'); + const mediator = require('oroui/js/mediator'); return { init: function(deferred, options) { - options.gridPromise.done(function(grid) { - - var gridName = grid.name; - + let gridName = grid.name; mediator.trigger('datagrid:doRefresh:' + gridName); - deferred.resolve(); }); } diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity.js b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity.js index ae6a539bb..837320c8e 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity.js +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity.js @@ -1,15 +1,23 @@ -define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multiple-entity/model', 'oro/dialog-widget', 'oroui/js/mediator' - ], function(_, routing, Backbone, EntityView, MultipleEntityModel, DialogWidget, mediator) { +define(function(require) { 'use strict'; - var $ = Backbone.$; + const _ = require('underscore'); + const routing = require('routing'); + const Backbone = require('backbone'); + const EntityView = require('./multiple-entity/view'); + const DialogWidget = require('oro/dialog-widget'); + const mediator = require('oroui/js/mediator'); + const $ = Backbone.$; /** * @export oroform/js/multiple-entity * @class oroform.MultipleEntity * @extends Backbone.View */ - return Backbone.View.extend({ + const PurchaseOrderMultipleEntityView = Backbone.View.extend({ + template: require('tpl-loader!marellopurchaseorder/js/multiple-entity/templates/multiple-entities.html'), + elementTemplate: require('tpl-loader!marellopurchaseorder/js/multiple-entity/templates/multiple-entity.html'), + options: { addedElement: null, allowAction: true, @@ -31,13 +39,24 @@ define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multip 'click .add-btn': 'addEntities' }, + /** + * @inheritDoc + */ + constructor: function PurchaseOrderMultipleEntityView(options) { + PurchaseOrderMultipleEntityView.__super__.constructor.call(this, options); + }, + initialize: function(options) { this.options = _.defaults(options || {}, this.options); - this.template = _.template(this.options.template); + if (typeof this.options.template === 'string') { + this.template = _.template(this.options.template); + } + if (typeof this.options.elementTemplate === 'string') { + this.elementTemplate = _.template(this.options.elementTemplate); + } this.listenTo(this.getCollection(), 'add', this.addEntity); this.listenTo(this.getCollection(), 'reset', this._onCollectionReset); this.listenTo(this.getCollection(), 'remove', this.removeDefault); - this.listenTo(this.getCollection(), 'change', this._onCollectionChange); this.$addedEl = $(this.options.addedElement); this.$removedEl = $(this.options.removedElement); @@ -56,16 +75,16 @@ define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multip }, handleRemove: function(item) { - var itemId = item && item.get('id'); + const itemId = item && item.get('id'); if (!itemId) { return; } - var addedElVal = this.$addedEl.val(); - var removedElVal = this.$removedEl.val(); + const addedElVal = this.$addedEl.val(); + const removedElVal = this.$removedEl.val(); - var added = (addedElVal && addedElVal.split(',')) || []; - var removed = (removedElVal && removedElVal.split(',')) || []; + let added = (addedElVal && addedElVal.split(',')) || []; + const removed = (removedElVal && removedElVal.split(',')) || []; if (_.contains(added, itemId)) { added = _.without(added, itemId); @@ -124,21 +143,21 @@ define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multip }, _isInitialCollectionItem: function(itemId) { - var isInitial = !!_.find(this.initialCollectionItems, function(id) { + const isInitial = !!_.find(this.initialCollectionItems, function(id) { return String(id) === String(itemId); }); return isInitial; }, _isAddedCollectionItem: function(itemId) { - var isAdded = !!_.find(this.addedCollectionItems, function(id) { + const isAdded = !!_.find(this.addedCollectionItems, function(id) { return String(id) === String(itemId); }); return isAdded; }, _isRemovedCollectionItem: function(itemId) { - var isRemoved = !!_.find(this.removedCollectionItems, function(id) { + const isRemoved = !!_.find(this.removedCollectionItems, function(id) { return String(id) === String(itemId); }); return isRemoved; @@ -159,11 +178,11 @@ define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multip if (item.get('id') === this.$defaultEl.val()) { item.set('isDefault', true); } - var entityView = new EntityView({ + const entityView = new EntityView({ model: item, name: this.options.name, hasDefault: this.options.defaultElement, - template: this.options.elementTemplate + template: this.elementTemplate }); entityView.on('removal', _.bind(this.handleRemove, this)); this.$entitiesContainer.append(entityView.render().$el); @@ -172,21 +191,21 @@ define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multip addEntities: function(e) { if (!this.selectorDialog) { - var url = this._getSelectionWidgetUrl(); - var routeAdditionalParams = $(e.target).data('route_additional_params'); + let url = this._getSelectionWidgetUrl(); + const routeAdditionalParams = $(e.target).data('route_additional_params'); if (routeAdditionalParams) { url = url + (url.indexOf('?') === -1 ? '?' : '&') + $.param(routeAdditionalParams); } this.selectorDialog = new DialogWidget({ - url: url, + url: url, title: this.options.selectorWindowTitle, stateEnabled: false, dialogOptions: { - 'modal': true, - 'width': 1024, - 'height': 500, - 'close': _.bind(function() { + modal: true, + width: 1024, + height: 500, + close: _.bind(function() { this.selectorDialog = null; }, this) } @@ -197,21 +216,27 @@ define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multip }, _getSelectionWidgetUrl: function() { - var url = this.options.selectionUrl || + const url = this.options.selectionUrl || routing.generate(this.options.selectionRouteName, this.options.selectionRouteParams); - var separator = url.indexOf('?') > -1 ? '&' : '?'; - var added = this.$addedEl.val(); - var removed = this.$removedEl.val(); - var defaultEl = this.$defaultEl.val(); + const separator = url.indexOf('?') > -1 ? '&' : '?'; + const added = this.$addedEl.val(); + const removed = this.$removedEl.val(); + const defaultEl = this.$defaultEl.val(); return url + separator + 'added=' + (added || '') + '&removed=' + (removed || '') + - '&default=' + (defaultEl || '') ; + '&default=' + (defaultEl || ''); + }, + + _initWidgets: function() { + _.delay(_.bind(function() { + this.$el.inputWidget('seekAndCreate'); + }, this)); }, processSelectedEntities: function(added, addedModels, removed) { - var self = this; + const self = this; _.intersection(added, removed).forEach(function(itemId) { if (self._isInitialCollectionItem(itemId)) { @@ -239,8 +264,8 @@ define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multip _.each(addedModels, _.bind(function(model) { this.getCollection().add(model); }, this)); - for (var i = 0; i < removed.length; i++) { - var model = this.getCollection().get(removed[i]); + for (let i = 0; i < removed.length; i++) { + const model = this.getCollection().get(removed[i]); if (model) { model.set('id', null); model.destroy(); @@ -248,15 +273,16 @@ define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multip } this.selectorDialog.remove(); + + this._initWidgets(); }, triggerTotalsUpdateEvent: function() { - var total = 0; - var currency = null; - var self = this; + let total = 0; + let currency = null; this.getCollection().each(function(model) { if (model.id !== null) { - var rowTotal = parseFloat(model.get('orderAmount')) * parseFloat(model.get('purchasePrice')); + let rowTotal = parseFloat(model.get('orderAmount')) * parseFloat(model.get('purchasePrice')); currency = model.get('currency'); if (!isNaN(rowTotal)) { total = total + rowTotal; @@ -275,7 +301,10 @@ define(['underscore', 'routing', 'backbone', './multiple-entity/view', './multip this.$entitiesContainer = this.$el.find(this.options.entitiesContainerSelector); + this._initWidgets(); return this; } }); + + return PurchaseOrderMultipleEntityView; }); diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/model.js b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/model.js index 29e7042e4..f7d876346 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/model.js +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/model.js @@ -7,18 +7,26 @@ define(['backbone'], * @class oroform.MultipleEntity.Model * @extends Backbone.Model */ - return Backbone.Model.extend({ + const EntityModel = Backbone.Model.extend({ defaults: { id: null, - link: null, label: null, isDefault: false, sku: null, - name: null, + productName: null, value: null, orderAmount: null, purchasePrice: null, currency: null + }, + + /** + * @inheritDoc + */ + constructor: function EntityModel(attrs, options) { + EntityModel.__super__.constructor.call(this, attrs, options); } + }); - }); + return EntityModel; +}); diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/templates/multiple-entity.html b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/templates/multiple-entity.html index 6c99c84ff..e699b38a8 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/templates/multiple-entity.html +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/templates/multiple-entity.html @@ -28,11 +28,8 @@ - -
-
diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/view.js b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/view.js index a52b47d5b..2c41c6dfb 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/view.js +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/public/js/multiple-entity/view.js @@ -1,16 +1,17 @@ -define(['underscore', 'backbone', 'oro/dialog-widget' - ], function(_, Backbone, DialogWidget) { +define(function(require) { 'use strict'; + const _ = require('underscore'); + const Backbone = require('backbone'); + const DialogWidget = require('oro/dialog-widget'); + /** * @export oroform/js/multiple-entity/view * @class oroform.MultipleEntity.View * @extends Backbone.View */ - return Backbone.View.extend({ - + const EntityView = Backbone.View.extend({ tagName: "tr", - className: "purchase-order-line-item display-values marello-line-item", events: { @@ -26,9 +27,20 @@ define(['underscore', 'backbone', 'oro/dialog-widget' template: null }, + /** + * @inheritDoc + */ + constructor: function EntityView(options) { + EntityView.__super__.constructor.call(this, options); + }, + initialize: function(options) { this.options = _.defaults(options || {}, this.options); - this.template = _.template(this.options.template); + if (typeof this.options.template === 'string') { + this.template = _.template(this.options.template); + } else { + this.template = this.options.template; + } this.listenTo(this.model, 'destroy', this.remove); if (this.options.defaultRequired) { this.listenTo(this.model, 'change:isDefault', this.toggleDefault); @@ -43,7 +55,7 @@ define(['underscore', 'backbone', 'oro/dialog-widget' viewDetails: function(e) { e.stopImmediatePropagation(); e.preventDefault(); - var widget = new DialogWidget({ + let widget = new DialogWidget({ 'url': this.options.model.get('link'), 'title': this.options.model.get('label'), dialogOptions: { @@ -72,8 +84,8 @@ define(['underscore', 'backbone', 'oro/dialog-widget' }, render: function() { - var data = this.model.toJSON(); - data.purchasePrice = parseFloat(data.purchasePrice).toFixed(2) + let data = this.model.toJSON(); + data.purchasePrice = parseFloat(data.purchasePrice).toFixed(2); this.$el.append(this.template(data)); this.$el.find('a.entity-info').click(_.bind(this.viewDetails, this)); this.$el.find('td.purchase-order-line-item-ordered-amount').find('input').change(_.bind(this.updateRowTotal, this)); @@ -84,18 +96,20 @@ define(['underscore', 'backbone', 'oro/dialog-widget' }, updateRowTotal: function() { - var amount = this.$el.find('td.purchase-order-line-item-ordered-amount').find('input').val(); - var price = this.$el.find('td.purchase-order-line-item-purchase-price').find('input[name*="value"]').val(); + let amount = this.$el.find('td.purchase-order-line-item-ordered-amount').find('input').val(); + let price = this.$el.find('td.purchase-order-line-item-purchase-price').find('input[name*="value"]').val(); this.model.set('orderAmount', amount); this.model.set('purchasePrice', price); - - var rowTotal = parseFloat(amount) * parseFloat(price); + + let rowTotal = parseFloat(amount) * parseFloat(price); if (!isNaN(rowTotal)) { - var currencySymbol = this.model.get('currency'); + let currencySymbol = this.model.get('currency'); this.$el.find('td.purchase-order-line-item-row-total').html(currencySymbol + rowTotal.toFixed(2)); } else { this.$el.find('td.purchase-order-line-item-row-total').html(''); } } }); + + return EntityView; }); diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/Form/fields.html.twig b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/Form/fields.html.twig index 2a9d66fe5..a7929a4f5 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/Form/fields.html.twig +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/Form/fields.html.twig @@ -78,7 +78,7 @@ {% endblock %} -{% macro marello_purchase_order_item_receive_collection_item_prototype(widget) %} +{% macro marello_purchase_order_item_receive_collection_item_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -91,7 +91,7 @@ {% endif %} {% if form.vars.value.status != 'complete' and form.vars.value.status != 'closed' %} - {{ form_widget(form) }} @@ -102,7 +102,7 @@ {% endif %} {% endmacro %} -{% macro marello_purchase_order_item_collection_item_prototype(widget) %} +{% macro marello_purchase_order_item_collection_item_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -115,7 +115,7 @@ {% set allow_delete = true %} {% endif %} - {% endmacro %} -{% macro marello_purchase_order_item_advice_collection_item_prototype(widget) %} +{% macro marello_purchase_order_item_advice_collection_item_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -150,7 +150,7 @@ {% set allow_delete = true %} {% endif %} - {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_purchase_order_item_collection_item_prototype(child) }} + {{ fields.marello_purchase_order_item_collection_item_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} @@ -216,9 +219,12 @@ {% endblock %} {% block marello_purchase_order_item_advice_collection_widget %} + {% import _self as fields %} + {% spaceless %} + {% set widgetContainerAttributes = block('widget_container_attributes') %} {% if prototype is defined %} - {% set prototype_html = _self.marello_purchase_order_item_advice_collection_item_prototype(form) %} + {% set prototype_html = fields.marello_purchase_order_item_advice_collection_item_prototype(form, widgetContainerAttributes) %} {% endif %} {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'marello-item-collection grid-container' }) %} {% set id = id ~ '_collection' %} @@ -242,7 +248,7 @@ {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_purchase_order_item_advice_collection_item_prototype(child) }} + {{ fields.marello_purchase_order_item_advice_collection_item_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} @@ -259,9 +265,12 @@ {% endblock %} {% block marello_purchase_order_item_receive_collection_widget %} + {% import _self as fields %} + {% spaceless %} + {% set widgetContainerAttributes = block('widget_container_attributes') %} {% if prototype is defined %} - {% set prototype_html = _self.marello_purchase_order_item_receive_collection_item_prototype(form) %} + {% set prototype_html = fields.marello_purchase_order_item_receive_collection_item_prototype(form, widgetContainerAttributes) %} {% endif %} {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'marello-item-collection grid-container' }) %} {% set id = id ~ '_collection' %} @@ -292,7 +301,7 @@ {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_purchase_order_item_receive_collection_item_prototype(child) }} + {{ fields.marello_purchase_order_item_receive_collection_item_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} @@ -325,41 +334,39 @@
{% endblock %} diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/create.html.twig b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/create.html.twig index 00aa65e05..57f607077 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/create.html.twig +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/create.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% set formAction = path('marello_purchaseorder_purchaseorder_create') %} diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/createStepOne.html.twig b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/createStepOne.html.twig index b9d5134e9..0fb8c0811 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/createStepOne.html.twig +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/createStepOne.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% set formAction = path('marello_purchaseorder_purchaseorder_create') %} diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/createStepTwo.html.twig b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/createStepTwo.html.twig index f02a15714..60064859e 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/createStepTwo.html.twig +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/createStepTwo.html.twig @@ -1,5 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} -{% form_theme form with 'MarelloPurchaseOrderBundle:Form:fields.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% oro_title_set({params : {"%sku%": entity.sku|default('N/A'|trans) , "%name%": (entity.id ? entity.defaultName.string : '')|default('N/A'|trans) } }) %} diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/update.html.twig b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/update.html.twig index b7e7c1c1f..79e484efa 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/update.html.twig +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% set formAction = path('marello_purchaseorder_purchaseorder_update', { id: entity.id }) %} diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/view.html.twig b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/view.html.twig index caf1f7338..982b86fc8 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/view.html.twig +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/view.html.twig @@ -6,7 +6,7 @@ {% block navButtons %} {% if marello_purchaseorder_can_edit(entity) %} - {% if resource_granted('EDIT', entity) %} + {% if is_granted('EDIT', entity) %} {{ UI.editButton({ 'path': path('marello_purchaseorder_purchaseorder_update', {'id': entity.id}), 'entity_label': 'marello.purchaseorder.entity_label'|trans diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/widget/productsBySupplier.html.twig b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/widget/productsBySupplier.html.twig index 8ebabc850..f86fafd25 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/widget/productsBySupplier.html.twig +++ b/src/Marello/Bundle/PurchaseOrderBundle/Resources/views/PurchaseOrder/widget/productsBySupplier.html.twig @@ -1,9 +1,10 @@ +{% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} +{% import 'OroUIBundle::macros.html.twig' as UI %} +
- {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% set gridName = 'marello-purchase-order-item-candidates' %} {% set params = {} %} - {% set params = { 'supplierId': supplierId, '_parameters': { @@ -25,58 +26,17 @@ {% endblock %} - +
diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Tests/Unit/EventListener/Doctrine/PurchaseOrderOnOrderOnDemandCreationListenerTest.php b/src/Marello/Bundle/PurchaseOrderBundle/Tests/Unit/EventListener/Doctrine/PurchaseOrderOnOrderOnDemandCreationListenerTest.php index 2d968ecc0..2843e0db0 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Tests/Unit/EventListener/Doctrine/PurchaseOrderOnOrderOnDemandCreationListenerTest.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Tests/Unit/EventListener/Doctrine/PurchaseOrderOnOrderOnDemandCreationListenerTest.php @@ -173,7 +173,7 @@ private function getProduct($id) $inventoryItem = $this->getEntity( InventoryItem::class, ['id' => $id, 'orderOnDemandAllowed' => true], - [null, $product] + [$product] ); $product->addInventoryItem($inventoryItem); /** @var ProductSupplierRelation $productSupplierRelation */ diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Twig/PurchaseOrderExtension.php b/src/Marello/Bundle/PurchaseOrderBundle/Twig/PurchaseOrderExtension.php index 1998ca933..96d46e1df 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Twig/PurchaseOrderExtension.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Twig/PurchaseOrderExtension.php @@ -4,8 +4,10 @@ use Marello\Bundle\PurchaseOrderBundle\Entity\PurchaseOrder; use Oro\Bundle\WorkflowBundle\Model\WorkflowManager; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; -class PurchaseOrderExtension extends \Twig_Extension +class PurchaseOrderExtension extends AbstractExtension { const NAME = 'marello_purchaseorder'; @@ -40,7 +42,7 @@ public function getName() public function getFunctions() { return [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_purchaseorder_can_edit', [$this, 'canEdit'] ) diff --git a/src/Marello/Bundle/PurchaseOrderBundle/Workflow/Action/ReceivePurchaseOrderAction.php b/src/Marello/Bundle/PurchaseOrderBundle/Workflow/Action/ReceivePurchaseOrderAction.php index f9892fa87..b143d4f64 100644 --- a/src/Marello/Bundle/PurchaseOrderBundle/Workflow/Action/ReceivePurchaseOrderAction.php +++ b/src/Marello/Bundle/PurchaseOrderBundle/Workflow/Action/ReceivePurchaseOrderAction.php @@ -13,7 +13,6 @@ use Oro\Component\Action\Action\ActionInterface; use Oro\Component\Action\Exception\InvalidParameterException; use Oro\Component\ConfigExpression\ContextAccessor; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\PropertyAccess\PropertyPathInterface; class ReceivePurchaseOrderAction extends AbstractAction diff --git a/src/Marello/Bundle/RefundBundle/Controller/RefundController.php b/src/Marello/Bundle/RefundBundle/Controller/RefundController.php index 53f216b39..1fe09f0e1 100644 --- a/src/Marello/Bundle/RefundBundle/Controller/RefundController.php +++ b/src/Marello/Bundle/RefundBundle/Controller/RefundController.php @@ -5,17 +5,20 @@ use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\RefundBundle\Entity\Refund; use Marello\Bundle\RefundBundle\Form\Type\RefundType; - -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Symfony\Component\HttpFoundation\Request; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; -class RefundController extends Controller +class RefundController extends AbstractController { /** - * @Config\Route("/", name="marello_refund_index") - * @Config\Template + * @Route( + * path="/", + * name="marello_refund_index" + * ) + * @Template * @AclAncestor("marello_refund_view") */ public function indexAction() @@ -26,8 +29,11 @@ public function indexAction() } /** - * @Config\Route("/view/{id}", name="marello_refund_view") - * @Config\Template + * @Route( + * path="/view/{id}", + * name="marello_refund_view" + * ) + * @Template * @AclAncestor("marello_refund_view") * * @param Refund $entity @@ -40,8 +46,11 @@ public function viewAction(Refund $entity) } /** - * @Config\Route("/create/{id}", name="marello_refund_create") - * @Config\Template("MarelloRefundBundle:Refund:update.html.twig") + * @Route( + * path="/create/{id}", + * name="marello_refund_create" + * ) + * @Template("MarelloRefundBundle:Refund:update.html.twig") * @AclAncestor("marello_refund_create") * * @param Request $request @@ -58,8 +67,12 @@ public function createAction(Request $request, Order $order) /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_refund_update") - * @Config\Template + * @Route( + * path="/update/{id}", + * requirements={"id"="\d+"}, + * name="marello_refund_update" + * ) + * @Template * @AclAncestor("marello_refund_update") * * @param Request $request diff --git a/src/Marello/Bundle/RefundBundle/Entity/Refund.php b/src/Marello/Bundle/RefundBundle/Entity/Refund.php index 5398e4e71..6874d8e19 100644 --- a/src/Marello/Bundle/RefundBundle/Entity/Refund.php +++ b/src/Marello/Bundle/RefundBundle/Entity/Refund.php @@ -7,9 +7,9 @@ use Doctrine\ORM\Mapping as ORM; use Marello\Bundle\CoreBundle\DerivedProperty\DerivedPropertyAwareInterface; use Marello\Bundle\CoreBundle\Model\EntityCreatedUpdatedAtTrait; -use Marello\Bundle\LocaleBundle\Model\LocaleAwareInterface; +use Marello\Bundle\LocaleBundle\Model\LocalizationAwareInterface; use Marello\Bundle\LocaleBundle\Model\LocalizationTrait; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Entity\OrderAwareInterface; use Marello\Bundle\OrderBundle\Entity\OrderItem; @@ -54,7 +54,7 @@ class Refund extends ExtendRefund implements DerivedPropertyAwareInterface, CurrencyAwareInterface, - LocaleAwareInterface, + LocalizationAwareInterface, OrderAwareInterface { use EntityCreatedUpdatedAtTrait; @@ -99,7 +99,7 @@ class Refund extends ExtendRefund implements protected $refundAmount; /** - * @ORM\ManyToOne(targetEntity="Marello\Bundle\OrderBundle\Entity\Customer") + * @ORM\ManyToOne(targetEntity="Marello\Bundle\CustomerBundle\Entity\Customer") * @ORM\JoinColumn(nullable=false) * @Oro\ConfigField( * defaultValues={ @@ -169,7 +169,6 @@ public static function fromOrder(Order $order) ->setCustomer($order->getCustomer()) ->setOrganization($order->getOrganization()) ->setCurrency($order->getCurrency()) - ->setLocale($order->getLocale()) ->setLocalization($order->getLocalization()) ; @@ -196,7 +195,6 @@ public static function fromReturn(ReturnEntity $return) ->setCustomer($return->getOrder()->getCustomer()) ->setOrganization($return->getOrganization()) ->setCurrency($return->getOrder()->getCurrency()) - ->setLocale($return->getOrder()->getLocale()) ->setLocalization($return->getOrder()->getLocalization()) ; diff --git a/src/Marello/Bundle/RefundBundle/Form/Type/OrderItemRefundType.php b/src/Marello/Bundle/RefundBundle/Form/Type/OrderItemRefundType.php index f53453f4d..d00eddf58 100644 --- a/src/Marello/Bundle/RefundBundle/Form/Type/OrderItemRefundType.php +++ b/src/Marello/Bundle/RefundBundle/Form/Type/OrderItemRefundType.php @@ -2,15 +2,13 @@ namespace Marello\Bundle\RefundBundle\Form\Type; +use Marello\Bundle\RefundBundle\Entity\RefundItem; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\MoneyType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\Extension\Core\Type\MoneyType; -use Symfony\Component\Validator\Constraints\GreaterThan; - -use Marello\Bundle\RefundBundle\Entity\RefundItem; class OrderItemRefundType extends AbstractType { diff --git a/src/Marello/Bundle/RefundBundle/Migrations/Schema/MarelloRefundBundleInstaller.php b/src/Marello/Bundle/RefundBundle/Migrations/Schema/MarelloRefundBundleInstaller.php index 7ce5c5391..b8889a8d0 100644 --- a/src/Marello/Bundle/RefundBundle/Migrations/Schema/MarelloRefundBundleInstaller.php +++ b/src/Marello/Bundle/RefundBundle/Migrations/Schema/MarelloRefundBundleInstaller.php @@ -32,7 +32,7 @@ public function setActivityExtension(ActivityExtension $activityExtension) */ public function getMigrationVersion() { - return 'v1_2'; + return 'v1_3'; } /** @@ -70,7 +70,6 @@ protected function createMarelloRefundTable(Schema $schema) $table->addColumn('created_at', 'datetime', []); $table->addColumn('updated_at', 'datetime', ['notnull' => false]); $table->addColumn('localization_id', 'integer', ['notnull' => false]); - $table->addColumn('locale', 'string', ['notnull' => false, 'length' => 5]); $table->setPrimaryKey(['id']); $table->addUniqueIndex(['refund_number'], 'UNIQ_973FA8836E8C706D'); $table->addIndex(['customer_id'], 'IDX_973FA8839395C3F3', []); @@ -117,7 +116,7 @@ protected function addMarelloRefundForeignKeys(Schema $schema) ['onDelete' => 'SET NULL', 'onUpdate' => null] ); $table->addForeignKeyConstraint( - $schema->getTable('marello_order_customer'), + $schema->getTable('marello_customer_customer'), ['customer_id'], ['id'], ['onDelete' => null, 'onUpdate' => null] diff --git a/src/Marello/Bundle/RefundBundle/Migrations/Schema/v1_1/MarelloRefundBundle.php b/src/Marello/Bundle/RefundBundle/Migrations/Schema/v1_1/MarelloRefundBundle.php index 6beaa84d8..8428b549b 100644 --- a/src/Marello/Bundle/RefundBundle/Migrations/Schema/v1_1/MarelloRefundBundle.php +++ b/src/Marello/Bundle/RefundBundle/Migrations/Schema/v1_1/MarelloRefundBundle.php @@ -3,10 +3,8 @@ namespace Marello\Bundle\RefundBundle\Migrations\Schema\v1_1; use Doctrine\DBAL\Schema\Schema; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtension; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtensionAwareInterface; -use Oro\Bundle\MigrationBundle\Migration\QueryBag; use Oro\Bundle\MigrationBundle\Migration\Migration; +use Oro\Bundle\MigrationBundle\Migration\QueryBag; /** * @SuppressWarnings(PHPMD.TooManyMethods) diff --git a/src/Marello/Bundle/RefundBundle/Migrations/Schema/v1_3/MarelloRefundBundle.php b/src/Marello/Bundle/RefundBundle/Migrations/Schema/v1_3/MarelloRefundBundle.php new file mode 100644 index 000000000..d8052feab --- /dev/null +++ b/src/Marello/Bundle/RefundBundle/Migrations/Schema/v1_3/MarelloRefundBundle.php @@ -0,0 +1,44 @@ +getTable('marello_refund'); + $table->dropColumn('locale'); + if ($table->hasForeignKey('fk_marello_refund_customer_id')) { + $table->removeForeignKey('fk_marello_refund_customer_id'); + } + $table->addForeignKeyConstraint( + $schema->getTable('marello_customer_customer'), + ['customer_id'], + ['id'], + ['onDelete' => null, 'onUpdate' => null] + ); + + $dropLocaleInConfigSql = <<addSql( + $dropLocaleInConfigSql, + ['field_name' => 'locale', 'class_name' => Refund::class], + ['field_name' => Type::STRING, 'class_name' => Type::STRING] + ); + $queries->addPostQuery($dropLocaleInConfigQuery); + } +} diff --git a/src/Marello/Bundle/RefundBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/RefundBundle/Resources/config/oro/datagrids.yml index c66096440..9a164f784 100644 --- a/src/Marello/Bundle/RefundBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/RefundBundle/Resources/config/oro/datagrids.yml @@ -15,7 +15,7 @@ datagrids: label: marello.refund.refund_number.label frontend_type: string customer: - label: marello.order.customer.entity_label + label: marello.customer.entity_label frontend_type: string orderNumber: data_name: order.orderNumber @@ -52,7 +52,7 @@ datagrids: updatedAt: data_name: r.updatedAt default: - refundNumber: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC" + refundNumber: "DESC" filters: columns: refundNumber: @@ -155,7 +155,7 @@ datagrids: updatedAt: data_name: r.updatedAt default: - refundNumber: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC" + refundNumber: "DESC" filters: columns: refundNumber: diff --git a/src/Marello/Bundle/RefundBundle/Resources/config/services.yml b/src/Marello/Bundle/RefundBundle/Resources/config/services.yml index bbe516ef0..e25428471 100644 --- a/src/Marello/Bundle/RefundBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/RefundBundle/Resources/config/services.yml @@ -11,8 +11,7 @@ services: class: Marello\Bundle\RefundBundle\Twig\RefundExtension arguments: - '@oro_workflow.manager' - calls: - - ['setRefundBalanceCalculator', ['@marello_refund.calculator.refund_balance']] + - '@marello_refund.calculator.refund_balance' tags: - { name: twig.extension } @@ -32,6 +31,7 @@ services: marello_refund.datagrid.action_permission_provider: class: 'Marello\Bundle\RefundBundle\Datagrid\RefundActionPermissionProvider' + public: true arguments: - '@doctrine.orm.entity_manager' diff --git a/src/Marello/Bundle/RefundBundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/RefundBundle/Resources/translations/messages.en.yml index 7c6202661..e898a3355 100644 --- a/src/Marello/Bundle/RefundBundle/Resources/translations/messages.en.yml +++ b/src/Marello/Bundle/RefundBundle/Resources/translations/messages.en.yml @@ -11,7 +11,6 @@ marello: additional_items.label: Additional Items original_order.label: Original Order currency.label: Currency - locale.label: Locale customer.label: Customer items.label: Items localization.label: Localization @@ -24,11 +23,11 @@ marello: base_amount.label: Base Amount id.label: Id name.label: Name - quantity.label: Quantity - refund_amount.label: Refund Amount - quantity_ordered.label: Ordered Quantity order_item.label: Order Item refund.label: Refund + quantity_ordered.label: Ordered Quantity + quantity.label: Refunded Quantity + refund_amount.label: Refunded Amount organization.label: Organization form: price.label: Price (incl VAT) diff --git a/src/Marello/Bundle/RefundBundle/Resources/views/Form/fields.html.twig b/src/Marello/Bundle/RefundBundle/Resources/views/Form/fields.html.twig index 30d2f3e0e..d24c66cde 100644 --- a/src/Marello/Bundle/RefundBundle/Resources/views/Form/fields.html.twig +++ b/src/Marello/Bundle/RefundBundle/Resources/views/Form/fields.html.twig @@ -53,7 +53,7 @@ {% endblock %} -{% macro marello_order_item_refund_prototype(widget) %} +{% macro marello_order_item_refund_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -72,13 +72,13 @@ {% set page_component_options = { 'disabled': not allow_delete } %} - {{ form_widget(form) }} {% endmacro %} -{% macro marello_additional_refund_prototype(widget) %} +{% macro marello_additional_refund_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -97,7 +97,7 @@ {% set page_component_options = { 'disabled': not allow_delete } %} - {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_order_item_refund_prototype(child) }} + {{ fields.marello_order_item_refund_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} @@ -157,9 +160,12 @@ {% endblock %} {% block marello_additional_refund_collection_widget %} + {% import _self as fields %} + {% spaceless %} + {% set widgetContainerAttributes = block('widget_container_attributes') %} {% if prototype is defined %} - {% set prototype_html = _self.marello_additional_refund_prototype(form) %} + {% set prototype_html = fields.marello_additional_refund_prototype(form, widgetContainerAttributes) %} {% endif %} {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'marello-item-collection grid-container' }) %} {% set id = id ~ '_collection' %} @@ -180,7 +186,7 @@ {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_additional_refund_prototype(child) }} + {{ fields.marello_additional_refund_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} diff --git a/src/Marello/Bundle/RefundBundle/Resources/views/Refund/create.html.twig b/src/Marello/Bundle/RefundBundle/Resources/views/Refund/create.html.twig index de2f9c50b..5a109ffb1 100644 --- a/src/Marello/Bundle/RefundBundle/Resources/views/Refund/create.html.twig +++ b/src/Marello/Bundle/RefundBundle/Resources/views/Refund/create.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% set formAction = path('marello_refund_create') %} @@ -6,7 +7,7 @@ {% block navButtons %} {{ UI.cancelButton(path('marello_refund_index')) }} {% set html = UI.saveAndCloseButton() %} - {% if resource_granted('inventory.vehicle_update') %} + {% if is_granted('inventory.vehicle_update') %} {% set html = html ~ UI.saveAndStayButton() %} {% endif %} {{ UI.dropdownSaveButton({ 'html': html }) }} diff --git a/src/Marello/Bundle/RefundBundle/Resources/views/Refund/update.html.twig b/src/Marello/Bundle/RefundBundle/Resources/views/Refund/update.html.twig index 1246395ce..41c656777 100644 --- a/src/Marello/Bundle/RefundBundle/Resources/views/Refund/update.html.twig +++ b/src/Marello/Bundle/RefundBundle/Resources/views/Refund/update.html.twig @@ -11,7 +11,7 @@ {% block navButtons %} {{ UI.cancelButton(path('marello_refund_index')) }} {% set html = UI.saveAndCloseButton() %} - {% if resource_granted('marello_refund_update') %} + {% if is_granted('marello_refund_update') %} {% set html = html ~ UI.saveAndStayButton() %} {% endif %} {{ UI.dropdownSaveButton({ 'html': html }) }} diff --git a/src/Marello/Bundle/RefundBundle/Resources/views/Refund/view.html.twig b/src/Marello/Bundle/RefundBundle/Resources/views/Refund/view.html.twig index da8f0a8ef..b5145d7d2 100644 --- a/src/Marello/Bundle/RefundBundle/Resources/views/Refund/view.html.twig +++ b/src/Marello/Bundle/RefundBundle/Resources/views/Refund/view.html.twig @@ -3,7 +3,7 @@ {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% block navButtons %} - {% if resource_granted('marello_refund_update') and marello_refund_is_pending(entity) %} + {% if is_granted('marello_refund_update') and marello_refund_is_pending(entity) %} {{ UI.editButton({ 'path' : path('marello_refund_update', { id: entity.id }), 'entity_label': 'marello.refund.entity_label'|trans diff --git a/src/Marello/Bundle/RefundBundle/Tests/Functional/DataFixtures/LoadRefundData.php b/src/Marello/Bundle/RefundBundle/Tests/Functional/DataFixtures/LoadRefundData.php index bb08c550b..321e51eae 100644 --- a/src/Marello/Bundle/RefundBundle/Tests/Functional/DataFixtures/LoadRefundData.php +++ b/src/Marello/Bundle/RefundBundle/Tests/Functional/DataFixtures/LoadRefundData.php @@ -5,12 +5,10 @@ use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Common\Persistence\ObjectManager; - -use Marello\Bundle\OrderBundle\Entity\Order; -use Marello\Bundle\RefundBundle\Entity\Refund; use Marello\Bundle\OrderBundle\Entity\OrderItem; -use Marello\Bundle\RefundBundle\Entity\RefundItem; use Marello\Bundle\OrderBundle\Tests\Functional\DataFixtures\LoadOrderData; +use Marello\Bundle\RefundBundle\Entity\Refund; +use Marello\Bundle\RefundBundle\Entity\RefundItem; class LoadRefundData extends AbstractFixture implements DependentFixtureInterface { diff --git a/src/Marello/Bundle/RefundBundle/Twig/RefundExtension.php b/src/Marello/Bundle/RefundBundle/Twig/RefundExtension.php index 00946a723..06d97af88 100644 --- a/src/Marello/Bundle/RefundBundle/Twig/RefundExtension.php +++ b/src/Marello/Bundle/RefundBundle/Twig/RefundExtension.php @@ -5,8 +5,10 @@ use Marello\Bundle\RefundBundle\Calculator\RefundBalanceCalculator; use Marello\Bundle\RefundBundle\Entity\Refund; use Oro\Bundle\WorkflowBundle\Model\WorkflowManager; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; -class RefundExtension extends \Twig_Extension +class RefundExtension extends AbstractExtension { const NAME = 'marello_refund'; @@ -21,11 +23,16 @@ class RefundExtension extends \Twig_Extension protected $refundBalanceCalculator; /** + * RefundExtension constructor. * @param WorkflowManager $workflowManager + * @param RefundBalanceCalculator $balanceCalculator */ - public function __construct(WorkflowManager $workflowManager) - { + public function __construct( + WorkflowManager $workflowManager, + RefundBalanceCalculator $balanceCalculator + ) { $this->workflowManager = $workflowManager; + $this->refundBalanceCalculator = $balanceCalculator; } /** @@ -46,11 +53,11 @@ public function getName() public function getFunctions() { return [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_refund_is_pending', [$this, 'isPending'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_refund_get_balance', [$this, 'getBalance'] ), @@ -81,18 +88,4 @@ public function getBalance(Refund $refund) { return $this->refundBalanceCalculator->caclulateBalance($refund); } - - /** - * Add refund calculator via call method on service in order to keep BC - * should be removed for 3.0 - * @deprecated remove in 3.0 - * @param RefundBalanceCalculator $balanceCalculator - * @return $this - */ - public function setRefundBalanceCalculator(RefundBalanceCalculator $balanceCalculator) - { - $this->refundBalanceCalculator = $balanceCalculator; - - return $this; - } } diff --git a/src/Marello/Bundle/ReportBundle/Controller/ReportController.php b/src/Marello/Bundle/ReportBundle/Controller/ReportController.php index a282b43a5..3e16950ae 100644 --- a/src/Marello/Bundle/ReportBundle/Controller/ReportController.php +++ b/src/Marello/Bundle/ReportBundle/Controller/ReportController.php @@ -2,21 +2,21 @@ namespace Marello\Bundle\ReportBundle\Controller; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Routing\Annotation\Route; -class ReportController extends Controller +class ReportController extends AbstractController { /** - * @Config\Route( - * "/static/{reportGroupName}/{reportName}/{_format}", + * @Route( + * path="/static/{reportGroupName}/{reportName}/{_format}", * name="marello_report_index", * requirements={"reportGroupName"="\w+", "reportName"="\w+", "_format"="html|json"}, * defaults={"_format" = "html"} * ) - * @Config\Template + * @Template * @AclAncestor("oro_report_view") * * @param string $reportGroupName diff --git a/src/Marello/Bundle/ReportBundle/DependencyInjection/Configuration.php b/src/Marello/Bundle/ReportBundle/DependencyInjection/Configuration.php index a7aec9923..e9878ed52 100644 --- a/src/Marello/Bundle/ReportBundle/DependencyInjection/Configuration.php +++ b/src/Marello/Bundle/ReportBundle/DependencyInjection/Configuration.php @@ -13,7 +13,7 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('marello_report'); + $treeBuilder->root('marello_report'); // Here you should define the parameters that are allowed to // configure your bundle. See the documentation linked above for diff --git a/src/Marello/Bundle/ReportBundle/DependencyInjection/MarelloReportExtension.php b/src/Marello/Bundle/ReportBundle/DependencyInjection/MarelloReportExtension.php index 5f0d5f36b..dde8f52df 100644 --- a/src/Marello/Bundle/ReportBundle/DependencyInjection/MarelloReportExtension.php +++ b/src/Marello/Bundle/ReportBundle/DependencyInjection/MarelloReportExtension.php @@ -20,7 +20,7 @@ class MarelloReportExtension extends Extension public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); + $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); diff --git a/src/Marello/Bundle/ReportBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/ReportBundle/Resources/config/oro/datagrids.yml index f4f889062..680f51a55 100644 --- a/src/Marello/Bundle/ReportBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/ReportBundle/Resources/config/oro/datagrids.yml @@ -36,7 +36,7 @@ datagrids: totalOrders: { data_name: totalOrders } totalRevenue: { data_name: totalRevenue } default: - totalRevenue: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' + totalRevenue: 'DESC' options: entityHint: report data export: false @@ -49,7 +49,7 @@ datagrids: query: select: - i.id - - p.name as productName + - p.denormalizedDefaultName as productName - p.sku as productSku - s.label as status - COALESCE(SUM(il.inventory), 0) AS inventoryQty @@ -66,7 +66,7 @@ datagrids: label: marello.product.sku.label frontend_type: string productName: - label: marello.product.name.label + label: marello.product.names.label frontend_type: string inventoryQty: label: marello.inventory.inventorylevel.inventory.label @@ -78,11 +78,11 @@ datagrids: sorters: columns: productSku: { data_name: p.sku } - productName: { data_name: p.name } + productName: { data_name: p.denormalizedDefaultName } inventoryQty: { data_name: inventoryQty } status: { data_name: s.label } default: - inventoryQty: %oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC + inventoryQty: 'ASC' options: entityHint: report data export: false @@ -125,7 +125,7 @@ datagrids: createdAt: { data_name: createdAt } quantitySold: { data_name: quantitySold } default: - quantitySold: %oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC + quantitySold: 'DESC' options: entityHint: report data export: false @@ -173,7 +173,7 @@ datagrids: createdAt: { data_name: createdAt } quantitySold: { data_name: quantitySold } default: - quantitySold: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC' + quantitySold: 'ASC' options: entityHint: report data export: false @@ -222,7 +222,7 @@ datagrids: returnReason: { data_name: returnReason } percentageReturned: { data_name: percentageReturned } default: - productSku: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC' + productSku: 'ASC' filters: columns: productSku: @@ -286,7 +286,7 @@ datagrids: quantityOrdered: { data_name: quantityOrdered } percentageReturned: { data_name: percentageReturned } default: - productSku: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC' + productSku: 'ASC' filters: columns: productSku: diff --git a/src/Marello/Bundle/ReportBundle/Resources/config/services.yml b/src/Marello/Bundle/ReportBundle/Resources/config/services.yml index 3a4b8c013..b3bcc6ff4 100644 --- a/src/Marello/Bundle/ReportBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/ReportBundle/Resources/config/services.yml @@ -1,6 +1,6 @@ services: marello_report.listener.datagrid.workflow_grid_listener: - class: '%marello_datagrid.event_listener.datagrid.workflow_grid_listener.class%' + class: 'Marello\Bundle\DataGridBundle\EventListener\Datagrid\WorkflowGridListener' tags: - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.after.marello_report-orders-revenue_per_sales_channel, method: removeWorkflow } diff --git a/src/Marello/Bundle/ReturnBundle/Controller/ReturnController.php b/src/Marello/Bundle/ReturnBundle/Controller/ReturnController.php index ba9dfef10..79e558534 100644 --- a/src/Marello/Bundle/ReturnBundle/Controller/ReturnController.php +++ b/src/Marello/Bundle/ReturnBundle/Controller/ReturnController.php @@ -7,16 +7,20 @@ use Marello\Bundle\ReturnBundle\Form\Type\ReturnType; use Marello\Bundle\ReturnBundle\Form\Type\ReturnUpdateType; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Oro\Bundle\SecurityBundle\Exception\ForbiddenException; +use Symfony\Component\Routing\Annotation\Route; -class ReturnController extends Controller +class ReturnController extends AbstractController { /** - * @Config\Route("/", name="marello_return_return_index") - * @Config\Template + * @Route( + * path="/", + * name="marello_return_return_index" + * ) + * @Template * @AclAncestor("marello_return_view") */ public function indexAction() @@ -25,8 +29,12 @@ public function indexAction() } /** - * @Config\Route("/create/{id}", requirements={"id"="\d+"}, name="marello_return_return_create") - * @Config\Template + * @Route( + * path="/create/{id}", + * requirements={"id"="\d+"}, + * name="marello_return_return_create" + * ) + * @Template * @AclAncestor("marello_return_create") * * @param Order $order @@ -81,8 +89,12 @@ public function createAction(Order $order, Request $request) } /** - * @Config\Route("/view/{id}", requirements={"id"="\d+"}, name="marello_return_return_view") - * @Config\Template + * @Route( + * path="/view/{id}", + * requirements={"id"="\d+"}, + * name="marello_return_return_view" + * ) + * @Template * @AclAncestor("marello_return_view") * * @param ReturnEntity $return @@ -95,8 +107,12 @@ public function viewAction(ReturnEntity $return) } /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_return_return_update") - * @Config\Template + * @Route( + * path="/update/{id}", + * requirements={"id"="\d+"}, + * name="marello_return_return_update" + * ) + * @Template * @AclAncestor("marello_return_update") * * @param ReturnEntity $return diff --git a/src/Marello/Bundle/ReturnBundle/Entity/Repository/ReturnItemRepository.php b/src/Marello/Bundle/ReturnBundle/Entity/Repository/ReturnItemRepository.php index c3edc64bc..d97e505ed 100644 --- a/src/Marello/Bundle/ReturnBundle/Entity/Repository/ReturnItemRepository.php +++ b/src/Marello/Bundle/ReturnBundle/Entity/Repository/ReturnItemRepository.php @@ -3,10 +3,6 @@ namespace Marello\Bundle\ReturnBundle\Entity\Repository; use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\Query\Expr\Join; -use Doctrine\ORM\Query\ResultSetMapping; -use Marello\Bundle\OrderBundle\Entity\OrderItem; -use Marello\Bundle\ReturnBundle\Entity\ReturnItem; class ReturnItemRepository extends EntityRepository { diff --git a/src/Marello/Bundle/ReturnBundle/Entity/ReturnEntity.php b/src/Marello/Bundle/ReturnBundle/Entity/ReturnEntity.php index 294f57ddc..a172558f7 100644 --- a/src/Marello/Bundle/ReturnBundle/Entity/ReturnEntity.php +++ b/src/Marello/Bundle/ReturnBundle/Entity/ReturnEntity.php @@ -6,7 +6,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Marello\Bundle\CoreBundle\DerivedProperty\DerivedPropertyAwareInterface; -use Marello\Bundle\LocaleBundle\Model\LocaleAwareInterface; +use Marello\Bundle\LocaleBundle\Model\LocalizationAwareInterface; use Marello\Bundle\LocaleBundle\Model\LocalizationTrait; use Marello\Bundle\CoreBundle\Model\EntityCreatedUpdatedAtTrait; use Marello\Bundle\OrderBundle\Entity\Order; @@ -14,6 +14,7 @@ use Marello\Bundle\ReturnBundle\Model\ExtendReturnEntity; use Marello\Bundle\SalesBundle\Entity\SalesChannel; use Marello\Bundle\ShippingBundle\Entity\HasShipmentTrait; +use Marello\Bundle\SalesBundle\Model\SalesChannelAwareInterface; use Marello\Bundle\ShippingBundle\Integration\ShippingAwareInterface; use Oro\Bundle\EntityConfigBundle\Metadata\Annotation as Oro; use Oro\Bundle\OrganizationBundle\Entity\OrganizationAwareInterface; @@ -52,9 +53,10 @@ class ReturnEntity extends ExtendReturnEntity implements DerivedPropertyAwareInterface, ShippingAwareInterface, - LocaleAwareInterface, + LocalizationAwareInterface, OrganizationAwareInterface, - OrderAwareInterface + OrderAwareInterface, + SalesChannelAwareInterface { use HasShipmentTrait; use LocalizationTrait; @@ -197,7 +199,6 @@ public function setOrder(Order $order) { $this->order = $order; $this->organization = $order->getOrganization(); - $this->locale= $order->getLocale(); $this->localization = $order->getLocalization(); return $this; diff --git a/src/Marello/Bundle/ReturnBundle/Form/EventListener/ReturnItemTypeSubscriber.php b/src/Marello/Bundle/ReturnBundle/Form/EventListener/ReturnItemTypeSubscriber.php index 174045c21..684fb591e 100644 --- a/src/Marello/Bundle/ReturnBundle/Form/EventListener/ReturnItemTypeSubscriber.php +++ b/src/Marello/Bundle/ReturnBundle/Form/EventListener/ReturnItemTypeSubscriber.php @@ -3,8 +3,6 @@ namespace Marello\Bundle\ReturnBundle\Form\EventListener; use Doctrine\ORM\EntityManager; -use Marello\Bundle\OrderBundle\Entity\OrderItem; -use Marello\Bundle\ReturnBundle\Entity\ReturnEntity; use Marello\Bundle\ReturnBundle\Entity\ReturnItem; use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper; use Symfony\Component\EventDispatcher\EventSubscriberInterface; diff --git a/src/Marello/Bundle/ReturnBundle/Form/EventListener/ReturnTypeSubscriber.php b/src/Marello/Bundle/ReturnBundle/Form/EventListener/ReturnTypeSubscriber.php index 916b6d3aa..e2f7fa6e9 100644 --- a/src/Marello/Bundle/ReturnBundle/Form/EventListener/ReturnTypeSubscriber.php +++ b/src/Marello/Bundle/ReturnBundle/Form/EventListener/ReturnTypeSubscriber.php @@ -35,7 +35,8 @@ public function onPreSetData(FormEvent $event) ->getItems() ->map(function (OrderItem $orderItem) use ($return) { $status = $orderItem->getStatus(); - if (in_array($status, [LoadOrderItemStatusData::DROPSHIPPING, LoadOrderItemStatusData::SHIPPED])) { + $statuses = [LoadOrderItemStatusData::DROPSHIPPING, LoadOrderItemStatusData::SHIPPED]; + if (in_array($status->getId(), $statuses)) { $return->addReturnItem(new ReturnItem($orderItem)); } }); diff --git a/src/Marello/Bundle/ReturnBundle/Manager/BusinessRuleManager.php b/src/Marello/Bundle/ReturnBundle/Manager/BusinessRuleManager.php index de7e2c8cc..bb88a703d 100644 --- a/src/Marello/Bundle/ReturnBundle/Manager/BusinessRuleManager.php +++ b/src/Marello/Bundle/ReturnBundle/Manager/BusinessRuleManager.php @@ -2,9 +2,6 @@ namespace Marello\Bundle\ReturnBundle\Manager; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Doctrine\Common\Collections\ArrayCollection; - class BusinessRuleManager { /** @var ReturnBusinessRuleRegistry $businessRuleregistry */ diff --git a/src/Marello/Bundle/ReturnBundle/Manager/Rules/MarelloProductWarranty.php b/src/Marello/Bundle/ReturnBundle/Manager/Rules/MarelloProductWarranty.php index 80a14273c..7c33e1dbc 100644 --- a/src/Marello/Bundle/ReturnBundle/Manager/Rules/MarelloProductWarranty.php +++ b/src/Marello/Bundle/ReturnBundle/Manager/Rules/MarelloProductWarranty.php @@ -60,6 +60,8 @@ public function applyRule($entity) $this->updateStatus(self::RETURN_ITEM_STATUS_AUTHORIZED, $returnItem); } }); + + return $this; } /** diff --git a/src/Marello/Bundle/ReturnBundle/Manager/Rules/MarelloRorWarranty.php b/src/Marello/Bundle/ReturnBundle/Manager/Rules/MarelloRorWarranty.php index e24e4343a..4ed12e210 100644 --- a/src/Marello/Bundle/ReturnBundle/Manager/Rules/MarelloRorWarranty.php +++ b/src/Marello/Bundle/ReturnBundle/Manager/Rules/MarelloRorWarranty.php @@ -60,6 +60,8 @@ public function applyRule($entity) $this->updateStatus(self::RETURN_ITEM_STATUS_AUTHORIZED, $returnItem); } }); + + return $this; } /** diff --git a/src/Marello/Bundle/ReturnBundle/Migrations/Schema/MarelloReturnBundleInstaller.php b/src/Marello/Bundle/ReturnBundle/Migrations/Schema/MarelloReturnBundleInstaller.php index c3c56ecfd..908635b4e 100644 --- a/src/Marello/Bundle/ReturnBundle/Migrations/Schema/MarelloReturnBundleInstaller.php +++ b/src/Marello/Bundle/ReturnBundle/Migrations/Schema/MarelloReturnBundleInstaller.php @@ -31,7 +31,7 @@ class MarelloReturnBundleInstaller implements */ public function getMigrationVersion() { - return 'v1_3'; + return 'v1_4'; } /** @@ -113,7 +113,6 @@ protected function createMarelloReturnReturnTable(Schema $schema) $table->addColumn('updated_at', 'datetime', ['notnull' => false]); $table->addColumn('sales_channel_name', 'string', ['length' => 255]); $table->addColumn('sales_channel_id', 'integer', ['notnull' => false]); - $table->addColumn('locale', 'string', ['notnull' => false, 'length' => 5]); $table->addColumn('localization_id', 'integer', ['notnull' => false]); $table->addColumn('shipment_id', 'integer', ['notnull' => false]); $table->addColumn('return_reference', 'string', ['notnull' => false, 'length' => 255]); diff --git a/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_1/MarelloReturnBundle.php b/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_1/MarelloReturnBundle.php index 6af0bea44..06cbe8f3d 100644 --- a/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_1/MarelloReturnBundle.php +++ b/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_1/MarelloReturnBundle.php @@ -3,13 +3,8 @@ namespace Marello\Bundle\ReturnBundle\Migrations\Schema\v1_1; use Doctrine\DBAL\Schema\Schema; -use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope; -use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension; -use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface; use Oro\Bundle\MigrationBundle\Migration\Migration; use Oro\Bundle\MigrationBundle\Migration\QueryBag; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtension; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtensionAwareInterface; /** * @SuppressWarnings(PHPMD.TooManyMethods) diff --git a/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_2/MarelloReturnBundle.php b/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_2/MarelloReturnBundle.php index f62578008..8c0fedced 100644 --- a/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_2/MarelloReturnBundle.php +++ b/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_2/MarelloReturnBundle.php @@ -3,13 +3,8 @@ namespace Marello\Bundle\ReturnBundle\Migrations\Schema\v1_2; use Doctrine\DBAL\Schema\Schema; -use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope; -use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension; -use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface; use Oro\Bundle\MigrationBundle\Migration\Migration; use Oro\Bundle\MigrationBundle\Migration\QueryBag; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtension; -use Oro\Bundle\ActivityBundle\Migration\Extension\ActivityExtensionAwareInterface; /** * @SuppressWarnings(PHPMD.TooManyMethods) diff --git a/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_4/MarelloReturnBundle.php b/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_4/MarelloReturnBundle.php new file mode 100644 index 000000000..fbbe38756 --- /dev/null +++ b/src/Marello/Bundle/ReturnBundle/Migrations/Schema/v1_4/MarelloReturnBundle.php @@ -0,0 +1,35 @@ +getTable('marello_return_return'); + $table->dropColumn('locale'); + + $dropLocaleInConfigSql = <<addSql( + $dropLocaleInConfigSql, + ['field_name' => 'locale', 'class_name' => ReturnEntity::class], + ['field_name' => Type::STRING, 'class_name' => Type::STRING] + ); + $queries->addPostQuery($dropLocaleInConfigQuery); + } +} diff --git a/src/Marello/Bundle/ReturnBundle/Resources/config/oro/api.yml b/src/Marello/Bundle/ReturnBundle/Resources/config/oro/api.yml index e44efd173..b97b7c467 100644 --- a/src/Marello/Bundle/ReturnBundle/Resources/config/oro/api.yml +++ b/src/Marello/Bundle/ReturnBundle/Resources/config/oro/api.yml @@ -30,8 +30,6 @@ api: exclude: true salesChannelName: exclude: true - locale: - exclude: true localization: exclude: true update: false diff --git a/src/Marello/Bundle/ReturnBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/ReturnBundle/Resources/config/oro/datagrids.yml index 86405de62..831b1f6e8 100644 --- a/src/Marello/Bundle/ReturnBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/ReturnBundle/Resources/config/oro/datagrids.yml @@ -69,7 +69,7 @@ datagrids: updatedAt: data_name: r.updatedAt default: - returnNumber: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' + returnNumber: 'DESC' properties: view_link: type: url @@ -161,7 +161,7 @@ datagrids: updatedAt: data_name: r.updatedAt default: - returnNumber: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_DESC' + returnNumber: 'DESC' properties: view_link: type: url diff --git a/src/Marello/Bundle/ReturnBundle/Resources/config/services.yml b/src/Marello/Bundle/ReturnBundle/Resources/config/services.yml index 218c2b62f..3606e8e56 100644 --- a/src/Marello/Bundle/ReturnBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/ReturnBundle/Resources/config/services.yml @@ -1,6 +1,3 @@ -parameters: - marello_return.entity.return.class: Marello\Bundle\ReturnBundle\Entity\ReturnEntity - services: marello_return.util.return_helper: class: Marello\Bundle\ReturnBundle\Util\ReturnHelper @@ -31,6 +28,7 @@ services: - '@service_container' marello_return.manager.businessrule_manager: + public: true class: Marello\Bundle\ReturnBundle\Manager\BusinessRuleManager arguments: - '@marello_return.manager.return_businessrule_registry' diff --git a/src/Marello/Bundle/ReturnBundle/Resources/config/shipping.yml b/src/Marello/Bundle/ReturnBundle/Resources/config/shipping.yml index fb4b003d1..19bb6de0c 100644 --- a/src/Marello/Bundle/ReturnBundle/Resources/config/shipping.yml +++ b/src/Marello/Bundle/ReturnBundle/Resources/config/shipping.yml @@ -4,4 +4,4 @@ services: arguments: - '@doctrine.orm.entity_manager' tags: - - { name: marello.shipping.data_provider, class: '%marello_return.entity.return.class%' } + - { name: marello.shipping.data_provider, class: 'Marello\Bundle\ReturnBundle\Entity\ReturnEntity' } diff --git a/src/Marello/Bundle/ReturnBundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/ReturnBundle/Resources/translations/messages.en.yml index 4e98e2359..5d54365e5 100644 --- a/src/Marello/Bundle/ReturnBundle/Resources/translations/messages.en.yml +++ b/src/Marello/Bundle/ReturnBundle/Resources/translations/messages.en.yml @@ -10,7 +10,6 @@ marello: return_items.label: Returned items workflow_item.label: Workflow Item workflow_step.label: Workflow Step - locale.label: Locale sales_channel_name.label: Sales Channel Name localization.label: Localization organization.label: Organization diff --git a/src/Marello/Bundle/ReturnBundle/Resources/views/Form/fields.html.twig b/src/Marello/Bundle/ReturnBundle/Resources/views/Form/fields.html.twig index 23dd1a798..a65253df5 100644 --- a/src/Marello/Bundle/ReturnBundle/Resources/views/Form/fields.html.twig +++ b/src/Marello/Bundle/ReturnBundle/Resources/views/Form/fields.html.twig @@ -40,7 +40,7 @@ {% endblock %} -{% macro marello_return_item_collection_item_prototype(widget) %} +{% macro marello_return_item_collection_item_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -52,7 +52,7 @@ {% set disabled = widget.parent.vars.disabled %} {% endif %} - {{ form_widget(form) }} @@ -63,9 +63,12 @@ {% endmacro %} {% block marello_return_item_collection_widget %} + {% import _self as fields %} + {% spaceless %} + {% set widgetContainerAttributes = block('widget_container_attributes') %} {% if prototype is defined %} - {% set prototype_html = _self.marello_return_item_collection_item_prototype(form) %} + {% set prototype_html = fields.marello_return_item_collection_item_prototype(form, widgetContainerAttributes) %} {% endif %} {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'marello-item-collection grid-container' }) %} {% set id = id ~ '_collection' %} @@ -88,7 +91,7 @@ {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_return_item_collection_item_prototype(child) }} + {{ fields.marello_return_item_collection_item_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {{ prototype_html|replace({(prototype_name): '0'})|raw }} diff --git a/src/Marello/Bundle/ReturnBundle/Resources/views/Return/create.html.twig b/src/Marello/Bundle/ReturnBundle/Resources/views/Return/create.html.twig index 773317c62..57b599a00 100644 --- a/src/Marello/Bundle/ReturnBundle/Resources/views/Return/create.html.twig +++ b/src/Marello/Bundle/ReturnBundle/Resources/views/Return/create.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% set order = form.vars.value.order %} diff --git a/src/Marello/Bundle/ReturnBundle/Resources/views/Return/update.html.twig b/src/Marello/Bundle/ReturnBundle/Resources/views/Return/update.html.twig index 88294492e..1d709cb9e 100644 --- a/src/Marello/Bundle/ReturnBundle/Resources/views/Return/update.html.twig +++ b/src/Marello/Bundle/ReturnBundle/Resources/views/Return/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% oro_title_set({params : {"%id%": form.vars.value.id }}) %} @@ -10,7 +11,7 @@ {{ UI.cancelButton(path('marello_return_return_view', {id: form.vars.value.id })) }} {% set html = UI.saveAndCloseButton() %} - {% if form.vars.value.id or resource_granted('marello_return_update') %} + {% if form.vars.value.id or is_granted('marello_return_update') %} {% set html = html ~ UI.saveAndStayButton() %} {% endif %} {{ UI.dropdownSaveButton({'html': html}) }} diff --git a/src/Marello/Bundle/ReturnBundle/Resources/views/Return/view.html.twig b/src/Marello/Bundle/ReturnBundle/Resources/views/Return/view.html.twig index a51143651..f55fdf651 100644 --- a/src/Marello/Bundle/ReturnBundle/Resources/views/Return/view.html.twig +++ b/src/Marello/Bundle/ReturnBundle/Resources/views/Return/view.html.twig @@ -16,7 +16,7 @@ {% endblock pageHeader %} {% block navButtons %} - {% if resource_granted('EDIT', entity) and marello_return_is_on_hold(entity) %} + {% if is_granted('EDIT', entity) and marello_return_is_on_hold(entity) %} {{ UI.buttonSeparator() }} {{ UI.editButton({ 'path': path('marello_return_return_update', {'id': entity.id}), diff --git a/src/Marello/Bundle/ReturnBundle/Tests/Functional/Api/responses/cget_return_list.yml b/src/Marello/Bundle/ReturnBundle/Tests/Functional/Api/responses/cget_return_list.yml index 3edd104a8..219e5ad32 100644 --- a/src/Marello/Bundle/ReturnBundle/Tests/Functional/Api/responses/cget_return_list.yml +++ b/src/Marello/Bundle/ReturnBundle/Tests/Functional/Api/responses/cget_return_list.yml @@ -6,7 +6,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return0->returnReference' - locale: null workflowItem: currentStep: name: pending @@ -49,7 +48,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return1->returnReference' - locale: null workflowItem: currentStep: name: pending @@ -89,7 +87,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return2->returnReference' - locale: null workflowItem: currentStep: name: pending @@ -132,7 +129,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return3->returnReference' - locale: null workflowItem: currentStep: name: pending @@ -166,7 +162,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return4->returnReference' - locale: null workflowItem: currentStep: name: pending @@ -203,7 +198,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return5->returnReference' - locale: null workflowItem: currentStep: name: pending @@ -246,7 +240,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return6->returnReference' - locale: null workflowItem: currentStep: name: pending @@ -280,7 +273,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return7->returnReference' - locale: null workflowItem: currentStep: name: pending @@ -317,7 +309,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return8->returnReference' - locale: null workflowItem: currentStep: name: pending diff --git a/src/Marello/Bundle/ReturnBundle/Tests/Functional/Api/responses/get_return_by_id.yml b/src/Marello/Bundle/ReturnBundle/Tests/Functional/Api/responses/get_return_by_id.yml index eff1724a1..664f6cf63 100644 --- a/src/Marello/Bundle/ReturnBundle/Tests/Functional/Api/responses/get_return_by_id.yml +++ b/src/Marello/Bundle/ReturnBundle/Tests/Functional/Api/responses/get_return_by_id.yml @@ -5,7 +5,6 @@ data: returnNumber: 'returnNumber)>' salesChannelName: channel1 returnReference: '@return1->returnReference' - locale: null workflowItem: currentStep: name: pending diff --git a/src/Marello/Bundle/ReturnBundle/Twig/ReturnExtension.php b/src/Marello/Bundle/ReturnBundle/Twig/ReturnExtension.php index b207f8218..e707d077b 100644 --- a/src/Marello/Bundle/ReturnBundle/Twig/ReturnExtension.php +++ b/src/Marello/Bundle/ReturnBundle/Twig/ReturnExtension.php @@ -6,8 +6,10 @@ use Marello\Bundle\ReturnBundle\Entity\ReturnEntity; use Marello\Bundle\ReturnBundle\Util\ReturnHelper; use Oro\Bundle\WorkflowBundle\Model\WorkflowManager; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; -class ReturnExtension extends \Twig_Extension +class ReturnExtension extends AbstractExtension { const NAME = 'marello_return'; @@ -47,11 +49,11 @@ public function getName() public function getFunctions() { return [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_return_get_order_item_returned_quantity', [$this, 'getOrderItemReturnedQuantity'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_return_is_on_hold', [$this, 'isOnHold'] ), diff --git a/src/Marello/Bundle/ReturnBundle/Workflow/InspectionAction.php b/src/Marello/Bundle/ReturnBundle/Workflow/InspectionAction.php index 8ebe8d027..6ccb9e1fd 100644 --- a/src/Marello/Bundle/ReturnBundle/Workflow/InspectionAction.php +++ b/src/Marello/Bundle/ReturnBundle/Workflow/InspectionAction.php @@ -2,19 +2,16 @@ namespace Marello\Bundle\ReturnBundle\Workflow; +use Marello\Bundle\InventoryBundle\Event\InventoryUpdateEvent; use Marello\Bundle\InventoryBundle\Model\InventoryUpdateContextFactory; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; - +use Marello\Bundle\OrderBundle\Entity\OrderItem; +use Marello\Bundle\ReturnBundle\Entity\ReturnEntity; +use Marello\Bundle\ReturnBundle\Entity\ReturnItem; use Oro\Bundle\WorkflowBundle\Entity\WorkflowItem; -use Oro\Component\ConfigExpression\ContextAccessor; use Oro\Component\Action\Action\AbstractAction; use Oro\Component\Action\Action\ActionInterface; - -use Marello\Bundle\ReturnBundle\Entity\ReturnEntity; -use Marello\Bundle\ReturnBundle\Entity\ReturnItem; -use Marello\Bundle\OrderBundle\Entity\OrderItem; -use Marello\Bundle\InventoryBundle\Event\InventoryUpdateEvent; -use Marello\Bundle\InventoryBundle\Model\InventoryUpdateContext; +use Oro\Component\ConfigExpression\ContextAccessor; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class InspectionAction extends AbstractAction { diff --git a/src/Marello/Bundle/RuleBundle/Datagrid/Extension/MassAction/StatusMassActionHandler.php b/src/Marello/Bundle/RuleBundle/Datagrid/Extension/MassAction/StatusMassActionHandler.php index f6bbe37c5..30534934d 100644 --- a/src/Marello/Bundle/RuleBundle/Datagrid/Extension/MassAction/StatusMassActionHandler.php +++ b/src/Marello/Bundle/RuleBundle/Datagrid/Extension/MassAction/StatusMassActionHandler.php @@ -8,7 +8,7 @@ use Oro\Bundle\DataGridBundle\Extension\MassAction\MassActionHandlerArgs; use Oro\Bundle\DataGridBundle\Extension\MassAction\MassActionHandlerInterface; use Oro\Bundle\DataGridBundle\Extension\MassAction\MassActionResponse; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; class StatusMassActionHandler implements MassActionHandlerInterface { diff --git a/src/Marello/Bundle/RuleBundle/Entity/Rule.php b/src/Marello/Bundle/RuleBundle/Entity/Rule.php index 85389aa3f..2d01db61e 100644 --- a/src/Marello/Bundle/RuleBundle/Entity/Rule.php +++ b/src/Marello/Bundle/RuleBundle/Entity/Rule.php @@ -143,7 +143,7 @@ class Rule extends ExtendRule implements DatesAwareInterface, RuleInterface /** * @var bool * - * @ORM\Column(name="system", type="boolean", nullable=false, options={"default"=false}) + * @ORM\Column(name="is_system", type="boolean", nullable=false, options={"default"=false}) * @ConfigField( * defaultValues={ * "dataaudit"={ diff --git a/src/Marello/Bundle/RuleBundle/Migrations/Schema/MarelloRuleBundleInstaller.php b/src/Marello/Bundle/RuleBundle/Migrations/Schema/MarelloRuleBundleInstaller.php index 30b6abfd1..f00831fb0 100644 --- a/src/Marello/Bundle/RuleBundle/Migrations/Schema/MarelloRuleBundleInstaller.php +++ b/src/Marello/Bundle/RuleBundle/Migrations/Schema/MarelloRuleBundleInstaller.php @@ -13,7 +13,7 @@ class MarelloRuleBundleInstaller implements Installation */ public function getMigrationVersion() { - return 'v1_1'; + return 'v1_2'; } /** @@ -36,7 +36,7 @@ protected function createMarelloRuleTable(Schema $schema) $table->addColumn('sort_order', 'integer', []); $table->addColumn('stop_processing', 'boolean', ['default' => false]); $table->addColumn('expression', 'text', ['notnull' => false]); - $table->addColumn('system', 'boolean', ['default' => false]); + $table->addColumn('is_system', 'boolean', ['default' => false]); $table->addColumn('created_at', 'datetime', []); $table->addColumn('updated_at', 'datetime', []); $table->setPrimaryKey(['id']); diff --git a/src/Marello/Bundle/RuleBundle/Migrations/Schema/v1_2/MarelloRuleBundleAddIsSystemColumn.php b/src/Marello/Bundle/RuleBundle/Migrations/Schema/v1_2/MarelloRuleBundleAddIsSystemColumn.php new file mode 100644 index 000000000..5cd6834b4 --- /dev/null +++ b/src/Marello/Bundle/RuleBundle/Migrations/Schema/v1_2/MarelloRuleBundleAddIsSystemColumn.php @@ -0,0 +1,44 @@ +updateMarelloRuleTable($schema, $queries); + } + + /** + * @param Schema $schema + * @param QueryBag $queries + */ + protected function updateMarelloRuleTable(Schema $schema, QueryBag $queries) + { + $table = $schema->getTable('marello_rule'); + + $table->addColumn('is_system', 'boolean', ['default' => false]); + $query = " + UPDATE marello_rule + SET + is_system = system"; + $queries->addQuery($query); + } +} diff --git a/src/Marello/Bundle/RuleBundle/Migrations/Schema/v1_2/MarelloRuleBundleDropSystemColumn.php b/src/Marello/Bundle/RuleBundle/Migrations/Schema/v1_2/MarelloRuleBundleDropSystemColumn.php new file mode 100644 index 000000000..0635d1fa3 --- /dev/null +++ b/src/Marello/Bundle/RuleBundle/Migrations/Schema/v1_2/MarelloRuleBundleDropSystemColumn.php @@ -0,0 +1,39 @@ +updateMarelloRuleTable($schema); + } + + /** + * @param Schema $schema + */ + protected function updateMarelloRuleTable(Schema $schema) + { + $table = $schema->getTable('marello_rule'); + + $table->dropColumn('system'); + + } +} diff --git a/src/Marello/Bundle/RuleBundle/Resources/config/services.yml b/src/Marello/Bundle/RuleBundle/Resources/config/services.yml index 0f5017c01..eb8f2dbbc 100644 --- a/src/Marello/Bundle/RuleBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/RuleBundle/Resources/config/services.yml @@ -1,6 +1,3 @@ -parameters: - marello_rule.entity.rule.class: 'Marello\Bundle\RuleBundle\Entity\Rule' - services: marello_rule.expression_language: class: 'Oro\Component\ExpressionLanguage\ExpressionLanguage' @@ -41,3 +38,15 @@ services: marello_rule.action.visibility_provider: class: 'Marello\Bundle\RuleBundle\Datagrid\RuleActionsVisibilityProvider' + + marello_rule.basic_expression_language_validator: + class: 'Oro\Component\ExpressionLanguage\BasicExpressionLanguageValidator' + arguments: + - '@marello_rule.expression_language' + + marello_rule.validator_constraints.expression_language_syntax_validator: + class: 'Marello\Bundle\RuleBundle\Validator\Constraints\ExpressionLanguageSyntaxValidator' + arguments: + - '@marello_rule.basic_expression_language_validator' + tags: + - { name: validator.constraint_validator, alias: oro_rule.validator_constraints.expression_language_syntax_validator } diff --git a/src/Marello/Bundle/RuleBundle/Validator/Constraints/ExpressionLanguageSyntax.php b/src/Marello/Bundle/RuleBundle/Validator/Constraints/ExpressionLanguageSyntax.php new file mode 100644 index 000000000..784b29ffb --- /dev/null +++ b/src/Marello/Bundle/RuleBundle/Validator/Constraints/ExpressionLanguageSyntax.php @@ -0,0 +1,16 @@ +basicExpressionLanguageValidator = $basicExpressionLanguageValidator; + } + + /** + * @param string $expression + * @param Constraint $constraint + */ + public function validate($expression, Constraint $constraint) + { + if ($expression) { + $validateResult = $this->basicExpressionLanguageValidator->validate($expression); + if ($validateResult) { + $this->context->addViolation($validateResult); + } + } + } +} diff --git a/src/Marello/Bundle/SalesBundle/Config/SalesChannelScopeManager.php b/src/Marello/Bundle/SalesBundle/Config/SalesChannelScopeManager.php new file mode 100644 index 000000000..0745354d9 --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/Config/SalesChannelScopeManager.php @@ -0,0 +1,40 @@ +dispatchScopeIdChangeEvent(); + + $this->scopeId = $scopeId; + } + + public function getScopeId() + { + return $this->scopeId; + } + + protected function isSupportedScopeEntity($entity) + { + return $entity instanceof SalesChannel; + } + + protected function getScopeEntityIdValue($entity) + { + return $entity->getId(); + } +} diff --git a/src/Marello/Bundle/SalesBundle/Controller/ConfigController.php b/src/Marello/Bundle/SalesBundle/Controller/ConfigController.php new file mode 100644 index 000000000..c036e3bd6 --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/Controller/ConfigController.php @@ -0,0 +1,84 @@ +get('marello_sales.config_form_provider.saleschannel'); + /** @var ConfigManager $manager */ + $manager = $this->get('oro_config.saleschannel'); + $prevScopeId = $manager->getScopeId(); + $manager->setScopeIdFromEntity($entity); + + list($activeGroup, $activeSubGroup) = $provider->chooseActiveGroups($activeGroup, $activeSubGroup); + + $jsTree = $provider->getJsTree(); + $form = false; + + if ($activeSubGroup !== null) { + $form = $provider->getForm($activeSubGroup); + + if ($this->get('oro_config.form.handler.config') + ->setConfigManager($manager) + ->process($form, $request) + ) { + $this->get('session')->getFlashBag()->add( + 'success', + $this->get('translator')->trans('oro.config.controller.config.saved.message') + ); + + // outdate content tags, it's only special case for generation that are not covered by NavigationBundle + $taggableData = ['name' => 'saleschannel_configuration', 'params' => [$activeGroup, $activeSubGroup]]; + $tagGenerator = $this->get('oro_sync.content.tag_generator'); + $dataUpdateTopicSender = $this->get('oro_sync.content.data_update_topic_sender'); + + $dataUpdateTopicSender->send($tagGenerator->generate($taggableData)); + + // recreate form to drop values for fields with use_parent_scope_value + $form = $provider->getForm($activeSubGroup); + $form->setData($manager->getSettingsByForm($form)); + } + } + $manager->setScopeId($prevScopeId); + + return [ + 'entity' => $entity, + 'data' => $jsTree, + 'form' => $form ? $form->createView() : null, + 'activeGroup' => $activeGroup, + 'activeSubGroup' => $activeSubGroup, + 'scopeEntity' => $entity, + 'scopeEntityClass' => SalesChannel::class, + 'scopeEntityId' => $entity->getId() + ]; + } +} diff --git a/src/Marello/Bundle/SalesBundle/Controller/SalesChannelController.php b/src/Marello/Bundle/SalesBundle/Controller/SalesChannelController.php index 74d4225c2..4d97d6d16 100644 --- a/src/Marello/Bundle/SalesBundle/Controller/SalesChannelController.php +++ b/src/Marello/Bundle/SalesBundle/Controller/SalesChannelController.php @@ -6,17 +6,21 @@ use Marello\Bundle\SalesBundle\Form\Type\SalesChannelType; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Oro\Bundle\SecurityBundle\Annotation\Acl; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; -class SalesChannelController extends Controller +class SalesChannelController extends AbstractController { /** - * @Config\Route("/", name="marello_sales_saleschannel_index") - * @Config\Method("GET") - * @Config\Template + * @Route( + * path="/", + * methods={"GET"}, + * name="marello_sales_saleschannel_index" + * ) + * @Template * @AclAncestor("marello_sales_saleschannel_view") */ public function indexAction() @@ -27,9 +31,12 @@ public function indexAction() } /** - * @Config\Route("/create", name="marello_sales_saleschannel_create") - * @Config\Method({"GET", "POST"}) - * @Config\Template("MarelloSalesBundle:SalesChannel:update.html.twig") + * @Route( + * path="/create", + * methods={"GET", "POST"}, + * name="marello_sales_saleschannel_create" + * ) + * @Template("MarelloSalesBundle:SalesChannel:update.html.twig") * @AclAncestor("marello_saleschannel_create") * * @param Request $request @@ -41,8 +48,12 @@ public function createAction(Request $request) } /** - * @Config\Route("/view/{id}", name="marello_sales_saleschannel_view", requirements={"id"="\d+"}) - * @Config\Template + * @Route( + * path="/view/{id}", + * name="marello_sales_saleschannel_view", + * requirements={"id"="\d+"} + * ) + * @Template * @Acl( * id="marello_sales_saleschannel_view", * type="entity", @@ -61,9 +72,13 @@ public function viewAction(SalesChannel $salesChannel) } /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_sales_saleschannel_update") - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Route( + * path="/update/{id}", + * methods={"GET", "POST"}, + * requirements={"id"="\d+"}, + * name="marello_sales_saleschannel_update" + * ) + * @Template * @AclAncestor("marello_saleschannel_update") * * @param Request $request diff --git a/src/Marello/Bundle/SalesBundle/Controller/SalesChannelGroupController.php b/src/Marello/Bundle/SalesBundle/Controller/SalesChannelGroupController.php index 749b7a44e..53887c889 100644 --- a/src/Marello/Bundle/SalesBundle/Controller/SalesChannelGroupController.php +++ b/src/Marello/Bundle/SalesBundle/Controller/SalesChannelGroupController.php @@ -6,16 +6,19 @@ use Marello\Bundle\SalesBundle\Form\Type\SalesChannelGroupType; use Oro\Bundle\SecurityBundle\Annotation\Acl; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; +use Symfony\Component\Routing\Annotation\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; -class SalesChannelGroupController extends Controller +class SalesChannelGroupController extends AbstractController { /** - * @Route("/", name="marello_sales_saleschannelgroup_index") + * @Route( + * path="/", + * name="marello_sales_saleschannelgroup_index" + * ) * @Template * @AclAncestor("marello_sales_saleschannelgroup_view") * @@ -29,7 +32,10 @@ public function indexAction() } /** - * @Route("/create", name="marello_sales_saleschannelgroup_create") + * @Route( + * path="/create", + * name="marello_sales_saleschannelgroup_create" + * ) * @Template("MarelloSalesBundle:SalesChannelGroup:update.html.twig") * @Acl( * id="marello_sales_saleschannelgroup_create", @@ -47,7 +53,11 @@ public function createAction(Request $request) } /** - * @Route("/view/{id}", name="marello_sales_saleschannelgroup_view", requirements={"id"="\d+"}) + * @Route( + * path="/view/{id}", + * name="marello_sales_saleschannelgroup_view", + * requirements={"id"="\d+"} + * ) * @Template * @Acl( * id="marello_sales_saleschannelgroup_view", @@ -68,7 +78,11 @@ public function viewAction(SalesChannelGroup $salesChannelGroup) } /** - * @Route("/update/{id}", name="marello_sales_saleschannelgroup_update", requirements={"id"="\d+"}) + * @Route( + * path="/update/{id}", + * name="marello_sales_saleschannelgroup_update", + * requirements={"id"="\d+"} + * ) * @Template * @Acl( * id="marello_sales_saleschannelgroup_update", diff --git a/src/Marello/Bundle/SalesBundle/Entity/SalesChannel.php b/src/Marello/Bundle/SalesBundle/Entity/SalesChannel.php index 35da69c5a..51294f5dc 100644 --- a/src/Marello/Bundle/SalesBundle/Entity/SalesChannel.php +++ b/src/Marello/Bundle/SalesBundle/Entity/SalesChannel.php @@ -4,7 +4,7 @@ use Doctrine\ORM\Mapping as ORM; use Marello\Bundle\CoreBundle\Model\EntityCreatedUpdatedAtTrait; -use Marello\Bundle\LocaleBundle\Model\LocaleAwareInterface; +use Marello\Bundle\LocaleBundle\Model\LocalizationAwareInterface; use Marello\Bundle\LocaleBundle\Model\LocalizationTrait; use Marello\Bundle\PricingBundle\Model\CurrencyAwareInterface; use Marello\Bundle\SalesBundle\Model\ExtendSalesChannel; @@ -48,7 +48,7 @@ */ class SalesChannel extends ExtendSalesChannel implements CurrencyAwareInterface, - LocaleAwareInterface + LocalizationAwareInterface { use EntityCreatedUpdatedAtTrait; use LocalizationTrait; diff --git a/src/Marello/Bundle/SalesBundle/Entity/SalesChannelGroup.php b/src/Marello/Bundle/SalesBundle/Entity/SalesChannelGroup.php index ed65066f7..6db0b29a8 100644 --- a/src/Marello/Bundle/SalesBundle/Entity/SalesChannelGroup.php +++ b/src/Marello/Bundle/SalesBundle/Entity/SalesChannelGroup.php @@ -9,8 +9,6 @@ use Marello\Bundle\SalesBundle\Model\ExtendSalesChannelGroup; use Oro\Bundle\EntityConfigBundle\Metadata\Annotation\Config; use Oro\Bundle\EntityConfigBundle\Metadata\Annotation\ConfigField; -use Oro\Bundle\OrganizationBundle\Entity\Organization; -use Oro\Bundle\OrganizationBundle\Entity\OrganizationInterface; use Oro\Bundle\OrganizationBundle\Entity\Ownership\AuditableOrganizationAwareTrait; /** @@ -88,7 +86,7 @@ class SalesChannelGroup extends ExtendSalesChannelGroup /** * @var bool * - * @ORM\Column(name="system", type="boolean", nullable=false, options={"default"=false}) + * @ORM\Column(name="is_system", type="boolean", nullable=false, options={"default"=false}) * @ConfigField( * defaultValues={ * "dataaudit"={ diff --git a/src/Marello/Bundle/SalesBundle/EventListener/Datagrid/SalesChannelGridListener.php b/src/Marello/Bundle/SalesBundle/EventListener/Datagrid/SalesChannelGridListener.php new file mode 100644 index 000000000..6f230b87d --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/EventListener/Datagrid/SalesChannelGridListener.php @@ -0,0 +1,32 @@ +getConfig(); + $config->offsetSetByPath( + '[properties][config_link]', + [ + 'type' => 'url', + 'route' => 'marello_sales_config_saleschannel', + 'params' => ['id'], + ] + ); + $config->offsetSetByPath( + '[actions][config]', + [ + 'type' => 'navigate', + 'label' => 'marello.sales.sales_channel.grid.action.config.label', + 'link' => 'config_link', + 'icon' => 'cog', + 'acl_resource' => 'marello_sales_saleschannel_update', + 'order' => 999, + ] + ); + } +} diff --git a/src/Marello/Bundle/SalesBundle/EventListener/Datagrid/SalesChannelsDatagridListener.php b/src/Marello/Bundle/SalesBundle/EventListener/Datagrid/SalesChannelsDatagridListener.php new file mode 100644 index 000000000..1da1ce645 --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/EventListener/Datagrid/SalesChannelsDatagridListener.php @@ -0,0 +1,162 @@ +relatedEntityClass = $relatedEntityClass; + $this->salesChannelsChoicesProvider = $salesChannelsChoicesProvider; + + $this->expressionBuilder = new Expr(); + } + + /** + * @param BuildBefore $event + */ + public function onBuildBefore(BuildBefore $event) + { + $config = $event->getConfig(); + + $this->addSelect($config); + $this->addJoin($config); + $this->addColumn($config); + $this->addSorter($config); + $this->addFilter($config); + } + + /** + * @param DatagridConfiguration $configuration + * @return string + * @throws \InvalidArgumentException when a root entity not found in the grid + */ + protected function getAlias(DatagridConfiguration $configuration) + { + $rootAlias = $configuration->getOrmQuery()->getRootAlias(); + if (!$rootAlias) { + throw new \InvalidArgumentException( + sprintf( + 'A root entity is missing for grid "%s"', + $configuration->getName() + ) + ); + } + + return $rootAlias; + } + + /** + * @return string + */ + protected function getColumnLabel() + { + return 'marello.product.channels.label'; + } + + /** + * @return string + */ + protected function getDataName() + { + return self::DATA_NAME; + } + + /** + * @return string + */ + protected function getJoinAlias() + { + return self::JOIN_ALIAS; + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSelect(DatagridConfiguration $config) + { + $config->getOrmQuery()->addSelect( + sprintf('count(%s.code) AS channelsCount', $this->getJoinAlias()) + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addJoin(DatagridConfiguration $config) + { + $config->getOrmQuery()->addLeftJoin( + $this->getAlias($config).'.channels', + $this->getJoinAlias() + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addColumn(DatagridConfiguration $config) + { + $config->offsetSetByPath(sprintf('[columns][%s]', $this->getDataName()), [ + 'label' => $this->getColumnLabel(), + 'type' => 'twig', + 'frontend_type' => 'html', + 'template' => 'MarelloSalesBundle:SalesChannel/Datagrid/Property:channels.html.twig', + 'renderable' => false + ]); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSorter(DatagridConfiguration $config) + { + $config->offsetSetByPath( + sprintf('[sorters][columns][%s]', $this->getDataName()), + ['data_name' => 'channelsCount'] + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addFilter(DatagridConfiguration $config) + { + $config->offsetSetByPath( + sprintf('[filters][columns][%s]', $this->getDataName()), + [ + 'type' => 'choice', + 'data_name' => $this->getJoinAlias() . '.code', + 'enabled' => false, + 'options' => [ + 'field_options' => [ + 'multiple' => true, + 'choices' => $this->salesChannelsChoicesProvider->getChannels() + ] + ] + ] + ); + } +} diff --git a/src/Marello/Bundle/SalesBundle/EventListener/Doctrine/SalesChannelGroupInventoryRebalanceListener.php b/src/Marello/Bundle/SalesBundle/EventListener/Doctrine/SalesChannelGroupInventoryRebalanceListener.php index 53578ca70..06bb5ae60 100644 --- a/src/Marello/Bundle/SalesBundle/EventListener/Doctrine/SalesChannelGroupInventoryRebalanceListener.php +++ b/src/Marello/Bundle/SalesBundle/EventListener/Doctrine/SalesChannelGroupInventoryRebalanceListener.php @@ -94,19 +94,6 @@ public function getIsEntityInstanceOf($entity) return ($entity instanceof SalesChannelGroup); } - /** - * @deprecated since 2.1.1 will be removed in 3.0 use - * rebalanceForSalesChannelGroup(SalesChannelGroup $entity) instead - * @throws \Oro\Component\MessageQueue\Transport\Exception\Exception - */ - protected function triggerRebalance() - { - $this->messageProducer->send( - Topics::RESOLVE_REBALANCE_ALL_INVENTORY, - Topics::ALL_INVENTORY - ); - } - /** * {@inheritdoc} * @param SalesChannelGroup $entity diff --git a/src/Marello/Bundle/SalesBundle/Form/EventListener/DefaultSalesChannelSubscriber.php b/src/Marello/Bundle/SalesBundle/Form/EventListener/DefaultSalesChannelSubscriber.php index c638cf3a9..cc2749149 100644 --- a/src/Marello/Bundle/SalesBundle/Form/EventListener/DefaultSalesChannelSubscriber.php +++ b/src/Marello/Bundle/SalesBundle/Form/EventListener/DefaultSalesChannelSubscriber.php @@ -3,7 +3,7 @@ namespace Marello\Bundle\SalesBundle\Form\EventListener; use Doctrine\ORM\EntityManager; -use Marello\Bundle\SalesBundle\Model\SalesChannelAwareInterface; +use Marello\Bundle\SalesBundle\Model\SalesChannelsAwareInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; @@ -40,7 +40,7 @@ public function preSetData(FormEvent $event) { $entity = $event->getData(); if (!$entity || null === $entity->getId()) { - if ($entity instanceof SalesChannelAwareInterface) { + if ($entity instanceof SalesChannelsAwareInterface) { $channels = $this->getDefaultChannels(); if (!is_null($channels) && count($channels) !== 0) { foreach ($channels as $channel) { diff --git a/src/Marello/Bundle/SalesBundle/Form/Type/SalesChannelType.php b/src/Marello/Bundle/SalesBundle/Form/Type/SalesChannelType.php index c7d77f544..d08833760 100644 --- a/src/Marello/Bundle/SalesBundle/Form/Type/SalesChannelType.php +++ b/src/Marello/Bundle/SalesBundle/Form/Type/SalesChannelType.php @@ -2,12 +2,10 @@ namespace Marello\Bundle\SalesBundle\Form\Type; -use Doctrine\ORM\EntityRepository; use Marello\Bundle\PricingBundle\Form\EventListener\CurrencySubscriber; use Marello\Bundle\SalesBundle\Entity\SalesChannel; use Oro\Bundle\CurrencyBundle\Form\Type\CurrencyType; use Oro\Bundle\LocaleBundle\Form\Type\LocalizationSelectType; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\FormBuilderInterface; diff --git a/src/Marello/Bundle/SalesBundle/Migrations/Data/ORM/LoadSalesChannelData.php b/src/Marello/Bundle/SalesBundle/Migrations/Data/ORM/LoadSalesChannelData.php index 5f44084c0..b876019f2 100644 --- a/src/Marello/Bundle/SalesBundle/Migrations/Data/ORM/LoadSalesChannelData.php +++ b/src/Marello/Bundle/SalesBundle/Migrations/Data/ORM/LoadSalesChannelData.php @@ -43,7 +43,6 @@ protected function loadSalesChannels() $channel->setCurrency($values['currency']); $channel->setOwner($organization); $channel->setLocalization($localization); - $channel->setLocale('nl_NL'); $this->manager->persist($channel); $i++; diff --git a/src/Marello/Bundle/SalesBundle/Migrations/Schema/MarelloSalesBundleInstaller.php b/src/Marello/Bundle/SalesBundle/Migrations/Schema/MarelloSalesBundleInstaller.php index 0949dcf15..359036121 100644 --- a/src/Marello/Bundle/SalesBundle/Migrations/Schema/MarelloSalesBundleInstaller.php +++ b/src/Marello/Bundle/SalesBundle/Migrations/Schema/MarelloSalesBundleInstaller.php @@ -3,7 +3,6 @@ namespace Marello\Bundle\SalesBundle\Migrations\Schema; use Doctrine\DBAL\Schema\Schema; -use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope; use Oro\Bundle\MigrationBundle\Migration\Installation; use Oro\Bundle\MigrationBundle\Migration\QueryBag; @@ -18,7 +17,7 @@ class MarelloSalesBundleInstaller implements Installation */ public function getMigrationVersion() { - return 'v1_1'; + return 'v1_2'; } /** @@ -44,7 +43,7 @@ protected function createMarelloSalesChannelGroupTable(Schema $schema) $table->addColumn('id', 'integer', ['autoincrement' => true]); $table->addColumn('name', 'string', ['length' => 255]); $table->addColumn('description', 'text', ['notnull' => false]); - $table->addColumn('system', 'boolean', ['default' => false]); + $table->addColumn('is_system', 'boolean', ['default' => false]); $table->addColumn('organization_id', 'integer', ['notnull' => false]); $table->addColumn('created_at', 'datetime', []); $table->addColumn('updated_at', 'datetime', ['notnull' => false]); @@ -70,7 +69,6 @@ protected function createMarelloSalesSalesChannelTable(Schema $schema) $table->addColumn('code', 'string', ['length' => 255]); $table->addColumn('currency', 'string', ['length' => 5]); $table->addColumn('localization_id', 'integer', ['notnull' => false]); - $table->addColumn('locale', 'string', ['notnull' => false, 'length' => 5]); $table->addColumn('group_id', 'integer', ['notnull' => false]); $table->addColumn('integration_channel_id', 'integer', ['notnull' => false]); $table->setPrimaryKey(['id']); diff --git a/src/Marello/Bundle/SalesBundle/Migrations/Schema/v1_2/MarelloSalesBundle.php b/src/Marello/Bundle/SalesBundle/Migrations/Schema/v1_2/MarelloSalesBundle.php new file mode 100644 index 000000000..0cc87fa43 --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/Migrations/Schema/v1_2/MarelloSalesBundle.php @@ -0,0 +1,35 @@ +getTable('marello_sales_sales_channel'); + $table->dropColumn('locale'); + + $dropLocaleInConfigSql = <<addSql( + $dropLocaleInConfigSql, + ['field_name' => 'locale', 'class_name' => SalesChannel::class], + ['field_name' => Type::STRING, 'class_name' => Type::STRING] + ); + $queries->addPostQuery($dropLocaleInConfigQuery); + } +} diff --git a/src/Marello/Bundle/SalesBundle/Migrations/Schema/v1_2/MarelloSalesBundleAddIsSystemColumn.php b/src/Marello/Bundle/SalesBundle/Migrations/Schema/v1_2/MarelloSalesBundleAddIsSystemColumn.php new file mode 100644 index 000000000..8fa1ee079 --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/Migrations/Schema/v1_2/MarelloSalesBundleAddIsSystemColumn.php @@ -0,0 +1,44 @@ +updateMarelloSalesChannelGroupTable($schema, $queries); + } + + /** + * @param Schema $schema + * @param QueryBag $queries + */ + protected function updateMarelloSalesChannelGroupTable(Schema $schema, QueryBag $queries) + { + $table = $schema->getTable('marello_sales_channel_group'); + + $table->addColumn('is_system', 'boolean', ['default' => false]); + $query = " + UPDATE marello_sales_channel_group + SET + is_system = system"; + $queries->addQuery($query); + } +} diff --git a/src/Marello/Bundle/SalesBundle/Migrations/Schema/v1_2/MarelloSalesBundleDropSystemColumn.php b/src/Marello/Bundle/SalesBundle/Migrations/Schema/v1_2/MarelloSalesBundleDropSystemColumn.php new file mode 100644 index 000000000..084b0b9b5 --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/Migrations/Schema/v1_2/MarelloSalesBundleDropSystemColumn.php @@ -0,0 +1,39 @@ +updateMarelloSalesChannelGroupTable($schema); + } + + /** + * @param Schema $schema + */ + protected function updateMarelloSalesChannelGroupTable(Schema $schema) + { + $table = $schema->getTable('marello_sales_channel_group'); + + $table->dropColumn('system'); + + } +} diff --git a/src/Marello/Bundle/SalesBundle/Model/ChannelAwareInterface.php b/src/Marello/Bundle/SalesBundle/Model/ChannelAwareInterface.php deleted file mode 100644 index 8ef61f2c3..000000000 --- a/src/Marello/Bundle/SalesBundle/Model/ChannelAwareInterface.php +++ /dev/null @@ -1,17 +0,0 @@ -getTreeData(self::CONFIG_KEY, self::CORRECT_FIELDS_NESTING_LEVEL); + } + + public function getJsTree() + { + return $this->getJsTreeData(self::CONFIG_KEY, self::CORRECT_MENU_NESTING_LEVEL); + } + + public function setParentCheckboxLabel($label) + { + $this->parentCheckboxLabel = $label; + } + + protected function getParentCheckboxLabel() + { + return $this->parentCheckboxLabel; + } +} diff --git a/src/Marello/Bundle/SalesBundle/Provider/SalesChannelLocalizationProvider.php b/src/Marello/Bundle/SalesBundle/Provider/SalesChannelLocalizationProvider.php index 85e241f9a..fb470f8a0 100644 --- a/src/Marello/Bundle/SalesBundle/Provider/SalesChannelLocalizationProvider.php +++ b/src/Marello/Bundle/SalesBundle/Provider/SalesChannelLocalizationProvider.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\SalesBundle\Provider; -use Marello\Bundle\LocaleBundle\Model\LocaleAwareInterface; +use Marello\Bundle\LocaleBundle\Model\LocalizationAwareInterface; use Marello\Bundle\LocaleBundle\Provider\EntityLocalizationProviderInterface; use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Entity\OrderAwareInterface; @@ -13,7 +13,7 @@ class SalesChannelLocalizationProvider implements EntityLocalizationProviderInte /** * @inheritDoc */ - public function getLocalization(LocaleAwareInterface $entity) + public function getLocalization(LocalizationAwareInterface $entity) { if ($entity instanceof SalesChannel) { return $entity->getLocalization(); @@ -35,7 +35,7 @@ public function getLocalization(LocaleAwareInterface $entity) /** * @inheritDoc */ - public function isApplicable(LocaleAwareInterface $entity) + public function isApplicable(LocalizationAwareInterface $entity) { if ($entity instanceof SalesChannel || $entity instanceof Order || $entity instanceof OrderAwareInterface) { return true; diff --git a/src/Marello/Bundle/SalesBundle/Resources/config/form.yml b/src/Marello/Bundle/SalesBundle/Resources/config/form.yml index 3cb311222..b1d16637b 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/config/form.yml +++ b/src/Marello/Bundle/SalesBundle/Resources/config/form.yml @@ -1,49 +1,38 @@ -parameters: - #form types - marello_sales.form_type.saleschannel.class: Marello\Bundle\SalesBundle\Form\Type\SalesChannelType - marello_sales.form_type.saleschannel.select.class: Marello\Bundle\SalesBundle\Form\Type\SalesChannelSelectType - marello_sales.form_type.saleschannel.select.api.class: Marello\Bundle\SalesBundle\Form\Type\SalesChannelSelectApiType - marello_sales.form_type.saleschannel.multiselect.class: Marello\Bundle\SalesBundle\Form\Type\SalesChannelMultiSelectType - marello_sales.form_type.system_group_saleschannel.multiselect.class: Marello\Bundle\SalesBundle\Form\Type\SystemGroupSalesChannelMultiselectType - marello_sales.form_type.saleschannelgroup.class: Marello\Bundle\SalesBundle\Form\Type\SalesChannelGroupType - #form handlers - marello_sales.form_handler.saleschannel.class: Marello\Bundle\SalesBundle\Form\Handler\SalesChannelHandler - marello_sales.form_handler.saleschannelgroup.class: Marello\Bundle\SalesBundle\Form\Handler\SalesChannelGroupHandler services: #form types marello_sales_form_type.saleschannel: - class: '%marello_sales.form_type.saleschannel.class%' + class: 'Marello\Bundle\SalesBundle\Form\Type\SalesChannelType' arguments: - '@marello_productprice.pricing.form.event_listener.currency_subscriber' tags: - { name: form.type } marello_sales_form_type.saleschannelgroup: - class: '%marello_sales.form_type.saleschannelgroup.class%' + class: 'Marello\Bundle\SalesBundle\Form\Type\SalesChannelGroupType' tags: - { name: form.type } marello_sales_form_type.saleschannel_multi_select: - class: '%marello_sales.form_type.saleschannel.multiselect.class%' + class: 'Marello\Bundle\SalesBundle\Form\Type\SalesChannelMultiSelectType' arguments: - '@marello_sales.form.data_transformer.sales_channel' tags: - { name: form.type } marello_sales_form_type.system_group_saleschannel_multi_select: - class: '%marello_sales.form_type.system_group_saleschannel.multiselect.class%' + class: 'Marello\Bundle\SalesBundle\Form\Type\SystemGroupSalesChannelMultiselectType' arguments: - '@marello_sales.form.data_transformer.sales_channel' tags: - { name: form.type } marello_sales_form_type.saleschannel_select: - class: '%marello_sales.form_type.saleschannel.select.class%' + class: 'Marello\Bundle\SalesBundle\Form\Type\SalesChannelSelectType' tags: - { name: form.type } marello_sales.form.type.sales_channel_select_api: - class: '%marello_sales.form_type.saleschannel.select.api.class%' + class: 'Marello\Bundle\SalesBundle\Form\Type\SalesChannelSelectApiType' arguments: - '@marello_sales.form.data_transformer.sales_channel_to_code' tags: @@ -51,14 +40,14 @@ services: #form handlers marello_sales.saleschannel_form.handler: - class: '%marello_sales.form_handler.saleschannel.class%' + class: 'Marello\Bundle\SalesBundle\Form\Handler\SalesChannelHandler' arguments: - '@doctrine.orm.entity_manager' tags: - { name: oro_form.form.handler, alias: marello_sales.saleschannel_form.handler } marello_sales.saleschannelgroup_form.handler: - class: '%marello_sales.form_handler.saleschannelgroup.class%' + class: 'Marello\Bundle\SalesBundle\Form\Handler\SalesChannelGroupHandler' arguments: - '@doctrine.orm.entity_manager' tags: diff --git a/src/Marello/Bundle/SalesBundle/Resources/config/jsmodules.yml b/src/Marello/Bundle/SalesBundle/Resources/config/jsmodules.yml new file mode 100755 index 000000000..70ae2a2be --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/Resources/config/jsmodules.yml @@ -0,0 +1,8 @@ +aliases: + oro/select2-autocomplete-system-group-sales-channels-component$: marellosales/js/app/components/select2-autocomplete-system-group-sales-channels-component +dynamic-imports: + marellosales: + - marellosales/js/app/components/sales-channel-select-component + - marellosales/js/app/components/select2-autocomplete-system-group-sales-channels-component + commons: + - oro/select2-autocomplete-system-group-sales-channels-component \ No newline at end of file diff --git a/src/Marello/Bundle/SalesBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/SalesBundle/Resources/config/oro/datagrids.yml index 676149d0b..b6b3c7b9b 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/SalesBundle/Resources/config/oro/datagrids.yml @@ -61,7 +61,7 @@ datagrids: default: data_name: sc.default default: - name: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC" + name: "ASC" properties: id: ~ view_link: @@ -88,9 +88,6 @@ datagrids: - scg from: - { table: MarelloSalesBundle:SalesChannelGroup, alias: scg } - join: - left: - - { join: scg.salesChannels, alias: sc } where: and: - scg.system != true @@ -110,13 +107,13 @@ datagrids: columns: name: type: string - data_name: sc.name + data_name: scg.name sorters: columns: name: data_name: scg.name description: - data_name: sc.description + data_name: scg.description properties: id: ~ view_link: @@ -166,7 +163,13 @@ datagrids: type: orm query: select: - - sc + - sc.id + - sc.name + - sc.code + - sc.channelType + - sc.currency + - sc.active + - sc.default - (CASE WHEN (sc.id IN (:data_in)) AND sc.id NOT IN (:data_not_in) THEN true ELSE false END) as hasChannel from: - { table: MarelloSalesBundle:SalesChannel, alias: sc } @@ -229,7 +232,7 @@ datagrids: default: data_name: sc.default default: - name: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC" + name: "ASC" properties: id: ~ actions: ~ diff --git a/src/Marello/Bundle/SalesBundle/Resources/config/oro/navigation.yml b/src/Marello/Bundle/SalesBundle/Resources/config/oro/navigation.yml index 55c94ccb5..dcbde0663 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/config/oro/navigation.yml +++ b/src/Marello/Bundle/SalesBundle/Resources/config/oro/navigation.yml @@ -63,3 +63,4 @@ navigation: marello_sales_saleschannelgroup_create: 'oro.ui.create_entity' marello_sales_saleschannelgroup_update: '%name% - oro.ui.edit' marello_sales_saleschannelgroup_view: '%name% - oro.ui.view' + marello_sales_config_config: '%name% - marello.sales.config.saleschannel.label' diff --git a/src/Marello/Bundle/SalesBundle/Resources/config/oro/placeholders.yml b/src/Marello/Bundle/SalesBundle/Resources/config/oro/placeholders.yml new file mode 100644 index 000000000..cc9c5c916 --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/Resources/config/oro/placeholders.yml @@ -0,0 +1,10 @@ +placeholders: + placeholders: + view_navButtons_before: + items: + saleschannel_config_button: ~ + + items: + saleschannel_config_button: + template: MarelloSalesBundle:Config:button.html.twig + applicable: "@marello_sales.placeholder.filter->isSalesChannelPage($entity$)" diff --git a/src/Marello/Bundle/SalesBundle/Resources/config/oro/routing.yml b/src/Marello/Bundle/SalesBundle/Resources/config/oro/routing.yml index 204692d54..5bb19e1fd 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/config/oro/routing.yml +++ b/src/Marello/Bundle/SalesBundle/Resources/config/oro/routing.yml @@ -3,6 +3,11 @@ marello_sales_saleschannel: type: annotation prefix: /marello/saleschannel +marello_sales_config: + resource: "@MarelloSalesBundle/Controller/ConfigController.php" + type: annotation + prefix: /config + marello_sales_saleschannelgroup: resource: "@MarelloSalesBundle/Controller/SalesChannelGroupController.php" type: annotation diff --git a/src/Marello/Bundle/SalesBundle/Resources/config/requirejs.yml b/src/Marello/Bundle/SalesBundle/Resources/config/requirejs.yml deleted file mode 100755 index 80363c20d..000000000 --- a/src/Marello/Bundle/SalesBundle/Resources/config/requirejs.yml +++ /dev/null @@ -1,4 +0,0 @@ -config: - paths: - 'marellosales/js/app/components/sales-channel-select-component': 'bundles/marellosales/js/app/components/sales-channel-select-component.js' - 'oro/select2-autocomplete-system-group-sales-channels-component': 'bundles/marellosales/js/app/components/select2-autocomplete-system-group-sales-channels-component.js' diff --git a/src/Marello/Bundle/SalesBundle/Resources/config/services.yml b/src/Marello/Bundle/SalesBundle/Resources/config/services.yml index 421ba4ab3..0ab8431a1 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/SalesBundle/Resources/config/services.yml @@ -1,8 +1,3 @@ -parameters: - marello_sales.saleschannel.entity.class: Marello\Bundle\SalesBundle\Entity\SalesChannel - marello_sales.saleschannelgroup.entity.class: Marello\Bundle\SalesBundle\Entity\SalesChannelGroup - marello_sales.saleschannel.model.channel_aware_interface.class: Marello\Bundle\SalesBundle\Model\SalesChannelAwareInterface - services: marello_sales.form.event_listener.default_sales_channel_subscriber: class: Marello\Bundle\SalesBundle\Form\EventListener\DefaultSalesChannelSubscriber @@ -21,6 +16,7 @@ services: marello_sales.provider.basic_sales_channels_choices: class: Marello\Bundle\SalesBundle\Provider\BasicSalesChannelsChoicesProvider + public: true arguments: - '@oro_entity.doctrine_helper' @@ -36,8 +32,8 @@ services: marello_sales.saleschannel.form.autocomplete.search_handler: parent: oro_form.autocomplete.search_handler arguments: - - "%marello_sales.saleschannel.entity.class%" - - ["name"] + - 'Marello\Bundle\SalesBundle\Entity\SalesChannel' + - ['name'] tags: - { name: oro_form.autocomplete.search_handler, alias: saleschannels, acl_resource: marello_product_view } @@ -45,8 +41,8 @@ services: class: 'Marello\Bundle\SalesBundle\Autocomplete\ActiveSalesChannelHandler' parent: oro_form.autocomplete.search_handler arguments: - - "%marello_sales.saleschannel.entity.class%" - - ["name", "code", "channelType"] + - 'Marello\Bundle\SalesBundle\Entity\SalesChannel' + - ['name', 'code', 'channelType'] tags: - { name: oro_form.autocomplete.search_handler, alias: active_saleschannels, acl_resource: marello_product_view } @@ -54,8 +50,8 @@ services: class: 'Marello\Bundle\SalesBundle\Autocomplete\SystemGroupSalesChannelHandler' parent: oro_form.autocomplete.search_handler arguments: - - "%marello_sales.saleschannel.entity.class%" - - ["name"] + - 'Marello\Bundle\SalesBundle\Entity\SalesChannel' + - ['name'] tags: - { name: oro_form.autocomplete.search_handler, alias: system_group_saleschannels, acl_resource: marello_product_view } @@ -111,8 +107,17 @@ services: tags: - { name: kernel.event_listener, event: oro_datagrid.orm_datasource.result.after.marello-sales-channel-groups, method: onResultAfter } + marello_sales.event_listener.datagrid.products_grid: + class: 'Marello\Bundle\SalesBundle\EventListener\Datagrid\SalesChannelsDatagridListener' + arguments: + - 'Marello\Bundle\ProductBundle\Entity\Product' + - '@marello_sales.provider.basic_sales_channels_choices' + tags: + - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before.marello-products-grid, method: onBuildBefore } + marello_sales.datagrid.saleschannelgroup.action_permission_provider: class: 'Marello\Bundle\SalesBundle\Datagrid\SalesChannelGroupActionPermissionProvider' + public: true marello_sales.condition.sales_channel_has_valid_integration: class: 'Marello\Bundle\SalesBundle\Condition\HasValidIntegration' @@ -125,4 +130,25 @@ services: marello_sales.entity_localization_provider.sales_channel: class: 'Marello\Bundle\SalesBundle\Provider\SalesChannelLocalizationProvider' tags: - - {name: marello_entity_localization_provider, priority: 10} \ No newline at end of file + - {name: marello_entity_localization_provider, priority: 10} + + marello_sales.scope.saleschannel: + class: 'Marello\Bundle\SalesBundle\Config\SalesChannelScopeManager' + parent: oro_config.scope_manager.abstract + tags: + - { name: oro_config.scope, scope: saleschannel, priority: 60 } + + marello_sales.config_form_provider.saleschannel: + public: true + class: 'Marello\Bundle\SalesBundle\Provider\SalesChannelConfigurationFormProvider' + parent: 'oro_config.provider.abstract_provider' + lazy: true + + marello_sales.event_listener.saleschannel_grid_listener: + class: 'Marello\Bundle\SalesBundle\EventListener\Datagrid\SalesChannelGridListener' + tags: + - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before.marello-sales-channel, method: onBuildBefore } + + marello_sales.placeholder.filter: + public: true + class: 'Marello\Bundle\SalesBundle\Placeholder\PlaceholderFilter' diff --git a/src/Marello/Bundle/SalesBundle/Resources/public/js/app/components/sales-channel-select-component.js b/src/Marello/Bundle/SalesBundle/Resources/public/js/app/components/sales-channel-select-component.js index 65e0c1ec2..525a6ef33 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/public/js/app/components/sales-channel-select-component.js +++ b/src/Marello/Bundle/SalesBundle/Resources/public/js/app/components/sales-channel-select-component.js @@ -3,13 +3,12 @@ define(function(require) { 'use strict'; - var SalesChannelSelectComponent; - var $ = require('jquery'); - var _ = require('underscore'); - var BaseComponent = require('oroui/js/app/components/base/component'); - var mediator = require('oroui/js/mediator'); + const $ = require('jquery'); + const _ = require('underscore'); + const BaseComponent = require('oroui/js/app/components/base/component'); + const mediator = require('oroui/js/mediator'); - SalesChannelSelectComponent = BaseComponent.extend({ + const SalesChannelSelectComponent = BaseComponent.extend({ /** * @property {Object} */ diff --git a/src/Marello/Bundle/SalesBundle/Resources/public/js/app/components/select2-autocomplete-system-group-sales-channels-component.js b/src/Marello/Bundle/SalesBundle/Resources/public/js/app/components/select2-autocomplete-system-group-sales-channels-component.js index 01394017c..4b7110f42 100755 --- a/src/Marello/Bundle/SalesBundle/Resources/public/js/app/components/select2-autocomplete-system-group-sales-channels-component.js +++ b/src/Marello/Bundle/SalesBundle/Resources/public/js/app/components/select2-autocomplete-system-group-sales-channels-component.js @@ -1,8 +1,8 @@ define(function (require) { 'use strict'; - var Select2AutocompleteSystemChannelGroupAwareComponent, + const Select2AutocompleteComponent = require('oro/select2-autocomplete-component'); - Select2AutocompleteSystemChannelGroupAwareComponent = Select2AutocompleteComponent.extend({ + const Select2AutocompleteSystemChannelGroupAwareComponent = Select2AutocompleteComponent.extend({ /** * @property {Object} diff --git a/src/Marello/Bundle/SalesBundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/SalesBundle/Resources/translations/messages.en.yml index adfd26101..3813c94e5 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/translations/messages.en.yml +++ b/src/Marello/Bundle/SalesBundle/Resources/translations/messages.en.yml @@ -2,6 +2,9 @@ marello: sales: navigation: sales.label: Sales + config: + saleschannel.label: Sales Channel Configuration + use_default.label: Use system saleschannel: header.label: Manage Sales Channels entity_label: Sales Channel @@ -14,13 +17,11 @@ marello: code.label: Code channel_type.label: Type owner.label: Owner - locale.label: Locale localization.label: Localization controller.message.saved: Sales Channel saved form.select_saleschannels: Select Sales Channels form.select_saleschannel: Select Sales Channel selected_channels.label: Selected Sales Channels - localization.label: Localization group.label: Group create_own_group.label: Create Sales Channel Group integration_channel.label: Integration Channel @@ -33,6 +34,9 @@ marello: shortcut_new_saleschannel.description: Create new sales channel instance shortcut_list_saleschannel.label: Show sales channel list shortcut_list_saleschannel.description: Show list of sales channels + grid: + action: + config.label: Sales Channel Configuration datablock: general: General @@ -54,4 +58,4 @@ marello: success: saved: Sales Channel Group has been saved successfully warning: - is_system_update_attempt: System Sales Channel Group can't be updated \ No newline at end of file + is_system_update_attempt: System Sales Channel Group can't be updated diff --git a/src/Marello/Bundle/SalesBundle/Resources/views/Config/button.html.twig b/src/Marello/Bundle/SalesBundle/Resources/views/Config/button.html.twig new file mode 100644 index 000000000..cbd071d95 --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/Resources/views/Config/button.html.twig @@ -0,0 +1,9 @@ +{% if is_granted('EDIT', entity) %} + {% import 'OroUIBundle::macros.html.twig' as UI %} + {{ UI.button({ + 'path' : path('marello_sales_config_saleschannel', { 'id': entity.id }), + 'label': 'oro.config.menu.system_configuration.label'|trans, + 'title': 'marello.sales.config.saleschannel.label'|trans, + 'iCss': 'fa-cog', + }) }} +{% endif %} diff --git a/src/Marello/Bundle/SalesBundle/Resources/views/Config/salesChannel.html.twig b/src/Marello/Bundle/SalesBundle/Resources/views/Config/salesChannel.html.twig new file mode 100644 index 000000000..cf66edf03 --- /dev/null +++ b/src/Marello/Bundle/SalesBundle/Resources/views/Config/salesChannel.html.twig @@ -0,0 +1,24 @@ +{% extends 'OroConfigBundle::configPage.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} + +{% oro_title_set({params : {"%name%": entity.name }}) %} + +{% set pageTitle = [ + UI.link({ + 'path': path('marello_sales_saleschannel_index'), + 'label': 'marello.sales.saleschannel.entity_plural_label'|trans + }), + UI.link({ + 'path': path('marello_sales_saleschannel_view', {id: entity.id}), + 'label': entity.name + }), + 'marello.sales.config.saleschannel.label'|trans +] +%} + +{% set formAction = path( + 'marello_sales_config_saleschannel', + {id:entity.id, activeGroup: activeGroup, activeSubGroup: activeSubGroup} +) %} +{% set routeName = 'marello_sales_config_saleschannel' %} +{% set routeParameters = {id: entity.id} %} diff --git a/src/Marello/Bundle/SalesBundle/Resources/views/Form/fields.html.twig b/src/Marello/Bundle/SalesBundle/Resources/views/Form/fields.html.twig index f2fb1320b..deac063b5 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/views/Form/fields.html.twig +++ b/src/Marello/Bundle/SalesBundle/Resources/views/Form/fields.html.twig @@ -1,91 +1,3 @@ -{% block marello_product_channel_tax_relation_form_widget %} - -
- {{ form_widget(form.salesChannel) }} -
- {{ form_errors(form.salesChannel) }} - - -
- {{ form_widget(form.taxCode) }} -
- {{ form_errors(form.taxCode) }} - -{% endblock %} - -{% macro marello_product_channel_tax_relation_collection_form_item_prototype(widget) %} - {% if 'collection' in widget.vars.block_prefixes %} - {% set form = widget.vars.prototype %} - {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} - {% set disabled = widget.vars.disabled %} - {% set allow_delete = widget.vars.allow_delete %} - {% else %} - {% if widget.vars.disallow_delete is defined and widget.vars.disallow_delete %} - {% set allow_delete = false %} - {% else %} - {% set allow_delete = widget.parent.vars.allow_delete %} - {% endif %} - {% set form = widget %} - {% set name = widget.vars.full_name %} - {% set disabled = widget.parent.vars.disabled %} - {% endif %} - - {% set page_component_options = { 'disabled': not allow_delete } %} - - - {{ form_widget(form) }} - {% if allow_delete %} - - - - {% elseif widget.parent.vars.allow_delete %} - - {% endif %} - -{% endmacro %} - -{% block marello_product_channel_tax_relation_collection_form_widget %} - {% spaceless %} - {% if prototype is defined %} - {% set prototype_html = _self.marello_product_channel_tax_relation_collection_form_item_prototype(form) %} - {% endif %} - {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'marello-item-collection grid-container' }) %} - {% set id = id ~ '_collection' %} -
- {% set prototype_name = form.vars.prototype_name %} -
- - - - - - {% if form.vars.allow_delete %} - - {% endif %} - - - - {% if form.children|length %} - {% for child in form.children %} - {{ _self.marello_product_channel_tax_relation_collection_form_item_prototype(child) }} - {% endfor %} - {% elseif show_form_when_empty and prototype_html is defined %} - {{ prototype_html|replace({(prototype_name): '0'})|raw }} - {% endif %} - -
{{ 'marello.product.productchanneltaxrelation.sales_channel.label'|trans }}{{ 'marello.product.productchanneltaxrelation.tax_code.label'|trans }}
-
- - {% if allow_add %} - {{ form.vars.add_label|default('marello.product.productchanneltaxrelation.form.add_productchanneltaxrelation')|trans }} - {% endif %} -
- {% endspaceless %} -{% endblock %} - {% block marello_sales_saleschannel_select_widget %}
{{ form_widget(form) }} diff --git a/src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/channels.html.twig b/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannel/Datagrid/Property/channels.html.twig similarity index 100% rename from src/Marello/Bundle/ProductBundle/Resources/views/Product/Datagrid/Property/channels.html.twig rename to src/Marello/Bundle/SalesBundle/Resources/views/SalesChannel/Datagrid/Property/channels.html.twig diff --git a/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannel/update.html.twig b/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannel/update.html.twig index d9b9d6a24..9cb5fd641 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannel/update.html.twig +++ b/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannel/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% oro_title_set({params : {'%name%': entity.name, '%entityName%': 'marello.sales.saleschannel.entity_label'|trans }}) %} @@ -13,9 +14,9 @@ {{ parent() }} {{ UI.cancelButton(path('marello_sales_saleschannel_index')) }} - {% if entity.id and resource_granted('marello_sales_saleschannel_update') or resource_granted('marello_sales_saleschannel_create') %} + {% if entity.id and is_granted('marello_sales_saleschannel_update') or is_granted('marello_sales_saleschannel_create') %} {% set html = '' %} - {% if resource_granted('marello_sales_saleschannel_view') %} + {% if is_granted('marello_sales_saleschannel_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_sales_saleschannel_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannelGroup/index.html.twig b/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannelGroup/index.html.twig index 162a10543..96dcbede2 100644 --- a/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannelGroup/index.html.twig +++ b/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannelGroup/index.html.twig @@ -4,7 +4,7 @@ {% set pageTitle = 'marello.sales.saleschannelgroup.entity_plural_label'|trans %} {% block navButtons %} - {% if resource_granted('marello_sales_saleschannelgroup_create') %} + {% if is_granted('marello_sales_saleschannelgroup_create') %}
{{ UI.addButton({ 'path': path('marello_sales_saleschannelgroup_create'), diff --git a/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannelGroup/update.html.twig b/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannelGroup/update.html.twig index a6becdd24..60a03f27d 100755 --- a/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannelGroup/update.html.twig +++ b/src/Marello/Bundle/SalesBundle/Resources/views/SalesChannelGroup/update.html.twig @@ -1,5 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} - +{% import 'OroUIBundle::macros.html.twig' as UI %} {% set formAction = entity.id ? path('marello_sales_saleschannelgroup_update', {id: entity.id}) : path('marello_sales_saleschannelgroup_create') %} {% oro_title_set({params : {'%name%': entity.name, '%entityName%': 'marello.sales.saleschannelgroup.entity_label'|trans} }) %} @@ -7,9 +7,9 @@ {% block navButtons %} {{ parent() }} {{ UI.cancelButton(path('marello_sales_saleschannelgroup_index')) }} - {% if entity.id and resource_granted('marello_sales_saleschannelgroup_update') or resource_granted('marello_sales_saleschannelgroup_create') %} + {% if entity.id and is_granted('marello_sales_saleschannelgroup_update') or is_granted('marello_sales_saleschannelgroup_create') %} {% set html = '' %} - {% if resource_granted('marello_sales_saleschannelgroup_view') %} + {% if is_granted('marello_sales_saleschannelgroup_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_sales_saleschannelgroup_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/SalesBundle/Tests/Unit/Entity/SalesChannelTest.php b/src/Marello/Bundle/SalesBundle/Tests/Unit/Entity/SalesChannelTest.php index 8ca995035..478b9783e 100644 --- a/src/Marello/Bundle/SalesBundle/Tests/Unit/Entity/SalesChannelTest.php +++ b/src/Marello/Bundle/SalesBundle/Tests/Unit/Entity/SalesChannelTest.php @@ -27,8 +27,7 @@ public function testAccessors() ['channelType', 'some string'], ['createdAt', new \DateTime()], ['updatedAt', new \DateTime()], - ['localization', new Localization()], - ['locale', 'some string'] + ['localization', new Localization()] ]); } } diff --git a/src/Marello/Bundle/SalesBundle/Tests/Unit/EventListener/Doctrine/SalesChannelGroupListenerTest.php b/src/Marello/Bundle/SalesBundle/Tests/Unit/EventListener/Doctrine/SalesChannelGroupListenerTest.php index 523734e80..b39289f4b 100644 --- a/src/Marello/Bundle/SalesBundle/Tests/Unit/EventListener/Doctrine/SalesChannelGroupListenerTest.php +++ b/src/Marello/Bundle/SalesBundle/Tests/Unit/EventListener/Doctrine/SalesChannelGroupListenerTest.php @@ -10,6 +10,7 @@ use Marello\Bundle\SalesBundle\Entity\SalesChannel; use Marello\Bundle\SalesBundle\Entity\SalesChannelGroup; use Marello\Bundle\SalesBundle\EventListener\Doctrine\SalesChannelGroupListener; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Session; @@ -38,14 +39,14 @@ protected function setUp() * @dataProvider preRemoveDataProvider * @param int $flushQty * @param int $persistQty - * @param \PHPUnit_Framework_MockObject_MockObject|null $systemSalesChannelGroup - * @param \PHPUnit_Framework_MockObject_MockObject|null $systemLink + * @param MockObject|null $systemSalesChannelGroup + * @param MockObject|null $systemLink */ public function testPreRemove( $flushQty, $persistQty, - \PHPUnit_Framework_MockObject_MockObject $systemSalesChannelGroup = null, - \PHPUnit_Framework_MockObject_MockObject $systemLink = null + MockObject $systemSalesChannelGroup = null, + MockObject $systemLink = null ) { /** @var SalesChannel|\PHPUnit_Framework_MockObject_MockObject $salesChannel **/ $salesChannel = $this->createMock(SalesChannel::class); @@ -143,11 +144,11 @@ public function preRemoveDataProvider() /** * @dataProvider postPersistDataProvider * @param int $qty - * @param \PHPUnit_Framework_MockObject_MockObject|null $defaultLink + * @param MockObject|null $defaultLink */ - public function testPostPersist($qty, \PHPUnit_Framework_MockObject_MockObject $defaultLink = null) + public function testPostPersist($qty, MockObject $defaultLink = null) { - /** @var SalesChannelGroup|\PHPUnit_Framework_MockObject_MockObject $salesChannelGroup **/ + /** @var SalesChannelGroup|MockObject $salesChannelGroup **/ $salesChannelGroup = $this->createMock(SalesChannelGroup::class); $repository = $this->createMock(WarehouseChannelGroupLinkRepository::class); diff --git a/src/Marello/Bundle/SalesBundle/Tests/Unit/EventListener/Doctrine/SalesChannelListenerTest.php b/src/Marello/Bundle/SalesBundle/Tests/Unit/EventListener/Doctrine/SalesChannelListenerTest.php index f7ca4a9a4..060287816 100644 --- a/src/Marello/Bundle/SalesBundle/Tests/Unit/EventListener/Doctrine/SalesChannelListenerTest.php +++ b/src/Marello/Bundle/SalesBundle/Tests/Unit/EventListener/Doctrine/SalesChannelListenerTest.php @@ -5,6 +5,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\LifecycleEventArgs; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Marello\Bundle\SalesBundle\Entity\SalesChannel; @@ -29,9 +30,9 @@ protected function setUp() /** * @dataProvider prePersistDataProvider - * @param \PHPUnit_Framework_MockObject_MockObject|null $salesChannelGroup + * @param MockObject|null $salesChannelGroup */ - public function testPrePersist(\PHPUnit_Framework_MockObject_MockObject $salesChannelGroup = null) + public function testPrePersist(MockObject $salesChannelGroup = null) { $salesChannel = new SalesChannel(); @@ -48,7 +49,7 @@ public function testPrePersist(\PHPUnit_Framework_MockObject_MockObject $salesCh ->with(SalesChannelGroup::class) ->willReturn($repository); - /** @var LifecycleEventArgs|\PHPUnit_Framework_MockObject_MockObject $args **/ + /** @var LifecycleEventArgs|MockObject $args **/ $args = $this ->getMockBuilder(LifecycleEventArgs::class) ->disableOriginalConstructor() diff --git a/src/Marello/Bundle/SalesBundle/Tests/Unit/Form/Handler/SalesChannelGroupHandlerTest.php b/src/Marello/Bundle/SalesBundle/Tests/Unit/Form/Handler/SalesChannelGroupHandlerTest.php index 4fa485e36..2c652766c 100644 --- a/src/Marello/Bundle/SalesBundle/Tests/Unit/Form/Handler/SalesChannelGroupHandlerTest.php +++ b/src/Marello/Bundle/SalesBundle/Tests/Unit/Form/Handler/SalesChannelGroupHandlerTest.php @@ -5,6 +5,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Persistence\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\ParameterBag; @@ -122,10 +123,10 @@ public function testProcess() } /** - * @param \PHPUnit_Framework_MockObject_MockObject $group - * @return \PHPUnit_Framework_MockObject_MockObject + * @param MockObject $group + * @return MockObject */ - private function mockSalesChannel(\PHPUnit_Framework_MockObject_MockObject $group) + private function mockSalesChannel(MockObject $group) { $salesChannel = $this->createMock(SalesChannel::class); $salesChannel diff --git a/src/Marello/Bundle/SalesBundle/Tests/Unit/Provider/SalesChannelLocalizationProviderTest.php b/src/Marello/Bundle/SalesBundle/Tests/Unit/Provider/SalesChannelLocalizationProviderTest.php index 52be9e137..3ebdef4a5 100644 --- a/src/Marello/Bundle/SalesBundle/Tests/Unit/Provider/SalesChannelLocalizationProviderTest.php +++ b/src/Marello/Bundle/SalesBundle/Tests/Unit/Provider/SalesChannelLocalizationProviderTest.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\SalesBundle\Tests\Unit\Provider; -use Marello\Bundle\LocaleBundle\Model\LocaleAwareInterface; +use Marello\Bundle\LocaleBundle\Model\LocalizationAwareInterface; use Marello\Bundle\OrderBundle\Entity\Order; use Marello\Bundle\OrderBundle\Entity\OrderAwareInterface; use Marello\Bundle\SalesBundle\Entity\SalesChannel; @@ -60,7 +60,7 @@ public function testOrderAwareEntity() ->expects(static::once()) ->method('getSalesChannel') ->willReturn($salesChannel); - $entity = $this->createMock([OrderAwareInterface::class, LocaleAwareInterface::class]); + $entity = $this->createMock([OrderAwareInterface::class, LocalizationAwareInterface::class]); $entity ->expects(static::once()) ->method('getOrder') diff --git a/src/Marello/Bundle/SalesBundle/Tests/Unit/Twig/SalesExtensionTest.php b/src/Marello/Bundle/SalesBundle/Tests/Unit/Twig/SalesExtensionTest.php index b9821d346..88ce57f1f 100644 --- a/src/Marello/Bundle/SalesBundle/Tests/Unit/Twig/SalesExtensionTest.php +++ b/src/Marello/Bundle/SalesBundle/Tests/Unit/Twig/SalesExtensionTest.php @@ -7,6 +7,7 @@ use Marello\Bundle\SalesBundle\Entity\SalesChannel; use Marello\Bundle\SalesBundle\Twig\SalesExtension; use Marello\Bundle\SalesBundle\Entity\Repository\SalesChannelRepository; +use Twig\TwigFunction; class SalesExtensionTest extends TestCase { @@ -31,7 +32,7 @@ public function testGetFunctions() { static::assertEquals( [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_sales_has_active_channels', [$this->extension, 'checkActiveChannels'] ) diff --git a/src/Marello/Bundle/SalesBundle/Twig/SalesExtension.php b/src/Marello/Bundle/SalesBundle/Twig/SalesExtension.php index 70ea3332f..dc1926c1b 100644 --- a/src/Marello/Bundle/SalesBundle/Twig/SalesExtension.php +++ b/src/Marello/Bundle/SalesBundle/Twig/SalesExtension.php @@ -3,8 +3,10 @@ namespace Marello\Bundle\SalesBundle\Twig; use Marello\Bundle\SalesBundle\Entity\Repository\SalesChannelRepository; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; -class SalesExtension extends \Twig_Extension +class SalesExtension extends AbstractExtension { const NAME = 'marello_sales'; @@ -27,7 +29,7 @@ public function __construct(SalesChannelRepository $salesChannelRepository) public function getFunctions() { return [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_sales_has_active_channels', [$this, 'checkActiveChannels'] ) diff --git a/src/Marello/Bundle/ShippingBundle/Context/Builder/Basic/BasicShippingContextBuilder.php b/src/Marello/Bundle/ShippingBundle/Context/Builder/Basic/BasicShippingContextBuilder.php index 7610a09ff..d70f317ad 100644 --- a/src/Marello/Bundle/ShippingBundle/Context/Builder/Basic/BasicShippingContextBuilder.php +++ b/src/Marello/Bundle/ShippingBundle/Context/Builder/Basic/BasicShippingContextBuilder.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\ShippingBundle\Context\Builder\Basic; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\ShippingBundle\Context\Builder\ShippingContextBuilderInterface; use Marello\Bundle\ShippingBundle\Context\LineItem\Collection\ShippingLineItemCollectionInterface; use Marello\Bundle\ShippingBundle\Context\ShippingContext; diff --git a/src/Marello/Bundle/ShippingBundle/Context/Builder/ShippingContextBuilderInterface.php b/src/Marello/Bundle/ShippingBundle/Context/Builder/ShippingContextBuilderInterface.php index 4e69acf2b..2ad03755e 100644 --- a/src/Marello/Bundle/ShippingBundle/Context/Builder/ShippingContextBuilderInterface.php +++ b/src/Marello/Bundle/ShippingBundle/Context/Builder/ShippingContextBuilderInterface.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\ShippingBundle\Context\Builder; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\ShippingBundle\Context\LineItem\Collection\ShippingLineItemCollectionInterface; use Marello\Bundle\ShippingBundle\Context\ShippingContextInterface; use Oro\Bundle\CurrencyBundle\Entity\Price; diff --git a/src/Marello/Bundle/ShippingBundle/Context/ShippingContext.php b/src/Marello/Bundle/ShippingBundle/Context/ShippingContext.php index 19511dc4f..3fccf058c 100644 --- a/src/Marello/Bundle/ShippingBundle/Context/ShippingContext.php +++ b/src/Marello/Bundle/ShippingBundle/Context/ShippingContext.php @@ -3,7 +3,7 @@ namespace Marello\Bundle\ShippingBundle\Context; use Marello\Bundle\AddressBundle\Entity\MarelloAddress; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\ShippingBundle\Context\LineItem\Collection\ShippingLineItemCollectionInterface; use Oro\Bundle\CurrencyBundle\Entity\Price; use Symfony\Component\HttpFoundation\ParameterBag; diff --git a/src/Marello/Bundle/ShippingBundle/Context/ShippingContextInterface.php b/src/Marello/Bundle/ShippingBundle/Context/ShippingContextInterface.php index 8916d4575..2b6a25b64 100644 --- a/src/Marello/Bundle/ShippingBundle/Context/ShippingContextInterface.php +++ b/src/Marello/Bundle/ShippingBundle/Context/ShippingContextInterface.php @@ -3,7 +3,7 @@ namespace Marello\Bundle\ShippingBundle\Context; use Marello\Bundle\AddressBundle\Entity\MarelloAddress; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\ShippingBundle\Context\LineItem\Collection\ShippingLineItemCollectionInterface; use Oro\Bundle\CurrencyBundle\Entity\Price; diff --git a/src/Marello/Bundle/ShippingBundle/Controller/ShippingMethodsConfigsRuleController.php b/src/Marello/Bundle/ShippingBundle/Controller/ShippingMethodsConfigsRuleController.php index 397bd4be8..df524e7f9 100755 --- a/src/Marello/Bundle/ShippingBundle/Controller/ShippingMethodsConfigsRuleController.php +++ b/src/Marello/Bundle/ShippingBundle/Controller/ShippingMethodsConfigsRuleController.php @@ -8,16 +8,19 @@ use Marello\Bundle\ShippingBundle\Entity\ShippingMethodsConfigsRule; use Marello\Bundle\ShippingBundle\Form\Handler\ShippingMethodsConfigsRuleHandler; use Marello\Bundle\ShippingBundle\Form\Type\ShippingMethodsConfigsRuleType; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; +use Symfony\Component\Routing\Annotation\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -class ShippingMethodsConfigsRuleController extends Controller +class ShippingMethodsConfigsRuleController extends AbstractController { /** - * @Route("/", name="marello_shipping_methods_configs_rule_index") + * @Route( + * path="/", + * name="marello_shipping_methods_configs_rule_index" + * ) * @Template * @AclAncestor("marello_shipping_methods_configs_rule_view") * @@ -31,7 +34,10 @@ public function indexAction() } /** - * @Route("/create", name="marello_shipping_methods_configs_rule_create") + * @Route( + * path="/create", + * name="marello_shipping_methods_configs_rule_create" + * ) * @Template("MarelloShippingBundle:ShippingMethodsConfigsRule:update.html.twig") * @Acl( * id="marello_shipping_methods_configs_rule_create", @@ -49,7 +55,11 @@ public function createAction(Request $request) } /** - * @Route("/view/{id}", name="marello_shipping_methods_configs_rule_view", requirements={"id"="\d+"}) + * @Route( + * path="/view/{id}", + * name="marello_shipping_methods_configs_rule_view", + * requirements={"id"="\d+"} + * ) * @Template * @Acl( * id="marello_shipping_methods_configs_rule_view", @@ -70,7 +80,11 @@ public function viewAction(ShippingMethodsConfigsRule $shippingRule) } /** - * @Route("/update/{id}", name="marello_shipping_methods_configs_rule_update", requirements={"id"="\d+"}) + * @Route( + * path="/update/{id}", + * name="marello_shipping_methods_configs_rule_update", + * requirements={"id"="\d+"} + * ) * @Template * @Acl( * id="marello_shipping_methods_configs_rule_update", @@ -118,7 +132,10 @@ protected function update(ShippingMethodsConfigsRule $entity, Request $request) } /** - * @Route("/{gridName}/massAction/{actionName}", name="marello_status_shipping_rule_massaction") + * @Route( + * path="/{gridName}/massAction/{actionName}", + * name="marello_status_shipping_rule_massaction" + * ) * @Acl( * id="marello_shipping_methods_configs_rule_update", * type="entity", diff --git a/src/Marello/Bundle/ShippingBundle/Entity/HasShipmentTrait.php b/src/Marello/Bundle/ShippingBundle/Entity/HasShipmentTrait.php index b6c67ea9f..3cbfe6288 100644 --- a/src/Marello/Bundle/ShippingBundle/Entity/HasShipmentTrait.php +++ b/src/Marello/Bundle/ShippingBundle/Entity/HasShipmentTrait.php @@ -12,7 +12,7 @@ trait HasShipmentTrait protected $shipment; /** - * @return Shipment + * @return Shipment|null */ public function getShipment() { @@ -24,7 +24,7 @@ public function getShipment() * * @return $this */ - public function setShipment(?Shipment $shipment = null) + public function setShipment(Shipment $shipment = null) { $this->shipment = $shipment; diff --git a/src/Marello/Bundle/ShippingBundle/Entity/Repository/ShippingMethodsConfigsRuleRepository.php b/src/Marello/Bundle/ShippingBundle/Entity/Repository/ShippingMethodsConfigsRuleRepository.php index 33fe3bda4..dfc09113a 100755 --- a/src/Marello/Bundle/ShippingBundle/Entity/Repository/ShippingMethodsConfigsRuleRepository.php +++ b/src/Marello/Bundle/ShippingBundle/Entity/Repository/ShippingMethodsConfigsRuleRepository.php @@ -116,7 +116,7 @@ public function disableRulesWithoutShippingMethods() if (0 < count($rules)) { $enabledRulesIds = array_column($rules, 'id'); $qb = $this->createQueryBuilder('methodsConfigsRule'); - $qb->update('OroRuleBundle:Rule', 'rule') + $qb->update('MarelloRuleBundle:Rule', 'rule') ->set('rule.enabled', ':newValue') ->setParameter('newValue', false) ->where($qb->expr()->in('rule.id', ':rules')) diff --git a/src/Marello/Bundle/ShippingBundle/Form/Type/ShippingMethodsConfigsRuleType.php b/src/Marello/Bundle/ShippingBundle/Form/Type/ShippingMethodsConfigsRuleType.php index 2174ed34f..f4ae56066 100755 --- a/src/Marello/Bundle/ShippingBundle/Form/Type/ShippingMethodsConfigsRuleType.php +++ b/src/Marello/Bundle/ShippingBundle/Form/Type/ShippingMethodsConfigsRuleType.php @@ -2,20 +2,18 @@ namespace Marello\Bundle\ShippingBundle\Form\Type; +use Marello\Bundle\RuleBundle\Form\Type\RuleType; +use Marello\Bundle\ShippingBundle\Entity\ShippingMethodsConfigsRule; +use Marello\Bundle\ShippingBundle\Provider\ShippingMethodChoicesProviderInterface; +use Oro\Bundle\CurrencyBundle\Form\Type\CurrencySelectionType; +use Oro\Bundle\FormBundle\Form\Type\CollectionType; +use Oro\Bundle\FormBundle\Form\Type\OroChoiceType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Translation\TranslatorInterface; - -use Oro\Bundle\FormBundle\Form\Type\OroChoiceType; -use Oro\Bundle\FormBundle\Form\Type\CollectionType; -use Oro\Bundle\CurrencyBundle\Form\Type\CurrencySelectionType; - -use Marello\Bundle\RuleBundle\Form\Type\RuleType; -use Marello\Bundle\ShippingBundle\Entity\ShippingMethodsConfigsRule; -use Marello\Bundle\ShippingBundle\Provider\ShippingMethodChoicesProviderInterface; +use Symfony\Contracts\Translation\TranslatorInterface; class ShippingMethodsConfigsRuleType extends AbstractType { diff --git a/src/Marello/Bundle/ShippingBundle/Integration/ShippingAwareInterface.php b/src/Marello/Bundle/ShippingBundle/Integration/ShippingAwareInterface.php index e339ee7ea..17ad1d940 100644 --- a/src/Marello/Bundle/ShippingBundle/Integration/ShippingAwareInterface.php +++ b/src/Marello/Bundle/ShippingBundle/Integration/ShippingAwareInterface.php @@ -6,7 +6,14 @@ interface ShippingAwareInterface { + /** + * @return Shipment|null + */ public function getShipment(); - public function setShipment(?Shipment $shipment); + /** + * @param Shipment|null $shipment + * @return $this + */ + public function setShipment(Shipment $shipment = null); } diff --git a/src/Marello/Bundle/ShippingBundle/Method/Configuration/AllowUnlistedShippingMethodConfigurationInterface.php b/src/Marello/Bundle/ShippingBundle/Method/Configuration/AllowUnlistedShippingMethodConfigurationInterface.php deleted file mode 100644 index 312035993..000000000 --- a/src/Marello/Bundle/ShippingBundle/Method/Configuration/AllowUnlistedShippingMethodConfigurationInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -get(self::FIELD_ALLOW_UNLISTED_SHIPPING_METHOD, false); - } - - /** - * {@inheritdoc} - */ - public function isShippingMethodLocked() - { - return $this->get(self::FIELD_IS_SHIPPING_METHOD_LOCKED, false); - } - - /** - * {@inheritdoc} - */ - public function isOverriddenShippingCost() - { - return $this->get(self::FIELD_IS_OVERRIDDEN_SHIPPING_COST, false); - } - - /** - * {@inheritdoc} - */ - public function getShippingMethod() - { - return $this->get(self::FIELD_SHIPPING_METHOD); - } - - /** - * {@inheritdoc} - */ - public function getShippingMethodType() - { - return $this->get(self::FIELD_SHIPPING_METHOD_TYPE); - } - - /** - * {@inheritdoc} - */ - public function getShippingCost() - { - return $this->get(self::FIELD_SHIPPING_COST); - } -} diff --git a/src/Marello/Bundle/ShippingBundle/Method/Configuration/Composed/ComposedShippingMethodConfigurationBuilder.php b/src/Marello/Bundle/ShippingBundle/Method/Configuration/Composed/ComposedShippingMethodConfigurationBuilder.php deleted file mode 100644 index 02d581e40..000000000 --- a/src/Marello/Bundle/ShippingBundle/Method/Configuration/Composed/ComposedShippingMethodConfigurationBuilder.php +++ /dev/null @@ -1,145 +0,0 @@ -shippingMethod) { - $params[ComposedShippingMethodConfiguration::FIELD_SHIPPING_METHOD] = $this->shippingMethod; - } - - if (null !== $this->shippingMethodType) { - $params[ComposedShippingMethodConfiguration::FIELD_SHIPPING_METHOD_TYPE] = $this->shippingMethodType; - } - - if (null !== $this->shippingCost) { - $params[ComposedShippingMethodConfiguration::FIELD_SHIPPING_COST] = $this->shippingCost; - } - - if (null !== $this->isOverriddenCost) { - $params[ComposedShippingMethodConfiguration::FIELD_IS_OVERRIDDEN_SHIPPING_COST] = $this->isOverriddenCost; - } - - if (null !== $this->isShippingMethodLocked) { - $params[ComposedShippingMethodConfiguration::FIELD_IS_SHIPPING_METHOD_LOCKED] = - $this->isShippingMethodLocked; - } - - if (null !== $this->isAllowUnlistedShippingMethod) { - $params[ComposedShippingMethodConfiguration::FIELD_ALLOW_UNLISTED_SHIPPING_METHOD] = - $this->isAllowUnlistedShippingMethod; - } - - return new ComposedShippingMethodConfiguration($params); - } - - /** - * {@inheritdoc} - */ - public function buildShippingMethod( - PreConfiguredShippingMethodConfigurationInterface $preConfiguredShippingMethodConfiguration - ) { - $this->shippingMethod = $preConfiguredShippingMethodConfiguration->getShippingMethod(); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function buildShippingMethodType( - PreConfiguredShippingMethodConfigurationInterface $preConfiguredShippingMethodConfiguration - ) { - $this->shippingMethodType = $preConfiguredShippingMethodConfiguration->getShippingMethodType(); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function buildShippingCost( - PreConfiguredShippingMethodConfigurationInterface $preConfiguredShippingMethodConfiguration - ) { - $this->shippingCost = $preConfiguredShippingMethodConfiguration->getShippingCost(); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function buildIsOverriddenCost( - OverriddenCostShippingMethodConfigurationInterface $overriddenCostShippingMethodConfiguration - ) { - $this->isOverriddenCost = $overriddenCostShippingMethodConfiguration->isOverriddenShippingCost(); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function buildIsShippingMethodLocked( - MethodLockedShippingMethodConfigurationInterface $methodLockedShippingMethodConfiguration - ) { - $this->isShippingMethodLocked = $methodLockedShippingMethodConfiguration->isShippingMethodLocked(); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function buildIsAllowUnlistedShippingMethod( - AllowUnlistedShippingMethodConfigurationInterface $allowUnlistedShippingMethodConfiguration - ) { - $this->isAllowUnlistedShippingMethod = $allowUnlistedShippingMethodConfiguration - ->isAllowUnlistedShippingMethod(); - - return $this; - } -} diff --git a/src/Marello/Bundle/ShippingBundle/Method/Configuration/Composed/ComposedShippingMethodConfigurationBuilderFactory.php b/src/Marello/Bundle/ShippingBundle/Method/Configuration/Composed/ComposedShippingMethodConfigurationBuilderFactory.php deleted file mode 100644 index b92157933..000000000 --- a/src/Marello/Bundle/ShippingBundle/Method/Configuration/Composed/ComposedShippingMethodConfigurationBuilderFactory.php +++ /dev/null @@ -1,15 +0,0 @@ - {{ UI.addButton({ 'path': path('marello_shipping_methods_configs_rule_create'), diff --git a/src/Marello/Bundle/ShippingBundle/Resources/views/ShippingMethodsConfigsRule/update.html.twig b/src/Marello/Bundle/ShippingBundle/Resources/views/ShippingMethodsConfigsRule/update.html.twig index 330dc9c24..e1289c9b4 100755 --- a/src/Marello/Bundle/ShippingBundle/Resources/views/ShippingMethodsConfigsRule/update.html.twig +++ b/src/Marello/Bundle/ShippingBundle/Resources/views/ShippingMethodsConfigsRule/update.html.twig @@ -1,5 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} - +{% import 'OroUIBundle::macros.html.twig' as UI %} {% oro_title_set({params : {"%id%": entity.id|default('N/A'|trans)} }) %} {% set formAction = entity.id ? path('marello_shipping_methods_configs_rule_update', { 'id': entity.id }) : path('marello_shipping_methods_configs_rule_create') %} @@ -15,9 +15,9 @@ {{ parent() }} {% else %} {% set breadcrumbs = { - 'indexLabel': 'Create', - 'entityTitle': 'Shipping Rule', - 'indexPath': path('marello_shipping_methods_configs_rule_create') + 'indexLabel': 'Create', + 'entityTitle': 'Shipping Rule', + 'indexPath': path('marello_shipping_methods_configs_rule_create') } %} {% set title = 'oro.ui.create_entity'|trans({'%entityName%': 'marello.shipping.shippingmethodsconfigsrule.entity_label'|trans}) %} {% include 'OroUIBundle::page_title_block.html.twig' with { title: title } %} @@ -32,13 +32,13 @@ {% set html = '' %} {% if is_granted('marello_shipping_methods_configs_rule_view') %} {% set html = UI.saveAndCloseButton({ - 'route': 'marello_shipping_methods_configs_rule_view', - 'params': {'id': '$id'} + 'route': 'marello_shipping_methods_configs_rule_view', + 'params': {'id': '$id'} }) %} {% endif %} {% set html = html ~ UI.saveAndStayButton({ - 'route': 'marello_shipping_methods_configs_rule_update', - 'params': {'id': '$id'} + 'route': 'marello_shipping_methods_configs_rule_update', + 'params': {'id': '$id'} }) %} {{ UI.dropdownSaveButton({'html': html}) }} @@ -106,12 +106,6 @@ ] }] }, - { - 'title': 'marello.shipping.sections.shippingrule_conditions'|trans, - 'subblocks': [{ - 'data': [] - }] - }, { 'title': 'marello.shipping.sections.shippingrule_configurations.label'|trans, 'content_attr': { diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/Controller/ShippingMethodsConfigsRuleControllerTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Controller/ShippingMethodsConfigsRuleControllerTest.php new file mode 100644 index 000000000..74b38b9eb --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Controller/ShippingMethodsConfigsRuleControllerTest.php @@ -0,0 +1,516 @@ +initClient(); + $this->client->useHashNavigation(true); + $this->loadFixtures( + [ + LoadShippingMethodsConfigsRulesWithConfigs::class, + LoadUserData::class + ] + ); + $this->shippingMethodProvider = static::getContainer()->get('marello_shipping.shipping_method_provider'); + $this->translator = static::getContainer()->get('translator'); + } + + public function testIndexWithoutCreate() + { + $this->initClient([], static::generateBasicAuthHeader(LoadUserData::USER_VIEWER, LoadUserData::USER_VIEWER)); + $crawler = $this->client->request('GET', $this->getUrl('marello_shipping_methods_configs_rule_index')); + $result = $this->client->getResponse(); + static::assertHtmlResponseStatusCodeEquals($result, 200); + static::assertEquals(0, $crawler->selectLink('Create Shipping Rule')->count()); + } + + /** + * @return string + */ + public function testCreate() + { + $this->initClient( + [], + static::generateBasicAuthHeader(LoadUserData::USER_VIEWER_CREATOR, LoadUserData::USER_VIEWER_CREATOR) + ); + $crawler = $this->client->request('GET', $this->getUrl('marello_shipping_methods_configs_rule_create')); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + /** @var Form $form */ + $form = $crawler + ->selectButton('Save and Close') + ->form(); + + $name = 'New Rule'; + + $formValues = $form->getPhpValues(); + $formValues['marello_shipping_methods_configs_rule']['rule']['name'] = $name; + $formValues['marello_shipping_methods_configs_rule']['rule']['enabled'] = false; + $formValues['marello_shipping_methods_configs_rule']['currency'] = 'USD'; + $formValues['marello_shipping_methods_configs_rule']['rule']['sortOrder'] = 1; + $formValues['marello_shipping_methods_configs_rule']['destinations'] = + [ + [ + 'postalCodes' => '54321', + 'country' => 'FR', + 'region' => 'FR-75' + ] + ]; + $formValues['marello_shipping_methods_configs_rule']['methodConfigs'] = + [ + [ + 'method' => $this->getManualShippingIdentifier(), + 'options' => '', + 'typeConfigs' => [ + [ + 'enabled' => '1', + 'type' => 'primary', + 'options' => [ + 'price' => 12, + 'handling_fee' => null, + 'type' => 'per_item', + ], + ] + ] + ] + ]; + + $this->client->followRedirects(true); + $crawler = $this->client->request($form->getMethod(), $form->getUri(), $formValues); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + + $html = $crawler->html(); + + $this->assertContains('Shipping rule has been saved', $html); + $this->assertContains('No', $html); + + return $name; + } + + /** + * @depends testCreate + * + * @param string $name + */ + public function testIndex($name) + { + $auth = static::generateBasicAuthHeader(LoadUserData::USER_VIEWER_CREATOR, LoadUserData::USER_VIEWER_CREATOR); + $this->initClient([], $auth); + $crawler = $this->client->request('GET', $this->getUrl('marello_shipping_methods_configs_rule_index')); + $result = $this->client->getResponse(); + static::assertHtmlResponseStatusCodeEquals($result, 200); + static::assertContains('marello-shipping-methods-configs-rule-grid', $crawler->html()); + $href = $crawler->selectLink('Create Shipping Rule')->attr('href'); + static::assertEquals($this->getUrl('marello_shipping_methods_configs_rule_create'), $href); + + $response = $this->client->requestGrid( + [ + 'gridName' => 'marello-shipping-methods-configs-rule-grid', + 'marello-shipping-methods-configs-rule-grid[_sort_by][id]' => 'ASC', + ] + ); + + static::getJsonResponseContent($response, 200); + } + + /** + * @depends testCreate + * + * @param string $name + */ + public function testView($name) + { + $this->initClient([], static::generateBasicAuthHeader()); + $shippingRule = $this->getShippingMethodsConfigsRuleByName($name); + + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_shipping_methods_configs_rule_view', ['id' => $shippingRule->getId()]) + ); + + $result = $this->client->getResponse(); + static::assertHtmlResponseStatusCodeEquals($result, 200); + + $html = $crawler->html(); + + $this->assertContains($shippingRule->getRule()->getName(), $html); + $destination = $shippingRule->getDestinations(); + $this->assertContains((string)$destination[0], $html); + $methodConfigs = $shippingRule->getMethodConfigs(); + $label = $this->shippingMethodProvider + ->getShippingMethod($methodConfigs[0]->getMethod()) + ->getLabel(); + $this->assertContains($this->translator->trans($label), $html); + } + + /** + * @depends testCreate + * + * @param string $name + * + * @return ShippingMethodsConfigsRule|object|null + */ + public function testUpdate($name) + { + $shippingRule = $this->getShippingMethodsConfigsRuleByName($name); + + $this->assertNotEmpty($shippingRule); + + $id = $shippingRule->getId(); + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_shipping_methods_configs_rule_update', ['id' => $id]) + ); + + /** @var Form $form */ + $form = $crawler->selectButton('Save and Close')->form(); + + $newName = 'New name for new rule'; + $formValues = $form->getPhpValues(); + $formValues['marello_shipping_methods_configs_rule']['rule']['name'] = $newName; + $formValues['marello_shipping_methods_configs_rule']['rule']['enabled'] = false; + $formValues['marello_shipping_methods_configs_rule']['currency'] = 'USD'; + $formValues['marello_shipping_methods_configs_rule']['rule']['sortOrder'] = 1; + $formValues['marello_shipping_methods_configs_rule']['destinations'] = + [ + [ + 'postalCodes' => '54321', + 'country' => 'TH', + 'region' => 'TH-83' + ] + ]; + $formValues['marello_shipping_methods_configs_rule']['methodConfigs'] = + [ + [ + 'method' => $this->getManualShippingIdentifier(), + 'options' => '', + 'typeConfigs' => [ + [ + 'enabled' => '1', + 'type' => 'primary', + 'options' => [ + 'price' => 24, + 'handling_fee' => null, + 'type' => 'per_order', + ], + ] + ] + ] + ]; + + $this->client->followRedirects(true); + $crawler = $this->client->request($form->getMethod(), $form->getUri(), $formValues); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + $html = $crawler->html(); + static::assertContains('Shipping rule has been saved', $html); + + $shippingRule = $this->getShippingMethodsConfigsRuleByName($newName); + static::assertEquals($id, $shippingRule->getId()); + + $destination = $shippingRule->getDestinations(); + static::assertEquals('TH', $destination[0]->getCountry()->getIso2Code()); + static::assertEquals('TH-83', $destination[0]->getRegion()->getCombinedCode()); + static::assertEquals('54321', $destination[0]->getPostalCodes()->current()->getName()); + $methodConfigs = $shippingRule->getMethodConfigs(); + static::assertEquals($this->getManualShippingIdentifier(), $methodConfigs[0]->getMethod()); + static::assertEquals( + 24, + $methodConfigs[0]->getTypeConfigs()[0]->getOptions()['price'] + ); + static::assertFalse($shippingRule->getRule()->isEnabled()); + + return $shippingRule; + } + + /** + * @depends testUpdate + * + * @param ShippingMethodsConfigsRule $shippingRule + */ + public function testCancel(ShippingMethodsConfigsRule $shippingRule) + { + $shippingRule = $this->getShippingMethodsConfigsRuleByName($shippingRule->getRule()->getName()); + + $this->assertNotEmpty($shippingRule); + + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_shipping_methods_configs_rule_update', ['id' => $shippingRule->getId()]) + ); + + $link = $crawler->selectLink('Cancel')->link(); + $this->client->click($link); + $response = $this->client->getResponse(); + + static::assertHtmlResponseStatusCodeEquals($response, 200); + + $html = $response->getContent(); + + static::assertContains($shippingRule->getRule()->getName(), $html); + $destination = $shippingRule->getDestinations(); + static::assertContains((string)$destination[0], $html); + $methodConfigs = $shippingRule->getMethodConfigs(); + $label = $this->shippingMethodProvider + ->getShippingMethod($methodConfigs[0]->getMethod()) + ->getLabel(); + static::assertContains($this->translator->trans($label), $html); + } + + /** + * @depends testUpdate + * + * @param ShippingMethodsConfigsRule $shippingRule + * + * @return ShippingMethodsConfigsRule + */ + public function testUpdateRemoveDestination(ShippingMethodsConfigsRule $shippingRule) + { + $this->assertNotEmpty($shippingRule); + + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_shipping_methods_configs_rule_update', ['id' => $shippingRule->getId()]) + ); + + /** @var Form $form */ + $form = $crawler->selectButton('Save and Close')->form(); + + $formValues = $form->getPhpValues(); + $formValues['marello_shipping_methods_configs_rule']['destinations'] = []; + + $this->client->followRedirects(true); + $this->client->request($form->getMethod(), $form->getUri(), $formValues); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + $shippingRule = $this->getEntityManager()->find( + 'MarelloShippingBundle:ShippingMethodsConfigsRule', + $shippingRule->getId() + ); + static::assertCount(0, $shippingRule->getDestinations()); + + return $shippingRule; + } + + public function testStatusDisableMass() + { + $this->initClient([], static::generateBasicAuthHeader()); + /** @var ShippingMethodsConfigsRule $shippingRule1 */ + $shippingRule1 = $this->getReference('shipping_rule.1'); + /** @var ShippingMethodsConfigsRule $shippingRule2 */ + $shippingRule2 = $this->getReference('shipping_rule.2'); + $url = $this->getUrl( + 'marello_status_shipping_rule_massaction', + [ + 'gridName' => 'marello-shipping-methods-configs-rule-grid', + 'actionName' => 'disable', + 'inset' => 1, + 'values' => sprintf( + '%s,%s', + $shippingRule1->getId(), + $shippingRule2->getId() + ) + ] + ); + $this->ajaxRequest('GET', $url); + $result = $this->client->getResponse(); + $data = json_decode($result->getContent(), true); + $this->assertTrue($data['successful']); + $this->assertSame(2, $data['count']); + $this->assertFalse( + $this + ->getShippingMethodsConfigsRuleById($shippingRule1->getId()) + ->getRule() + ->isEnabled() + ); + $this->assertFalse( + $this + ->getShippingMethodsConfigsRuleById($shippingRule2->getId()) + ->getRule() + ->isEnabled() + ); + } + + /** + * @depends testStatusDisableMass + */ + public function testStatusEnableMass() + { + $this->initClient([], static::generateBasicAuthHeader()); + /** @var ShippingMethodsConfigsRule $shippingRule1 */ + $shippingRule1 = $this->getReference('shipping_rule.1'); + /** @var ShippingMethodsConfigsRule $shippingRule2 */ + $shippingRule2 = $this->getReference('shipping_rule.2'); + $url = $this->getUrl( + 'marello_status_shipping_rule_massaction', + [ + 'gridName' => 'marello-shipping-methods-configs-rule-grid', + 'actionName' => 'enable', + 'inset' => 1, + 'values' => sprintf( + '%s,%s', + $shippingRule1->getId(), + $shippingRule2->getId() + ) + ] + ); + $this->ajaxRequest('GET', $url); + $result = $this->client->getResponse(); + $data = json_decode($result->getContent(), true); + $this->assertTrue($data['successful']); + $this->assertSame(2, $data['count']); + $this->assertTrue( + $this + ->getShippingMethodsConfigsRuleById($shippingRule1->getId()) + ->getRule() + ->isEnabled() + ); + $this->assertTrue( + $this + ->getShippingMethodsConfigsRuleById($shippingRule2->getId()) + ->getRule() + ->isEnabled() + ); + } + + public function testShippingMethodsConfigsRuleEditWOPermission() + { + $authParams = static::generateBasicAuthHeader(LoadUserData::USER_VIEWER, LoadUserData::USER_VIEWER); + $this->initClient([], $authParams); + + /** @var ShippingMethodsConfigsRule $shippingRule */ + $shippingRule = $this->getReference('shipping_rule.1'); + + $this->client->request( + 'GET', + $this->getUrl('marello_shipping_methods_configs_rule_update', ['id' => $shippingRule->getId()]) + ); + + static::assertJsonResponseStatusCodeEquals($this->client->getResponse(), 403); + } + + public function testShippingMethodsConfigsRuleEdit() + { + $authParams = static::generateBasicAuthHeader(LoadUserData::USER_EDITOR, LoadUserData::USER_EDITOR); + $this->initClient([], $authParams); + + /** @var ShippingMethodsConfigsRule $shippingRule */ + $shippingRule = $this->getReference('shipping_rule.1'); + + $crawler = $this->client->request( + 'GET', + $this->getUrl('marello_shipping_methods_configs_rule_update', ['id' => $shippingRule->getId()]) + ); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + + /** @var Form $form */ + $form = $crawler->selectButton('Save')->form(); + + $rule = $shippingRule->getRule(); + $form['marello_shipping_methods_configs_rule[rule][enabled]'] = !$rule->isEnabled(); + $form['marello_shipping_methods_configs_rule[rule][name]'] = $rule->getName() . ' new name'; + $form['marello_shipping_methods_configs_rule[rule][sortOrder]'] = $rule->getSortOrder() + 1; + $form['marello_shipping_methods_configs_rule[currency]'] = $shippingRule->getCurrency() === 'USD' ? 'EUR' : 'USD'; + $form['marello_shipping_methods_configs_rule[rule][stopProcessing]'] = !$rule->isStopProcessing(); + $form['marello_shipping_methods_configs_rule[destinations][0][postalCodes]'] = '11111'; + $form['marello_shipping_methods_configs_rule[methodConfigs][0][typeConfigs][0][options][price]'] = 12; + $form['marello_shipping_methods_configs_rule[methodConfigs][0][typeConfigs][0][enabled]'] = true; + + $this->client->followRedirects(true); + $crawler = $this->client->submit($form); + + static::assertHtmlResponseStatusCodeEquals($this->client->getResponse(), 200); + static::assertContains('Shipping rule has been saved', $crawler->html()); + } + + public function testDeleteButtonNotVisible() + { + $authParams = static::generateBasicAuthHeader(LoadUserData::USER_VIEWER, LoadUserData::USER_VIEWER); + $this->initClient([], $authParams); + + $response = $this->client->requestGrid( + ['gridName' => 'marello-shipping-methods-configs-rule-grid'], + [], + true + ); + + $result = static::getJsonResponseContent($response, 200); + + $this->assertEquals(false, isset($result['metadata']['massActions']['delete'])); + } + + /** + * @return ObjectManager|null + */ + protected function getEntityManager() + { + return static::getContainer() + ->get('doctrine') + ->getManagerForClass('MarelloShippingBundle:ShippingMethodsConfigsRule'); + } + + /** + * @param string $name + * + * @return ShippingMethodsConfigsRule|null + */ + protected function getShippingMethodsConfigsRuleByName($name) + { + /** @var RuleInterface $rule */ + $rule = $this + ->getEntityManager() + ->getRepository('MarelloRuleBundle:Rule') + ->findOneBy(['name' => $name]); + + return $this + ->getEntityManager() + ->getRepository('MarelloShippingBundle:ShippingMethodsConfigsRule') + ->findOneBy(['rule' => $rule]); + } + + /** + * @param int $id + * + * @return ShippingMethodsConfigsRule|null + */ + protected function getShippingMethodsConfigsRuleById($id) + { + return $this->getEntityManager() + ->getRepository('MarelloShippingBundle:ShippingMethodsConfigsRule') + ->find($id); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodConfigsWithFakeMethods.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodConfigsWithFakeMethods.php new file mode 100644 index 000000000..04a52eba8 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodConfigsWithFakeMethods.php @@ -0,0 +1,83 @@ +getShippingMethodConfigsData() as $reference => $data) { + $this->loadShippingMethodConfig($reference, $data, $manager); + } + + $manager->flush(); + } + + /** + * @return array + */ + protected function getShippingMethodConfigsData() + { + return Yaml::parse(file_get_contents(__DIR__.'/data/shipping_method_configs_with_fake_methods.yml')); + } + + /** + * @param string $reference + * @param array $data + * @param ObjectManager $manager + */ + private function loadShippingMethodConfig($reference, $data, ObjectManager $manager) + { + $methodsConfigsRule = $this->getShippingMethodsConfigsRule($data['methods_configs_rule']); + + $methodConfig = $this->createMethodConfig($methodsConfigsRule, $data['method']); + + $manager->persist($methodConfig); + + $this->setReference($reference, $methodConfig); + } + + /** + * @param ShippingMethodsConfigsRule $configsRule + * @param string $method + * + * @return ShippingMethodConfig + */ + private function createMethodConfig(ShippingMethodsConfigsRule $configsRule, $method) + { + $methodConfig = new ShippingMethodConfig(); + + return $methodConfig->setMethodConfigsRule($configsRule) + ->setMethod($method); + } + + /** + * @param string $reference + * + * @return ShippingMethodsConfigsRule|object + */ + private function getShippingMethodsConfigsRule($reference) + { + return $this->getReference($reference); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodTypeConfigsWithFakeTypes.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodTypeConfigsWithFakeTypes.php new file mode 100644 index 000000000..32758593a --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodTypeConfigsWithFakeTypes.php @@ -0,0 +1,83 @@ +getShippingMethodTypeConfigsData() as $reference => $data) { + $this->loadShippingMethodTypeConfig($reference, $data, $manager); + } + + $manager->flush(); + } + + /** + * @return array + */ + protected function getShippingMethodTypeConfigsData() + { + return Yaml::parse(file_get_contents(__DIR__.'/data/shipping_method_type_configs_with_fake_types.yml')); + } + + /** + * @param string $reference + * @param array $data + * @param ObjectManager $manager + */ + private function loadShippingMethodTypeConfig($reference, $data, ObjectManager $manager) + { + $methodConfig = $this->getShippingMethodConfig($data['method_config']); + + $typeConfig = $this->createMethodTypeConfig($methodConfig, $data['type']); + + $manager->persist($typeConfig); + + $this->setReference($reference, $typeConfig); + } + + /** + * @param ShippingMethodConfig $methodConfig + * @param string $type + * + * @return ShippingMethodTypeConfig + */ + private function createMethodTypeConfig(ShippingMethodConfig $methodConfig, $type) + { + $configRule = new ShippingMethodTypeConfig(); + + return $configRule->setMethodConfig($methodConfig) + ->setType($type); + } + + /** + * @param string $reference + * + * @return ShippingMethodConfig|object + */ + private function getShippingMethodConfig($reference) + { + return $this->getReference($reference); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodsConfigsRules.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodsConfigsRules.php new file mode 100644 index 000000000..4d1b12eb2 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodsConfigsRules.php @@ -0,0 +1,79 @@ +getShippingMethodsConfigsRulesData() as $reference => $data) { + $this->loadShippingMethodsConfigsRule($reference, $data, $manager); + } + + $manager->flush(); + } + + /** + * @return array + */ + protected function getShippingMethodsConfigsRulesData() + { + return Yaml::parse(file_get_contents(__DIR__.'/data/shipping_methods_configs_rules.yml')); + } + + /** + * @param string $reference + * @param array $data + * @param ObjectManager $manager + */ + private function loadShippingMethodsConfigsRule($reference, $data, ObjectManager $manager) + { + $rule = $this->buildRule($reference, $data['rule']); + + $configRule = $this->createMethodsConfigsRule($rule, $data['currency']); + + $manager->persist($configRule); + + $this->setReference($reference, $configRule); + } + + /** + * @param RuleInterface $rule + * @param $currency + * + * @return ShippingMethodsConfigsRule + */ + private function createMethodsConfigsRule(RuleInterface $rule, $currency) + { + $configRule = new ShippingMethodsConfigsRule(); + + return $configRule->setRule($rule) + ->setCurrency($currency); + } + + /** + * @param string $reference + * @param array $ruleData + * + * @return RuleInterface + */ + private function buildRule($reference, $ruleData) + { + $rule = new Rule(); + + return $rule->setName($reference) + ->setEnabled($ruleData['enabled']) + ->setSortOrder($ruleData['sortOrder']) + ->setExpression($ruleData['expression']); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodsConfigsRulesWithConfigs.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodsConfigsRulesWithConfigs.php new file mode 100644 index 000000000..deda75a6e --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadShippingMethodsConfigsRulesWithConfigs.php @@ -0,0 +1,223 @@ +getShippingRuleData() as $reference => $data) { + $this->loadShippingRule($reference, $data, $manager); + } + + $manager->flush(); + } + + /** + * @return array + */ + protected function getShippingRuleData(): array + { + return Yaml::parse(file_get_contents(__DIR__.'/data/shipping_methods_configs_rules_with_configs.yml')); + } + + /** + * @param string $reference + * @param array $data + * @param ObjectManager $manager + */ + private function loadShippingRule($reference, $data, ObjectManager $manager) + { + $rule = $this->buildRule($reference, $data, $manager); + $configRule = $this->buildMethodsConfigsRule($reference, $data, $rule, $manager); + + $this->setReference($reference, $configRule); + + $manager->persist($configRule); + } + + /** + * @param string $reference + * @param array $data + * @param RuleInterface $rule + * @param ObjectManager $manager + * + * @return ShippingMethodsConfigsRule + */ + protected function buildMethodsConfigsRule( + string $reference, + array $data, + RuleInterface $rule, + ObjectManager $manager + ) { + $configRule = new ShippingMethodsConfigsRule(); + + $configRule + ->setRule($rule) + ->setCurrency($data['currency']) + ->setOrganization($this->getOrganization()); + + $this->setDestinations($configRule, $manager, $data); + $this->setMethodConfigs($configRule, $manager, $data); + + return $configRule; + } + + /** + * @param string $reference + * @param array $data + * @param ObjectManager $manager + * + * @return Rule + */ + protected function buildRule(string $reference, array $data, ObjectManager $manager) + { + $rule = new Rule(); + + $rule->setName($reference) + ->setEnabled($data['rule']['enabled']) + ->setSortOrder($data['rule']['sortOrder']) + ->setExpression($data['rule']['expression']); + + return $rule; + } + + /** + * @param ShippingMethodsConfigsRule $configRule + * @param ObjectManager $manager + * @param array $data + */ + private function setDestinations(ShippingMethodsConfigsRule $configRule, ObjectManager $manager, $data) + { + if (!array_key_exists('destinations', $data)) { + return; + } + + foreach ($data['destinations'] as $destination) { + /** @var Country $country */ + $country = $manager + ->getRepository('OroAddressBundle:Country') + ->findOneBy(['iso2Code' => $destination['country']]); + + $shippingRuleDestination = new ShippingMethodsConfigsRuleDestination(); + $shippingRuleDestination + ->setMethodConfigsRule($configRule) + ->setCountry($country); + + if (array_key_exists('region', $destination)) { + /** @var Region $region */ + $region = $manager + ->getRepository('OroAddressBundle:Region') + ->findOneBy(['combinedCode' => $destination['country'].'-'.$destination['region']]); + $shippingRuleDestination->setRegion($region); + } + + if (array_key_exists('postalCodes', $destination)) { + foreach ($destination['postalCodes'] as $postalCode) { + $destinationPostalCode = new ShippingMethodsConfigsRuleDestinationPostalCode(); + $destinationPostalCode->setName($postalCode['name']) + ->setDestination($shippingRuleDestination); + + $shippingRuleDestination->addPostalCode($destinationPostalCode); + } + } + + $manager->persist($shippingRuleDestination); + $configRule->addDestination($shippingRuleDestination); + } + } + + /** + * @param ShippingMethodsConfigsRule $configRule + * @param ObjectManager $manager + * @param array $data + */ + private function setMethodConfigs(ShippingMethodsConfigsRule $configRule, ObjectManager $manager, $data) + { + if (!array_key_exists('methodConfigs', $data)) { + return; + } + + foreach ($data['methodConfigs'] as $methodConfigData) { + $methodConfig = $this->buildMethodConfig($configRule); + + foreach ($methodConfigData['typeConfigs'] as $typeConfigData) { + $typeConfig = new ShippingMethodTypeConfig(); + $typeConfig->setType('primary') + ->setOptions([ + 'price' => $typeConfigData['options']['price'], + 'handling_fee' => null, + 'type' => $typeConfigData['options']['type'], + ]); + $typeConfig->setEnabled($typeConfigData['enabled']); + $methodConfig->addTypeConfig($typeConfig); + } + + $configRule->addMethodConfig($methodConfig); + + $manager->persist($methodConfig); + } + } + + /** + * @param ShippingMethodsConfigsRule $configRule + * + * @return ShippingMethodConfig + */ + private function buildMethodConfig(ShippingMethodsConfigsRule $configRule) + { + $methodConfig = new ShippingMethodConfig(); + + $methodConfig + ->setMethodConfigsRule($configRule) + ->setMethod($this->getManualShippingIdentifier()); + + return $methodConfig; + } + + /** + * @return Organization + */ + private function getOrganization() + { + return $this->container->get('doctrine') + ->getRepository('OroOrganizationBundle:Organization') + ->getFirst(); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadUserData.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadUserData.php new file mode 100644 index 000000000..662127d6b --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/LoadUserData.php @@ -0,0 +1,113 @@ + [ + [ + 'class' => ShippingMethodsConfigsRule::class, + 'acls' => [ + [ + AbstractLoadMultipleUserData::ACL_PERMISSION => 'VIEW', + AbstractLoadMultipleUserData::ACL_LEVEL => 'SYSTEM', + ], + ], + ], + [ + 'class' => Channel::class, + 'acls' => [ + [ + AbstractLoadMultipleUserData::ACL_PERMISSION => 'VIEW', + AbstractLoadMultipleUserData::ACL_LEVEL => 'SYSTEM', + ], + ], + ], + ], + self::ROLE_EDIT => [ + [ + 'class' => ShippingMethodsConfigsRule::class, + 'acls' => [ + [ + AbstractLoadMultipleUserData::ACL_PERMISSION => 'EDIT', + AbstractLoadMultipleUserData::ACL_LEVEL => 'SYSTEM', + ], + ], + ], + ], + self::ROLE_CREATE => [ + [ + 'class' => ShippingMethodsConfigsRule::class, + 'acls' => [ + [ + AbstractLoadMultipleUserData::ACL_PERMISSION => 'CREATE', + AbstractLoadMultipleUserData::ACL_LEVEL => 'SYSTEM', + ], + ], + ], + ], + ]; + + /** + * @var array + */ + protected $users = [ + [ + 'email' => 'shipping-user-viewer@example.com', + 'username' => self::USER_VIEWER, + 'password' => self::USER_VIEWER, + 'firstname' => 'ShippingUser1FN', + 'lastname' => 'ShippingUser1LN', + 'roles' => [self::ROLE_VIEW], + ], + [ + 'email' => 'shipping-user-editor@example.com', + 'username' => self::USER_EDITOR, + 'password' => self::USER_EDITOR, + 'firstname' => 'ShippingUser2FN', + 'lastname' => 'ShippingUser2LN', + 'roles' => [self::ROLE_VIEW, self::ROLE_EDIT], + ], + [ + 'email' => 'shipping-user-viewer-creator@example.com', + 'username' => self::USER_VIEWER_CREATOR, + 'password' => self::USER_VIEWER_CREATOR, + 'firstname' => 'ShippingUser2FN', + 'lastname' => 'ShippingUser2LN', + 'roles' => [self::ROLE_VIEW, self::ROLE_CREATE], + ] + ]; + + /** + * {@inheritdoc} + */ + protected function getRolesData() + { + return $this->roles; + } + + /** + * {@inheritdoc} + */ + protected function getUsersData() + { + return $this->users; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_method_configs_with_fake_methods.yml b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_method_configs_with_fake_methods.yml new file mode 100644 index 000000000..92cc165ec --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_method_configs_with_fake_methods.yml @@ -0,0 +1,15 @@ +shipping_rule.1.method_config.1: + methods_configs_rule: shipping_rule.1 + method: ups + +shipping_rule.2.method_config.1: + methods_configs_rule: shipping_rule.2 + method: flat_rate + +shipping_rule.3.method_config.1: + methods_configs_rule: shipping_rule.3 + method: dpd + +shipping_rule.3.method_config_without_type_configs: + methods_configs_rule: shipping_rule.3 + method: ups diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_method_type_configs_with_fake_types.yml b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_method_type_configs_with_fake_types.yml new file mode 100644 index 000000000..838270dee --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_method_type_configs_with_fake_types.yml @@ -0,0 +1,11 @@ +shipping_rule.1.method_config.1.type_config_1: + method_config: shipping_rule.1.method_config.1 + type: 3day + +shipping_rule.2.method_config.1.type_config_1: + method_config: shipping_rule.2.method_config.1 + type: primary + +shipping_rule.3.method_config.1.type_config_1: + method_config: shipping_rule.3.method_config.1 + type: classic diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_methods_configs_rules.yml b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_methods_configs_rules.yml new file mode 100644 index 000000000..15dd70cb3 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_methods_configs_rules.yml @@ -0,0 +1,27 @@ +shipping_rule.1: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'EUR' + +shipping_rule.2: + rule: + enabled: true + sortOrder: 1 + expression: 'true' + currency: 'EUR' + +shipping_rule.3: + rule: + enabled: false + sortOrder: 2 + expression: 'true' + currency: 'EUR' + +shipping_rule.4: + rule: + enabled: true + sortOrder: 3 + expression: 'true' + currency: 'EUR' diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_methods_configs_rules_with_configs.yml b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_methods_configs_rules_with_configs.yml new file mode 100644 index 000000000..76fc7a445 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/DataFixtures/data/shipping_methods_configs_rules_with_configs.yml @@ -0,0 +1,205 @@ +shipping_rule.1: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_order' + price: 10 + destinations: + - + postalCodes: + - + name: '12345' + country: 'US' + region: 'NY' +shipping_rule.2: + rule: + enabled: true + sortOrder: 1 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + country: 'US' + - + country: 'FR' + region: 'FR-75' +shipping_rule.3: + rule: + enabled: false + sortOrder: 2 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + country: 'US' + region: 'NY' + - + country: 'FR' +shipping_rule.4: + rule: + enabled: true + sortOrder: 2 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + postalCodes: + - + name: '12345' + - + name: '12346' + country: 'US' + region: 'NY' +shipping_rule.5: + rule: + enabled: true + sortOrder: 2 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 +shipping_rule.6: + rule: + enabled: true + sortOrder: 2 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + country: 'FR' +shipping_rule.7: + rule: + enabled: true + sortOrder: 3 + expression: 'true' + currency: 'EUR' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_item' + price: 10 + destinations: + - + postalCodes: + - + name: '12346' + country: 'US' + region: 'NY' +shipping_rule.8: + rule: + enabled: true + sortOrder: 3 + expression: 'true' + currency: 'EUR' + destinations: + - + country: 'US' + region: 'AL' +shipping_rule.9: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'USD' + methodConfigs: + - + typeConfigs: + - + enabled: true + options: + type: 'per_order' + price: 10 +shipping_rule.10: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'UAH' +shipping_rule.11: + rule: + enabled: false + sortOrder: 0 + expression: 'true' + currency: 'UAH' +shipping_rule.12: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'UAH' + destinations: + - + country: 'US' + region: 'NY' +shipping_rule_without_type_configs: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'USD' + methodConfigs: + - + typeConfigs: [] +shipping_rule_with_disabled_type_configs: + rule: + enabled: true + sortOrder: 0 + expression: 'true' + currency: 'USD' + methodConfigs: + - + typeConfigs: + - + enabled: false + options: + type: 'per_order' + price: 10 diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/Entity/Repository/ShippingMethodConfigRepositoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Entity/Repository/ShippingMethodConfigRepositoryTest.php new file mode 100644 index 000000000..206e6b72f --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Entity/Repository/ShippingMethodConfigRepositoryTest.php @@ -0,0 +1,90 @@ +initClient([], static::generateBasicAuthHeader()); + $this->loadFixtures([ + LoadShippingMethodTypeConfigsWithFakeTypes::class, + ]); + + $this->repository = static::getContainer()->get('doctrine') + ->getRepository('MarelloShippingBundle:ShippingMethodConfig'); + } + + public function testDeleteByMethod() + { + $method = 'ups'; + + static::assertNotEmpty($this->repository->findByMethod($method)); + + $this->repository->deleteByMethod($method); + + static::assertEmpty($this->repository->findByMethod($method)); + } + + public function testFindMethodConfigIdsWithoutTypeConfigs() + { + $methodConfig = $this->getReference('shipping_rule.3.method_config_without_type_configs'); + + static::assertEmpty($methodConfig->getTypeConfigs()); + + $ids = $this->repository->findIdsWithoutTypeConfigs(); + + static::assertEquals([$methodConfig->getId()], $ids); + } + + public function testDeleteMethodConfigByIds() + { + $ids = [ + $this->getReference('shipping_rule.3.method_config_without_type_configs')->getId(), + ]; + + $this->repository->deleteByIds($ids); + + static::assertEmpty($this->repository->findBy(['id' => $ids])); + } + + public function testFindByType() + { + $actualConfigs = $this->repository->findByMethod('flat_rate'); + + $expectedConfig = $this->getReference('shipping_rule.2.method_config.1'); + + static::assertContains($expectedConfig, $actualConfigs); + } + + public function testFindByTypes() + { + $methods = [ + 'ups', + 'flat_rate', + ]; + + $actualConfigs = $this->repository->findByMethod($methods); + + $expectedConfigs = [ + $this->getReference('shipping_rule.1.method_config.1'), + $this->getReference('shipping_rule.2.method_config.1'), + ]; + + foreach ($expectedConfigs as $expectedConfig) { + static::assertContains($expectedConfig, $actualConfigs); + } + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/Entity/Repository/ShippingMethodTypeConfigRepositoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Entity/Repository/ShippingMethodTypeConfigRepositoryTest.php new file mode 100644 index 000000000..cd3ee44d5 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Entity/Repository/ShippingMethodTypeConfigRepositoryTest.php @@ -0,0 +1,92 @@ +initClient([], static::generateBasicAuthHeader()); + $this->loadFixtures([ + LoadShippingMethodsConfigsRulesWithConfigs::class + ]); + + $this->repository = static::getContainer()->get('doctrine') + ->getRepository('MarelloShippingBundle:ShippingMethodTypeConfig'); + } + + public function testFindShippingMethodTypeConfigConfigsByMethodAndType() + { + $ids = $this->repository->findIdsByMethodAndType( + $this->getManualShippingIdentifier(), + $this->getManualShippingPrimaryIdentifier() + ); + + static::assertContains($this->getFirstTypeId('shipping_rule.1'), $ids); + static::assertContains($this->getFirstTypeId('shipping_rule.2'), $ids); + } + + /** + * @param string $ruleReference + * @return int + */ + private function getFirstTypeId($ruleReference) + { + /** @var ShippingMethodConfig $methodConfig */ + $methodConfig = $this->getReference($ruleReference)->getMethodConfigs()->first(); + return $methodConfig->getTypeConfigs()->first()->getId(); + } + + public function testDeleteMethodConfigByIds() + { + $ids = [ + $this->getFirstTypeId('shipping_rule.1'), + $this->getFirstTypeId('shipping_rule.2'), + ]; + + static::assertCount(2, $this->repository->findBy(['id' => $ids])); + + $this->repository->deleteByIds($ids); + + static::assertEmpty($this->repository->findBy(['id' => $ids])); + } + + public function testFindEnabledByMethodIdentifier() + { + $method = $this->getManualShippingIdentifier(); + + $actual = $this->repository->findEnabledByMethodIdentifier($method); + + static::assertContains($this->getFirstType('shipping_rule.4'), $actual); + static::assertContains($this->getFirstType('shipping_rule.9'), $actual); + static::assertNotContains($this->getFirstType('shipping_rule_without_type_configs'), $actual); + static::assertNotContains($this->getFirstType('shipping_rule_with_disabled_type_configs'), $actual); + } + + /** + * @param string $ruleReference + * + * @return ShippingMethodTypeConfig + */ + private function getFirstType($ruleReference) + { + /** @var ShippingMethodConfig $methodConfig */ + $methodConfig = $this->getReference($ruleReference)->getMethodConfigs()->first(); + + return $methodConfig->getTypeConfigs()->first(); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/Entity/Repository/ShippingMethodsConfigsRuleRepositoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Entity/Repository/ShippingMethodsConfigsRuleRepositoryTest.php new file mode 100644 index 000000000..54107e419 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Entity/Repository/ShippingMethodsConfigsRuleRepositoryTest.php @@ -0,0 +1,248 @@ +initClient([], static::generateBasicAuthHeader()); + $this->client->useHashNavigation(true); + + $this->loadFixtures([ + LoadShippingMethodsConfigsRulesWithConfigs::class, + ]); + + $this->em = static::getContainer()->get('doctrine') + ->getManagerForClass('MarelloShippingBundle:ShippingMethodsConfigsRule'); + $this->repository = $this->em->getRepository('MarelloShippingBundle:ShippingMethodsConfigsRule'); + } + + /** + * @param array $entities + * + * @return array + */ + private function getEntitiesIds(array $entities) + { + return array_map(function ($entity) { + return $entity->getId(); + }, $entities); + } + + /** + * @dataProvider getByDestinationAndCurrencyDataProvider + * + * @param array $shippingAddressData + * @param string $currency + * @param ShippingMethodsConfigsRule[] $expectedRules + */ + public function testGetByDestinationAndCurrency(array $shippingAddressData, $currency, array $expectedRules) + { + $expectedRulesIds = $this->getEntitiesIds($this->getEntitiesByReferences($expectedRules)); + $actualRules = $this->repository->getByDestinationAndCurrency( + $this->createShippingAddress($shippingAddressData), + $currency + ); + + $this->assertEquals($expectedRulesIds, $this->getEntitiesIds($actualRules)); + } + + /** + * @return array + */ + public function getByDestinationAndCurrencyDataProvider() + { + return [ + [ + 'shippingAddress' => [ + 'country' => 'US', + 'region' => [ + 'combinedCode' => 'US-NY', + 'code' => 'NY', + ], + 'postalCode' => '12345', + ], + 'currency' => 'EUR', + 'expectedRulesIds' => [ + 'shipping_rule.1', + 'shipping_rule.2', + 'shipping_rule.3', + 'shipping_rule.4', + 'shipping_rule.5', + ] + ], + ]; + } + + public function testGetByCurrencyWithoutDestination() + { + $currency = 'UAH'; + $expectedRules = $this->getEntitiesByReferences([ + 'shipping_rule.10', + 'shipping_rule.11' + ]); + + $actualRules = $this->repository->getByCurrencyWithoutDestination($currency); + + $this->assertEquals($this->getEntitiesIds($expectedRules), $this->getEntitiesIds($actualRules)); + } + + public function testGetRulesWithoutShippingMethods() + { + $rulesWithoutShippingMethods = $this->repository->getRulesWithoutShippingMethods(); + $enabledRulesWithoutShippingMethods = $this->repository->getRulesWithoutShippingMethods(true); + + static::assertCount(4, $rulesWithoutShippingMethods); + static::assertCount(3, $enabledRulesWithoutShippingMethods); + } + + public function testDisableRulesWithoutShippingMethods() + { + $this->repository->disableRulesWithoutShippingMethods(); + + $rulesWithoutShippingMethods = $this->repository->getRulesWithoutShippingMethods(); + $enabledRulesWithoutShippingMethods = $this->repository->getRulesWithoutShippingMethods(true); + + static::assertCount(4, $rulesWithoutShippingMethods); + static::assertCount(0, $enabledRulesWithoutShippingMethods); + } + + public function testGetRulesByMethod() + { + $rulesByExistingMethod = $this->repository->getRulesByMethod($this->getManualShippingIdentifier()); + + $expectedRuleReferences = [ + 'shipping_rule.1', + 'shipping_rule.2', + 'shipping_rule.3', + 'shipping_rule.4', + 'shipping_rule.5', + 'shipping_rule.6', + 'shipping_rule.7', + 'shipping_rule.9', + 'shipping_rule_without_type_configs', + 'shipping_rule_with_disabled_type_configs', + ]; + foreach ($expectedRuleReferences as $expectedRuleReference) { + static::assertContains($this->getReference($expectedRuleReference), $rulesByExistingMethod); + } + + $rulesByNotExistingMethod = $this->repository->getRulesByMethod('not_existing_method'); + static::assertCount(0, $rulesByNotExistingMethod); + } + + /** + * @dataProvider getEnabledRulesByMethodDataProvider + * + * @param string[] $expectedRuleReferences + */ + public function testGetEnabledRulesByMethod(array $expectedRuleReferences) + { + $actualRules = $this->repository->getEnabledRulesByMethod($this->getManualShippingIdentifier()); + + foreach ($expectedRuleReferences as $expectedRuleReference) { + static::assertContains($this->getReference($expectedRuleReference), $actualRules); + } + } + + /** + * @return array + */ + public function getEnabledRulesByMethodDataProvider() + { + return [ + [ + 'expectedRuleReferences' => [ + 'shipping_rule.1', + 'shipping_rule.2', + 'shipping_rule.4', + 'shipping_rule.5', + 'shipping_rule.6', + 'shipping_rule.7', + 'shipping_rule.9', + 'shipping_rule_without_type_configs', + 'shipping_rule_with_disabled_type_configs', + ] + ] + ]; + } + + /** + * @param array $rules + * + * @return array + */ + protected function getEntitiesByReferences(array $rules) + { + return array_map(function ($ruleReference) { + return $this->getReference($ruleReference); + }, $rules); + } + + /** + * @param array $data + * + * @return AddressInterface|object + */ + protected function createShippingAddress(array $data) + { + return $this->getEntity(ShippingAddressStub::class, [ + 'country' => new Country($data['country']), + 'region' => $this->getEntity( + Region::class, + [ + 'code' => $data['region']['code'], + ], + [ + 'combinedCode' => $data['region']['combinedCode'], + ] + ), + 'postalCode' => $data['postalCode'], + ]); + } + + public function testGetByCurrency() + { + $expectedRules = $this->getEntitiesByReferences([ + 'shipping_rule.10', + 'shipping_rule.11', + 'shipping_rule.12' + ]); + + $this->assertEquals( + $this->getEntitiesIds($expectedRules), + $this->getEntitiesIds($this->repository->getByCurrency('UAH')) + ); + } + + public function testGetByCurrencyWhenCurrencyNotExists() + { + $this->assertEmpty($this->repository->getByCurrency('WON')); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Functional/Helper/ManualShippingIntegrationTrait.php b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Helper/ManualShippingIntegrationTrait.php new file mode 100644 index 000000000..ceaf1316d --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Functional/Helper/ManualShippingIntegrationTrait.php @@ -0,0 +1,42 @@ +getChannelReference(); + + return sprintf('manual_shipping_%s', $channel->getId()); + } + + /** + * @return string + */ + protected function getManualShippingPrimaryIdentifier() + { + return 'primary'; + } + + /** + * @return Channel + */ + protected function getChannelReference() + { + return $this->getReference(LoadManualShippingIntegration::REFERENCE_MANUAL_SHIPPING); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Checker/ShippingMethodEnabledByIdentifierCheckerTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Checker/ShippingMethodEnabledByIdentifierCheckerTest.php new file mode 100644 index 000000000..32ebfdf1a --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Checker/ShippingMethodEnabledByIdentifierCheckerTest.php @@ -0,0 +1,85 @@ +method = $this->createMock(ShippingMethodInterface::class); + + $this->shippingMethodProvider = $this->createMock(ShippingMethodProviderInterface::class); + + $this->shippingMethodEnabledByIdentifierChecker = new ShippingMethodEnabledByIdentifierChecker( + $this->shippingMethodProvider + ); + } + + public function testIsEnabledForEnabledMethod() + { + $identifier = 'shipping_method_1'; + + $this->method + ->expects(static::once()) + ->method('isEnabled') + ->willReturn(true); + + $this->shippingMethodProvider + ->expects(static::any()) + ->method('getShippingMethod') + ->with($identifier) + ->willReturn($this->method); + + $this->assertTrue($this->shippingMethodEnabledByIdentifierChecker->isEnabled($identifier)); + } + + public function testIsEnabledForDisabledMethod() + { + $identifier = 'shipping_method_1'; + + $this->method + ->expects(static::once()) + ->method('isEnabled') + ->willReturn(false); + + $this->shippingMethodProvider + ->expects(static::any()) + ->method('getShippingMethod') + ->with($identifier) + ->willReturn($this->method); + + $this->assertFalse($this->shippingMethodEnabledByIdentifierChecker->isEnabled($identifier)); + } + + public function testIsEnabledForNotExistingMethod() + { + $identifier = 'shipping_method_1'; + + $this->shippingMethodProvider + ->expects(static::any()) + ->method('getShippingMethod') + ->with($identifier) + ->willReturn(null); + + $this->assertFalse($this->shippingMethodEnabledByIdentifierChecker->isEnabled($identifier)); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Checker/ShippingRuleEnabledCheckerTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Checker/ShippingRuleEnabledCheckerTest.php new file mode 100644 index 000000000..100460f0a --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Checker/ShippingRuleEnabledCheckerTest.php @@ -0,0 +1,63 @@ +methodEnabledChecker = $this->createMock( + ShippingMethodEnabledByIdentifierCheckerInterface::class + ); + + $this->ruleChecker = new ShippingRuleEnabledChecker($this->methodEnabledChecker); + } + + public function testCanBeEnabledForOneEnabledMethod() + { + $this->methodEnabledChecker->expects(static::at(1)) + ->method('isEnabled') + ->willReturn(true); + + $rule = $this->getRuleMock(); + + static::assertTrue($this->ruleChecker->canBeEnabled($rule)); + } + + public function testCanBeEnabledForNoEnabledMethods() + { + $rule = $this->getRuleMock(); + + static::assertFalse($this->ruleChecker->canBeEnabled($rule)); + } + + /** + * @return ShippingMethodsConfigsRule|\PHPUnit\Framework\MockObject\MockObject + */ + private function getRuleMock() + { + $rule = $this->createMock(ShippingMethodsConfigsRule::class); + $rule->expects(static::any()) + ->method('getMethodConfigs') + ->willReturn([ + new ShippingMethodConfig(), new ShippingMethodConfig() + ]); + + return $rule; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Condition/HasApplicableShippingMethodsTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Condition/HasApplicableShippingMethodsTest.php new file mode 100644 index 000000000..56c8103b6 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Condition/HasApplicableShippingMethodsTest.php @@ -0,0 +1,144 @@ +shippingMethodProvider = $this->createMock(ShippingMethodProviderInterface::class); + + $this->shippingPriceProvider = $this + ->getMockBuilder(ShippingPriceProvider::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->condition = new HasApplicableShippingMethods( + $this->shippingMethodProvider, + $this->shippingPriceProvider + ); + } + + protected function tearDown() + { + unset($this->condition, $this->shippingMethodProvider); + } + + public function testGetName() + { + $this->assertEquals(HasApplicableShippingMethods::NAME, $this->condition->getName()); + } + + /** + * @expectedException \Oro\Component\ConfigExpression\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "shippingContext" option + */ + public function testInitializeInvalid() + { + $this->assertInstanceOf( + 'Oro\Component\ConfigExpression\Condition\AbstractCondition', + $this->condition->initialize([]) + ); + } + + public function testInitialize() + { + $this->assertInstanceOf( + 'Oro\Component\ConfigExpression\Condition\AbstractCondition', + $this->condition->initialize([self::METHOD, new \stdClass()]) + ); + } + + /** + * @dataProvider evaluateProvider + * @param array $methods + * @param bool $expected + */ + public function testEvaluate($methods, $expected) + { + $method = $this->createMock('Marello\Bundle\ShippingBundle\Method\ShippingMethodInterface'); + $this->shippingMethodProvider->expects($this->any())->method('getShippingMethod')->willReturn($method); + + $this->shippingPriceProvider->expects($this->once()) + ->method('getApplicableMethodsViews') + ->willReturn($methods); + + $this->condition->initialize(['shippingContext' => new ShippingContext([])]); + $this->assertEquals($expected, $this->condition->evaluate([])); + } + + /** + * @return array + */ + public function evaluateProvider() + { + return [ + 'no_rules_no_methods' => [ + 'methods' => [], + 'expected' => false, + ], + 'with_rules_no_methods' => [ + 'methods' => [], + 'expected' => false, + ], + 'with_rules_and_methods' => [ + 'methods' => ['flat_rate'], + 'expected' => true, + ], + ]; + } + + public function testToArray() + { + $stdClass = new \stdClass(); + $this->condition->initialize(['shippingContext' => $stdClass]); + $result = $this->condition->toArray(); + + $key = '@' . HasApplicableShippingMethods::NAME; + + $this->assertInternalType('array', $result); + $this->assertArrayHasKey($key, $result); + + $resultSection = $result[$key]; + $this->assertInternalType('array', $resultSection); + $this->assertArrayHasKey('parameters', $resultSection); + $this->assertContains($stdClass, $resultSection['parameters']); + } + + public function testCompile() + { + $toStringStub = new ToStringStub(); + $options = ['shippingContext' => $toStringStub]; + + $this->condition->initialize($options); + $result = $this->condition->compile('$factory'); + $this->assertEquals( + sprintf( + '$factory->create(\'%s\', [%s])', + HasApplicableShippingMethods::NAME, + $toStringStub + ), + $result + ); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Condition/ShippingMethodHasShippingRulesTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Condition/ShippingMethodHasShippingRulesTest.php new file mode 100644 index 000000000..689a5f61a --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Condition/ShippingMethodHasShippingRulesTest.php @@ -0,0 +1,148 @@ +repository = $this->getMockBuilder(ShippingMethodsConfigsRuleRepository::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->propertyPath = $this->createMock(PropertyPathInterface::class); + $this->propertyPath->expects($this->any()) + ->method('__toString') + ->will($this->returnValue(self::PROPERTY_PATH_NAME)); + $this->propertyPath->expects($this->any()) + ->method('getElements') + ->will($this->returnValue([self::PROPERTY_PATH_NAME])); + + $this->shippingMethodHasShippingRulesCondition = new ShippingMethodHasShippingRules($this->repository); + } + + public function testGetName() + { + $this->assertEquals( + ShippingMethodHasShippingRules::NAME, + $this->shippingMethodHasShippingRulesCondition->getName() + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Missing "method_identifier" option + */ + public function testInitializeInvalid() + { + $this->assertInstanceOf( + ShippingMethodHasShippingRules::class, + $this->shippingMethodHasShippingRulesCondition->initialize([]) + ); + } + + public function testInitialize() + { + $this->assertInstanceOf( + ShippingMethodHasShippingRules::class, + $this->shippingMethodHasShippingRulesCondition->initialize(['method_identifier']) + ); + } + + /** + * @dataProvider evaluateProvider + * + * @param ShippingMethodsConfigsRule[] $rules + * @param bool $expected + */ + public function testEvaluate($rules, $expected) + { + $this->repository->expects(static::once()) + ->method('getRulesByMethod') + ->willReturn($rules); + + $this->shippingMethodHasShippingRulesCondition->initialize(['method_identifier']); + $this->assertEquals($expected, $this->shippingMethodHasShippingRulesCondition->evaluate([])); + } + + /** + * @return array + */ + public function evaluateProvider() + { + return [ + 'no_rules' => [ + 'rules' => [], + 'expected' => false, + ], + 'with_rules' => [ + 'rules' => [ + new ShippingMethodsConfigsRule(), + new ShippingMethodsConfigsRule(), + ], + 'expected' => true, + ], + ]; + } + + public function testToArray() + { + $result = $this->shippingMethodHasShippingRulesCondition->initialize([$this->propertyPath])->toArray(); + + $this->assertEquals( + sprintf('$%s', self::PROPERTY_PATH_NAME), + $result['@marello_shipping_method_has_shipping_rules']['parameters'][0] + ); + } + + public function testCompile() + { + $result = $this->shippingMethodHasShippingRulesCondition->compile('$factoryAccessor'); + + $this->assertContains('$factoryAccessor->create(\'marello_shipping_method_has_shipping_rules\'', $result); + } + + public function testSetContextAccessor() + { + /** @var ContextAccessorInterface|\PHPUnit\Framework\MockObject\MockObject $contextAccessor * */ + $contextAccessor = $this->getMockBuilder(ContextAccessorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->shippingMethodHasShippingRulesCondition->setContextAccessor($contextAccessor); + + $reflection = new \ReflectionProperty( + get_class($this->shippingMethodHasShippingRulesCondition), + 'contextAccessor' + ); + $reflection->setAccessible(true); + + $this->assertInstanceOf( + get_class($contextAccessor), + $reflection->getValue($this->shippingMethodHasShippingRulesCondition) + ); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Condition/ToStringStub.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Condition/ToStringStub.php new file mode 100644 index 000000000..a2aff3af0 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Condition/ToStringStub.php @@ -0,0 +1,12 @@ +priceMock = $this->createMock(Price::class); + + $this->productHolderMock = $this->createMock(OrderItem::class); + + $this->productHolderMock->method('getId')->willReturn(static::TEST_ENTITY_ID); + + $this->productMock = $this->createMock(Product::class); + + $this->productMock->method('getSku')->willReturn(static::TEST_PRODUCT_SKU); + $this->productMock->method('getId')->willReturn(static::TEST_PRODUCT_ID); + } + + /** + * @return array + */ + protected function getShippingLineItemParams() + { + return [ + ShippingLineItem::FIELD_PRICE => $this->priceMock, + ShippingLineItem::FIELD_QUANTITY => self::TEST_QUANTITY, + ShippingLineItem::FIELD_PRODUCT_HOLDER => $this->productHolderMock, + ShippingLineItem::FIELD_PRODUCT => $this->productMock, + ShippingLineItem::FIELD_PRODUCT_SKU => self::TEST_PRODUCT_SKU, + ShippingLineItem::FIELD_WEIGHT => self::TEST_WEIGHT, + ShippingLineItem::FIELD_ENTITY_IDENTIFIER => self::TEST_ENTITY_ID, + ]; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/Builder/Basic/BasicShippingContextBuilderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/Builder/Basic/BasicShippingContextBuilderTest.php new file mode 100644 index 000000000..aa37a1356 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/Builder/Basic/BasicShippingContextBuilderTest.php @@ -0,0 +1,188 @@ +customerMock = $this->createMock(Customer::class); + $this->lineItemsCollectionMock = $this->createMock(ShippingLineItemCollectionInterface::class); + $this->billingAddressMock = $this->createMock(AddressInterface::class); + $this->shippingAddressMock = $this->createMock(AddressInterface::class); + $this->shippingOriginMock = $this->createMock(AddressInterface::class); + $this->subtotalMock = $this->createMock(Price::class); + $this->sourceEntityMock = $this->createMock(Order::class); + $this->shippingOriginMock = $this->createMock(AddressInterface::class); + } + + public function testFullContextBuilding() + { + $paymentMethod = 'paymentMethod'; + $currency = 'usd'; + $entityId = '12'; + + $builder = new BasicShippingContextBuilder( + $this->sourceEntityMock, + $entityId + ); + + $builder + ->setCurrency($currency) + ->setSubTotal($this->subtotalMock) + ->setLineItems($this->lineItemsCollectionMock) + ->setShippingAddress($this->shippingAddressMock) + ->setBillingAddress($this->billingAddressMock) + ->setCustomer($this->customerMock) + ->setPaymentMethod($paymentMethod) + ->setShippingOrigin($this->shippingOriginMock); + + $expectedContext = $this->getExpectedFullContext( + $paymentMethod, + $currency, + $entityId, + $this->shippingOriginMock + ); + $context = $builder->getResult(); + + $this->assertEquals($expectedContext, $context); + } + + public function testOptionalFields() + { + $entityId = '12'; + + $builder = new BasicShippingContextBuilder( + $this->sourceEntityMock, + $entityId + ); + $builder->setShippingOrigin($this->shippingOriginMock); + + $expectedContext = $this->getExpectedContextWithoutOptionalFields( + $entityId, + $this->shippingOriginMock + ); + + $context = $builder->getResult(); + + $this->assertEquals($expectedContext, $context); + } + + public function testWithoutOrigin() + { + $paymentMethod = 'paymentMethod'; + $currency = 'usd'; + $entityId = '12'; + + $builder = new BasicShippingContextBuilder( + $this->sourceEntityMock, + $entityId + ); + + $builder + ->setCurrency($currency) + ->setSubTotal($this->subtotalMock) + ->setLineItems($this->lineItemsCollectionMock) + ->setShippingAddress($this->shippingAddressMock) + ->setBillingAddress($this->billingAddressMock) + ->setCustomer($this->customerMock) + ->setPaymentMethod($paymentMethod); + + $expectedContext = $this->getExpectedFullContext( + $paymentMethod, + $currency, + $entityId, + null + ); + $context = $builder->getResult(); + + $this->assertEquals($expectedContext, $context); + } + + /** + * @param string $paymentMethod + * @param string $currency + * @param int $entityId + * @param AddressInterface|null $shippingOrigin + * + * @return ShippingContext + */ + private function getExpectedFullContext($paymentMethod, $currency, $entityId, AddressInterface $shippingOrigin = null) + { + $params = [ + ShippingContext::FIELD_CUSTOMER => $this->customerMock, + ShippingContext::FIELD_LINE_ITEMS => $this->lineItemsCollectionMock, + ShippingContext::FIELD_BILLING_ADDRESS => $this->billingAddressMock, + ShippingContext::FIELD_SHIPPING_ADDRESS => $this->shippingAddressMock, + ShippingContext::FIELD_SHIPPING_ORIGIN => $shippingOrigin, + ShippingContext::FIELD_PAYMENT_METHOD => $paymentMethod, + ShippingContext::FIELD_CURRENCY => $currency, + ShippingContext::FIELD_SUBTOTAL => $this->subtotalMock, + ShippingContext::FIELD_SOURCE_ENTITY => $this->sourceEntityMock, + ShippingContext::FIELD_SOURCE_ENTITY_ID => $entityId, + ]; + + return new ShippingContext($params); + } + + /** + * @param int $entityId + * @param AddressInterface $shippingOrigin + * + * @return ShippingContext + */ + private function getExpectedContextWithoutOptionalFields($entityId, AddressInterface $shippingOrigin) + { + $params = [ + ShippingContext::FIELD_LINE_ITEMS => null, + ShippingContext::FIELD_SHIPPING_ORIGIN => $shippingOrigin, + ShippingContext::FIELD_SOURCE_ENTITY => $this->sourceEntityMock, + ShippingContext::FIELD_SOURCE_ENTITY_ID => $entityId, + ]; + + return new ShippingContext($params); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/Builder/Basic/Factory/BasicShippingContextBuilderFactoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/Builder/Basic/Factory/BasicShippingContextBuilderFactoryTest.php new file mode 100644 index 000000000..4d6956f87 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/Builder/Basic/Factory/BasicShippingContextBuilderFactoryTest.php @@ -0,0 +1,53 @@ +lineItemsCollectionMock = $this->createMock(ShippingLineItemCollectionInterface::class); + $this->subtotalMock = $this->createMock(Price::class); + $this->sourceEntityMock = $this->createMock(Order::class); + } + + public function testCreateBuilder() + { + $entityId = '12'; + + $builderFactory = new BasicShippingContextBuilderFactory(); + + $builder = $builderFactory->createShippingContextBuilder( + $this->sourceEntityMock, + $entityId + ); + + $expectedBuilder = new BasicShippingContextBuilder( + $this->sourceEntityMock, + $entityId + ); + + $this->assertEquals($expectedBuilder, $builder); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Builder/Basic/BasicShippingLineItemBuilderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Builder/Basic/BasicShippingLineItemBuilderTest.php new file mode 100644 index 000000000..9f40a7d44 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Builder/Basic/BasicShippingLineItemBuilderTest.php @@ -0,0 +1,48 @@ +productHolderMock + ); + + $builder + ->setProduct($this->productMock) + ->setPrice($this->priceMock) + ->setProductSku(self::TEST_PRODUCT_SKU) + ->setWeight(self::TEST_WEIGHT); + + $shippingLineItem = $builder->getResult(); + + $expectedShippingLineItem = new ShippingLineItem($this->getShippingLineItemParams()); + + $this->assertEquals($expectedShippingLineItem, $shippingLineItem); + } + + public function testOptionalBuild() + { + $builder = new BasicShippingLineItemBuilder( + self::TEST_QUANTITY, + $this->productHolderMock + ); + + $shippingLineItem = $builder->getResult(); + + $expectedShippingLineItem = new ShippingLineItem([ + ShippingLineItem::FIELD_QUANTITY => self::TEST_QUANTITY, + ShippingLineItem::FIELD_PRODUCT_HOLDER => $this->productHolderMock, + ShippingLineItem::FIELD_ENTITY_IDENTIFIER => self::TEST_ENTITY_ID + ]); + + $this->assertEquals($expectedShippingLineItem, $shippingLineItem); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Builder/Basic/Factory/BasicLineItemBuilderByLineItemFactoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Builder/Basic/Factory/BasicLineItemBuilderByLineItemFactoryTest.php new file mode 100644 index 000000000..2140fa56c --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Builder/Basic/Factory/BasicLineItemBuilderByLineItemFactoryTest.php @@ -0,0 +1,49 @@ +lineItemBuilderFactory = $this->createMock(ShippingLineItemBuilderFactoryInterface::class); + + $this->factory = new BasicLineItemBuilderByLineItemFactory($this->lineItemBuilderFactory); + } + + public function testCreate() + { + $lineItem = new ShippingLineItem($this->getShippingLineItemParams()); + + $builder = new BasicShippingLineItemBuilder( + $lineItem->getQuantity(), + $lineItem->getProductHolder() + ); + + $this->lineItemBuilderFactory + ->method('createBuilder') + ->willReturn($builder); + + $builder = $this->factory->createBuilder($lineItem); + + $this->assertEquals($lineItem, $builder->getResult()); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Builder/Basic/Factory/BasicShippingLineItemBuilderFactoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Builder/Basic/Factory/BasicShippingLineItemBuilderFactoryTest.php new file mode 100644 index 000000000..1685b11dd --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Builder/Basic/Factory/BasicShippingLineItemBuilderFactoryTest.php @@ -0,0 +1,39 @@ +productHolderMock = $this->createMock(OrderItem::class); + } + + public function testCreate() + { + $quantity = 15; + + $builderFactory = new BasicShippingLineItemBuilderFactory(); + + $builder = $builderFactory->createBuilder( + $quantity, + $this->productHolderMock + ); + + $expectedBuilder = new BasicShippingLineItemBuilder( + $quantity, + $this->productHolderMock + ); + + $this->assertEquals($expectedBuilder, $builder); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrineShippingLineItemCollectionFactoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrineShippingLineItemCollectionFactoryTest.php new file mode 100644 index 000000000..d23a10599 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrineShippingLineItemCollectionFactoryTest.php @@ -0,0 +1,42 @@ +createShippingLineItemCollection($shippingLineItems); + + $this->assertEquals($shippingLineItems, $collection->toArray()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Expected: Marello\Bundle\ShippingBundle\Context\ShippingLineItemInterface + */ + public function testFactoryWithException() + { + $shippingLineItems = [ + new OrderItem(), + new OrderItem(), + new OrderItem(), + new OrderItem(), + ]; + + $collectionFactory = new DoctrineShippingLineItemCollectionFactory(); + $collectionFactory->createShippingLineItemCollection($shippingLineItems); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrineShippingLineItemCollectionTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrineShippingLineItemCollectionTest.php new file mode 100644 index 000000000..500a1fe83 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/LineItem/Collection/ArrayCollectionDoctrine/DoctrineShippingLineItemCollectionTest.php @@ -0,0 +1,23 @@ +assertEquals($shippingLineItems, $collection->toArray()); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingContextCacheKeyGeneratorTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingContextCacheKeyGeneratorTest.php new file mode 100644 index 000000000..fde768ff5 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingContextCacheKeyGeneratorTest.php @@ -0,0 +1,406 @@ +generator = new ShippingContextCacheKeyGenerator(); + } + + /** + * @param $params + * @param ShippingContext|null $context + * + * @return ShippingContext + */ + private function createContext($params, ShippingContext $context = null) + { + $actualParams = $params; + + if (null === $context) { + $actualParams[ShippingContext::FIELD_LINE_ITEMS] = new DoctrineShippingLineItemCollection([]); + } else { + $actualParams = array_merge($context->all(), $actualParams); + } + + return new ShippingContext($actualParams); + } + + /** + * @param array $lineItemsParams + * @param ShippingContext|null $context + * + * @return ShippingContext + */ + private function createContextWithLineItems(array $lineItemsParams, ShippingContext $context = null) + { + $lineItems = []; + foreach ($lineItemsParams as $params) { + $lineItems[] = new ShippingLineItem($params); + } + + return $this->createContext( + [ + ShippingContext::FIELD_LINE_ITEMS => new DoctrineShippingLineItemCollection($lineItems), + ], + $context + ); + } + + public function testGenerateHashSimpleFields() + { + $context1 = $this->createContext([]); + $context2 = $this->createContext([]); + + $this->assertEquals(crc32(''), $this->generator->generateKey($context1)); + $this->assertEquals(crc32(''), $this->generator->generateKey($context2)); + + $context1 = $this->createContext([ShippingContext::FIELD_CURRENCY => 'USD'], $context1); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContext([ShippingContext::FIELD_CURRENCY => 'EUR'], $context2); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContext([ShippingContext::FIELD_CURRENCY => 'USD'], $context2); + $this->assertHashEquals($context1, $context2); + + $context1 = $this->createContext([ShippingContext::FIELD_PAYMENT_METHOD => 'payment_method'], $context1); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContext( + [ShippingContext::FIELD_PAYMENT_METHOD => 'another_payment_method'], + $context2 + ); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContext([ShippingContext::FIELD_PAYMENT_METHOD => 'payment_method'], $context2); + $this->assertHashEquals($context1, $context2); + + $context1 = $this->createContext([ShippingContext::FIELD_SUBTOTAL => new Price()], $context1); + $this->assertHashEquals($context1, $context2); + $context2 = $this->createContext([ShippingContext::FIELD_SUBTOTAL => new Price()], $context2); + $this->assertHashEquals($context1, $context2); + + $context1 = $this->createContext([ShippingContext::FIELD_SUBTOTAL => Price::create(10, 'USD')], $context1); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContext([ShippingContext::FIELD_SUBTOTAL => Price::create(11, 'USD')], $context2); + $this->assertHashNotEquals($context1, $context2); + $context1 = $this->createContext([ShippingContext::FIELD_SUBTOTAL => Price::create(10, 'USD')], $context1); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContext([ShippingContext::FIELD_SUBTOTAL => Price::create(10, 'USD')], $context2); + $this->assertHashEquals($context1, $context2); + } + + public function testGenerateHashBillingAddress() + { + $context1 = $this->createContext([]); + $context2 = $this->createContext([]); + + $address1 = new ShippingAddressStub(); + $address2 = new ShippingAddressStub(); + + $context1 = $this->createContext([ShippingContext::FIELD_BILLING_ADDRESS => $address1], $context1); + $this->assertHashEquals($context1, $context2); + $context2 = $this->createContext([ShippingContext::FIELD_BILLING_ADDRESS => $address2], $context2); + $this->assertHashEquals($context1, $context2); + + $this->assertAddressesFieldAffectsHash($context1, $context2, $address1, $address2); + } + + public function testGenerateHashShippingAddress() + { + $context1 = $this->createContext([]); + $context2 = $this->createContext([]); + + $address1 = new ShippingAddressStub(); + $address2 = new ShippingAddressStub(); + + $context1 = $this->createContext([ShippingContext::FIELD_SHIPPING_ADDRESS => $address1], $context1); + $this->assertHashEquals($context1, $context2); + $context2 = $this->createContext([ShippingContext::FIELD_SHIPPING_ADDRESS => $address2], $context2); + $this->assertHashEquals($context1, $context2); + + $this->assertAddressesFieldAffectsHash($context1, $context2, $address1, $address2); + } + + public function testGenerateHashShippingOrigin() + { + $context1 = $this->createContext([]); + $context2 = $this->createContext([]); + + $address1 = new ShippingAddressStub(); + $address2 = new ShippingAddressStub(); + + $context1 = $this->createContext([ShippingContext::FIELD_SHIPPING_ORIGIN => $address1], $context1); + $this->assertHashEquals($context1, $context2); + $context2 = $this->createContext([ShippingContext::FIELD_SHIPPING_ORIGIN => $address2], $context2); + $this->assertHashEquals($context1, $context2); + + $this->assertAddressesFieldAffectsHash($context1, $context2, $address1, $address2); + } + + /** + * @param ShippingContext $context1 + * @param ShippingContext $context2 + * @param ShippingAddressStub $address1 + * @param ShippingAddressStub $address2 + */ + protected function assertAddressesFieldAffectsHash( + ShippingContext $context1, + ShippingContext $context2, + ShippingAddressStub $address1, + ShippingAddressStub $address2 + ) { + $address1->setStreet('street'); + $this->assertHashNotEquals($context1, $context2); + $address2->setStreet('another_street'); + $this->assertHashNotEquals($context1, $context2); + $address2->setStreet('street'); + $this->assertHashEquals($context1, $context2); + + $address1->setStreet2('street2'); + $this->assertHashNotEquals($context1, $context2); + $address2->setStreet2('another_street2'); + $this->assertHashNotEquals($context1, $context2); + $address2->setStreet2('street2'); + $this->assertHashEquals($context1, $context2); + + $address1->setCity('city'); + $this->assertHashNotEquals($context1, $context2); + $address2->setCity('another_city'); + $this->assertHashNotEquals($context1, $context2); + $address2->setCity('city'); + $this->assertHashEquals($context1, $context2); + + $address1->setRegionText('region'); + $this->assertHashNotEquals($context1, $context2); + $address2->setRegionText('another_region'); + $this->assertHashNotEquals($context1, $context2); + $address2->setRegionText('region'); + $this->assertHashEquals($context1, $context2); + + $address1->setRegion((new Region(1))->setCode(1)); + $this->assertHashNotEquals($context1, $context2); + $address2->setRegion((new Region(2))->setCode(2)); + $this->assertHashNotEquals($context1, $context2); + $address2->setRegion((new Region(1))->setCode(1)); + $this->assertHashEquals($context1, $context2); + + $address1->setPostalCode('postal_code'); + $this->assertHashNotEquals($context1, $context2); + $address2->setPostalCode('another_postal_code'); + $this->assertHashNotEquals($context1, $context2); + $address2->setPostalCode('postal_code'); + $this->assertHashEquals($context1, $context2); + + $country1 = new Country('postal_code'); + $country2 = new Country('postal_code'); + + $address1->setCountry($country1); + $this->assertHashNotEquals($context1, $context2); + $address2->setCountry(new Country('wrong_postal_code')); + $this->assertHashNotEquals($context1, $context2); + $address2->setCountry($country2); + $this->assertHashEquals($context1, $context2); + + $country1->setName('postal_code'); + $this->assertHashNotEquals($context1, $context2); + $country2->setName('another_postal_code'); + $this->assertHashNotEquals($context1, $context2); + $country2->setName('postal_code'); + $this->assertHashEquals($context1, $context2); + + $country1->setIso3Code('code'); + $this->assertHashNotEquals($context1, $context2); + $country2->setIso3Code('another_code'); + $this->assertHashNotEquals($context1, $context2); + $country2->setIso3Code('code'); + $this->assertHashEquals($context1, $context2); + + $address1->setOrganization('organization'); + $this->assertHashNotEquals($context1, $context2); + $address2->setOrganization('another_organization'); + $this->assertHashNotEquals($context1, $context2); + $address2->setOrganization('organization'); + $this->assertHashEquals($context1, $context2); + } + + public function testGenerateHashLineItemsOrder() + { + $context1 = $this->createContext([]); + $context2 = $this->createContext([]); + + $product1 = new Product(); + $product2 = new Product(); + + $item1 = new ShippingLineItem([ShippingLineItem::FIELD_PRODUCT => $product1]); + $item2 = new ShippingLineItem([ShippingLineItem::FIELD_PRODUCT => $product2]); + + $lineItems = new DoctrineShippingLineItemCollection([$item1, $item2]); + $context1 = $this->createContext([ShippingContext::FIELD_LINE_ITEMS => $lineItems], $context1); + $this->assertHashEquals($context1, $context2); + + $lineItems = new DoctrineShippingLineItemCollection([$item1]); + $context1 = $this->createContext([ShippingContext::FIELD_LINE_ITEMS => $lineItems], $context1); + $this->assertHashEquals($context1, $context2); + $lineItems = new DoctrineShippingLineItemCollection([$item2]); + $context2 = $this->createContext([ShippingContext::FIELD_LINE_ITEMS => $lineItems], $context2); + $this->assertHashEquals($context1, $context2); + + $item1 = new ShippingLineItem( + [ShippingLineItem::FIELD_PRODUCT => $product1, ShippingLineItem::FIELD_QUANTITY => 1] + ); + $item2 = new ShippingLineItem( + [ShippingLineItem::FIELD_PRODUCT => $product2, ShippingLineItem::FIELD_QUANTITY => 2] + ); + + $lineItems = new DoctrineShippingLineItemCollection([$item1, $item2]); + $context1 = $this->createContext([ShippingContext::FIELD_LINE_ITEMS => $lineItems], $context1); + $context2 = $this->createContext([ShippingContext::FIELD_LINE_ITEMS => $lineItems], $context2); + $this->assertHashEquals($context1, $context2); + $lineItems = new DoctrineShippingLineItemCollection([$item2, $item1]); + $context2 = $this->createContext([ShippingContext::FIELD_LINE_ITEMS => $lineItems], $context2); + $this->assertHashEquals($context1, $context2); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testGenerateHashLineItems() + { + $context1 = $this->createContext([]); + $context2 = $this->createContext([]); + + $product1 = new Product(); + $product2 = new Product(); + + $context1 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRODUCT => $product1, ShippingLineItem::FIELD_QUANTITY => 1]], + $context1 + ); + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRODUCT => $product2]], + $context2 + ); + $this->assertHashNotEquals($context1, $context2); + + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRODUCT => $product2, ShippingLineItem::FIELD_QUANTITY => 2]], + $context2 + ); + $this->assertHashNotEquals($context1, $context2); + + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRODUCT => $product2, ShippingLineItem::FIELD_QUANTITY => 1]], + $context2 + ); + $this->assertHashEquals($context1, $context2); + + $context1 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRICE => Price::create(10, 'USD')]], + $context1 + ); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRICE => Price::create(11, 'USD')]], + $context2 + ); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRICE => Price::create(10, 'EUR')]], + $context2 + ); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRICE => Price::create(10, 'USD')]], + $context2 + ); + $this->assertHashEquals($context1, $context2); + + $context1 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRODUCT => $this->getEntity(Product::class, ['id' => 1])]], + $context1 + ); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRODUCT => $this->getEntity(Product::class, ['id' => 2])]], + $context2 + ); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_PRODUCT => $this->getEntity(Product::class, ['id' => 1])]], + $context2 + ); + $this->assertHashEquals($context1, $context2); + + $weight = 10; + $context1 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_WEIGHT => $weight]], + $context1 + ); + $this->assertHashNotEquals($context1, $context2); + $weight = 12; + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_WEIGHT => $weight]], + $context2 + ); + $this->assertHashNotEquals($context1, $context2); + $weight = 10; + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_WEIGHT => $weight]], + $context2 + ); + $this->assertHashEquals($context1, $context2); + + $context1 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_ENTITY_IDENTIFIER => 1]], + $context1 + ); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_ENTITY_IDENTIFIER => 2]], + $context2 + ); + $this->assertHashNotEquals($context1, $context2); + $context2 = $this->createContextWithLineItems( + [[ShippingLineItem::FIELD_ENTITY_IDENTIFIER => 1]], + $context2 + ); + $this->assertHashEquals($context1, $context2); + } + + /** + * @param ShippingContextInterface $context1 + * @param ShippingContextInterface $context2 + */ + protected function assertHashEquals(ShippingContextInterface $context1, ShippingContextInterface $context2) + { + $this->assertEquals($this->generator->generateKey($context1), $this->generator->generateKey($context2)); + } + + /** + * @param ShippingContextInterface $context1 + * @param ShippingContextInterface $context2 + */ + protected function assertHashNotEquals(ShippingContextInterface $context1, ShippingContextInterface $context2) + { + $this->assertNotEquals($this->generator->generateKey($context1), $this->generator->generateKey($context2)); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingContextMockTrait.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingContextMockTrait.php new file mode 100644 index 000000000..cab2716f9 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingContextMockTrait.php @@ -0,0 +1,19 @@ +createMock(ShippingContextInterface::class); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingContextTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingContextTest.php new file mode 100644 index 000000000..cb682fef3 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingContextTest.php @@ -0,0 +1,102 @@ +customerMock = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $this->lineItemsCollectionMock = $this->createMock(ShippingLineItemCollectionInterface::class); + $this->billingAddressMock = $this->createMock(AddressInterface::class); + $this->shippingAddressMock = $this->createMock(AddressInterface::class); + $this->shippingOriginMock = $this->createMock(AddressInterface::class); + $this->subtotalMock = $this->getMockBuilder(Price::class) + ->disableOriginalConstructor() + ->getMock(); + $this->sourceEntityMock = $this->getMockBuilder(Order::class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testConstructionAndGetters() + { + $paymentMethod = 'paymentMethod'; + $currency = 'usd'; + $entityId = '12'; + + $params = [ + ShippingContext::FIELD_CUSTOMER => $this->customerMock, + ShippingContext::FIELD_LINE_ITEMS => $this->lineItemsCollectionMock, + ShippingContext::FIELD_BILLING_ADDRESS => $this->billingAddressMock, + ShippingContext::FIELD_SHIPPING_ADDRESS => $this->shippingAddressMock, + ShippingContext::FIELD_SHIPPING_ORIGIN => $this->shippingOriginMock, + ShippingContext::FIELD_PAYMENT_METHOD => $paymentMethod, + ShippingContext::FIELD_CURRENCY => $currency, + ShippingContext::FIELD_SUBTOTAL => $this->subtotalMock, + ShippingContext::FIELD_SOURCE_ENTITY => $this->sourceEntityMock, + ShippingContext::FIELD_SOURCE_ENTITY_ID => $entityId, + ]; + + $shippingContext = new ShippingContext($params); + + $getterValues = [ + ShippingContext::FIELD_CUSTOMER => $shippingContext->getCustomer(), + ShippingContext::FIELD_LINE_ITEMS => $shippingContext->getLineItems(), + ShippingContext::FIELD_BILLING_ADDRESS => $shippingContext->getBillingAddress(), + ShippingContext::FIELD_SHIPPING_ADDRESS => $shippingContext->getShippingAddress(), + ShippingContext::FIELD_SHIPPING_ORIGIN => $shippingContext->getShippingOrigin(), + ShippingContext::FIELD_PAYMENT_METHOD => $shippingContext->getPaymentMethod(), + ShippingContext::FIELD_CURRENCY => $shippingContext->getCurrency(), + ShippingContext::FIELD_SUBTOTAL => $shippingContext->getSubtotal(), + ShippingContext::FIELD_SOURCE_ENTITY => $shippingContext->getSourceEntity(), + ShippingContext::FIELD_SOURCE_ENTITY_ID => $shippingContext->getSourceEntityIdentifier(), + ]; + + $this->assertEquals($params, $getterValues); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingLineItemTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingLineItemTest.php new file mode 100644 index 000000000..952d170f5 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Context/ShippingLineItemTest.php @@ -0,0 +1,35 @@ +getShippingLineItemParams(); + + $shippingLineItem = new ShippingLineItem($shippingLineItemParams); + + $this->assertEquals($shippingLineItemParams[ShippingLineItem::FIELD_PRICE], $shippingLineItem->getPrice()); + $this->assertEquals( + $shippingLineItemParams[ShippingLineItem::FIELD_QUANTITY], + $shippingLineItem->getQuantity() + ); + $this->assertEquals( + $shippingLineItemParams[ShippingLineItem::FIELD_PRODUCT_HOLDER], + $shippingLineItem->getProductHolder() + ); + $this->assertEquals($shippingLineItemParams[ShippingLineItem::FIELD_PRODUCT], $shippingLineItem->getProduct()); + $this->assertEquals( + $shippingLineItemParams[ShippingLineItem::FIELD_PRODUCT_SKU], + $shippingLineItem->getProductSku() + ); + $this->assertEquals($shippingLineItemParams[ShippingLineItem::FIELD_WEIGHT], $shippingLineItem->getWeight()); + $this->assertEquals( + $shippingLineItemParams[ShippingLineItem::FIELD_ENTITY_IDENTIFIER], + $shippingLineItem->getEntityIdentifier() + ); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Converter/Basic/ShippingContextToRuleValuesConverterTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Converter/Basic/ShippingContextToRuleValuesConverterTest.php new file mode 100644 index 000000000..3d484da60 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Converter/Basic/ShippingContextToRuleValuesConverterTest.php @@ -0,0 +1,105 @@ +factory = new DecoratedProductLineItemFactory( + $this->createMock(VirtualFieldsProductDecoratorFactory::class) + ); + + $this->shippingContextToRuleValuesConverter = new ShippingContextToRulesValuesConverter( + $this->factory + ); + } + + /** + * @dataProvider convertDataProvider + * @param ShippingContext $context + */ + public function testConvert(ShippingContext $context) + { + $expectedValues = [ + 'lineItems' => array_map(function (ShippingLineItem $lineItem) use ($context) { + return $this->factory + ->createLineItemWithDecoratedProductByLineItem($context->getLineItems()->toArray(), $lineItem); + }, $context->getLineItems()->toArray()), + 'shippingOrigin' => $context->getShippingOrigin(), + 'billingAddress' => $context->getBillingAddress(), + 'shippingAddress' => $context->getShippingAddress(), + 'paymentMethod' => $context->getPaymentMethod(), + 'currency' => $context->getCurrency(), + 'subtotal' => $context->getSubtotal(), + 'customer' => $context->getCustomer(), + ]; + $this->assertEquals($expectedValues, $this->shippingContextToRuleValuesConverter->convert($context)); + } + + /** + * @return array + */ + public function convertDataProvider() + { + return [ + [ + 'context' => new ShippingContext([ + ShippingContext::FIELD_LINE_ITEMS => new DoctrineShippingLineItemCollection([ + new ShippingLineItem([ + ShippingLineItem::FIELD_PRODUCT => $this->getEntity(Product::class, ['id' => 1]), + ]), + ]), + ShippingContext::FIELD_SHIPPING_ORIGIN => $this->getEntity(ShippingAddressStub::class, [ + 'region' => $this->getEntity(Region::class, [ + 'code' => 'CA', + ], ['US-CA']), + ]), + ShippingContext::FIELD_BILLING_ADDRESS => $this->getEntity(ShippingAddressStub::class, [ + 'country' => new Country('US'), + ]), + ShippingContext::FIELD_SHIPPING_ADDRESS => $this->getEntity(ShippingAddressStub::class, [ + 'country' => new Country('US'), + 'region' => $this->getEntity(Region::class, [ + 'code' => 'CA', + ], ['US-CA']), + 'postalCode' => '90401', + ]), + ShippingContext::FIELD_PAYMENT_METHOD => 'integration_payment_method', + ShippingContext::FIELD_CURRENCY => 'USD', + ShippingContext::FIELD_SUBTOTAL => Price::create(10.0, 'USD'), + ShippingContext::FIELD_CUSTOMER => (new Customer())->setFirstName('Customer Name'), + ]), + ], + ]; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodConfigTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodConfigTest.php new file mode 100644 index 000000000..abf9fd46c --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodConfigTest.php @@ -0,0 +1,28 @@ + 'test']], + ['methodConfigsRule', new ShippingMethodsConfigsRule()], + ]; + + $entity = new ShippingMethodConfig(); + + $this->assertPropertyAccessors($entity, $properties); + $this->assertPropertyCollection($entity, 'typeConfigs', new ShippingMethodTypeConfig()); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodTypeConfigTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodTypeConfigTest.php new file mode 100644 index 000000000..98cc54b13 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodTypeConfigTest.php @@ -0,0 +1,27 @@ + 'test']], + ['enabled', true], + ['methodConfig', new ShippingMethodConfig()], + ]; + + $entity = new ShippingMethodTypeConfig(); + + static::assertPropertyAccessors($entity, $properties); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodsConfigsRuleDestinationPostalCodeTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodsConfigsRuleDestinationPostalCodeTest.php new file mode 100644 index 000000000..0447dc321 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodsConfigsRuleDestinationPostalCodeTest.php @@ -0,0 +1,26 @@ +country = $this->createMockCountry(); + $this->region = $this->createMockRegion(); + + $this->shippingRuleDestination = $this->getEntity( + 'Marello\Bundle\ShippingBundle\Entity\ShippingMethodsConfigsRuleDestination', + [ + 'region' => $this->region, + 'country' => $this->country, + 'postalCodes' => new ArrayCollection([$this->createPostalCode('123')]), + ] + ); + } + + public function testProperties() + { + $properties = [ + ['id', 1], + ['region', new Region('code')], + ['regionText', 'text'], + ['country', new Country('UA')], + ['methodConfigsRule', new ShippingMethodsConfigsRule()], + ]; + + $destination = new ShippingMethodsConfigsRuleDestination(); + static::assertPropertyAccessors($destination, $properties); + static::assertPropertyCollection( + $destination, + 'postalCodes', + $this->createPostalCode('123') + ); + } + + public function testGetRegionName() + { + $this->assertEquals('RegionName', $this->shippingRuleDestination->getRegionName()); + $this->shippingRuleDestination->setRegion(null); + $this->assertEquals('', $this->shippingRuleDestination->getRegionName()); + } + + public function testGetRegionCode() + { + $this->assertEquals('RegionCode', $this->shippingRuleDestination->getRegionCode()); + $this->shippingRuleDestination->setRegion(null); + $this->assertEquals('', $this->shippingRuleDestination->getRegionCode()); + } + + public function testGetCountryName() + { + $this->assertEquals('CountryName', $this->shippingRuleDestination->getCountryName()); + $this->shippingRuleDestination->setCountry(null); + $this->assertEquals('', $this->shippingRuleDestination->getCountryName()); + } + + public function testGetCountryIso2() + { + $this->assertEquals('CountryIso2', $this->shippingRuleDestination->getCountryIso2()); + $this->shippingRuleDestination->setCountry(null); + $this->assertEquals('', $this->shippingRuleDestination->getCountryIso2()); + } + + public function testGetCountryIso3() + { + $this->assertEquals('CountryIso3', $this->shippingRuleDestination->getCountryIso3()); + $this->shippingRuleDestination->setCountry(null); + $this->assertEquals('', $this->shippingRuleDestination->getCountryIso3()); + } + + /** + * @dataProvider toStringDataProvider + * + * @param array $data + * @param string $expectedString + */ + public function testToString(array $data, $expectedString) + { + $entity = (string) $this->getEntity( + 'Marello\Bundle\ShippingBundle\Entity\ShippingMethodsConfigsRuleDestination', + $data + ); + $this->assertEquals($expectedString, $entity); + } + + /** + * @return array + */ + public function toStringDataProvider() + { + return [ + 'all' => [ + 'data' => [ + 'country' => $this->createMockCountry(), + 'region' => $this->createMockRegion(), + 'postalCodes' => new ArrayCollection([$this->createPostalCode('12345')]), + ], + 'expectedString' => 'RegionName, CountryName 12345' + ], + 'country and postal code' => [ + 'data' => [ + 'country' => $this->createMockCountry(), + 'region' => null, + 'postalCodes' => new ArrayCollection([ + $this->createPostalCode('12345'), + $this->createPostalCode('54321'), + ]), + ], + 'expectedString' => 'CountryName 12345, 54321' + ], + 'country and region' => [ + 'data' => [ + 'country' => $this->createMockCountry('SecondCountryName'), + 'region' => $this->createMockRegion('SecondRegionName'), + 'postalCodes' => new ArrayCollection(), + ], + 'expectedString' => 'SecondRegionName, SecondCountryName' + ], + 'only country' => [ + 'data' => [ + 'country' => $this->createMockCountry(), + 'region' => null, + 'postalCodes' => new ArrayCollection(), + ], + 'expectedString' => 'CountryName' + ] + ]; + } + + /** + * @param string $name + * @param string $iso2 + * @param string $iso3 + * @return \PHPUnit\Framework\MockObject\MockObject + */ + protected function createMockCountry($name = 'CountryName', $iso2 = 'CountryIso2', $iso3 = 'CountryIso3') + { + $result = $this->getMockBuilder('Oro\Bundle\AddressBundle\Entity\Country') + ->disableOriginalConstructor() + ->getMock(); + $result->expects($this->any()) + ->method('__toString') + ->will($this->returnValue($name)); + $result->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)); + $result->expects($this->any()) + ->method('getIso2Code') + ->will($this->returnValue($iso2)); + $result->expects($this->any()) + ->method('getIso3Code') + ->will($this->returnValue($iso3)); + + return $result; + } + + /** + * @param string $name + * @param string $code + * @return \PHPUnit\Framework\MockObject\MockObject + */ + protected function createMockRegion($name = 'RegionName', $code = 'RegionCode') + { + $result = $this->createMock('Oro\Bundle\AddressBundle\Entity\Region', [], ['combinedCode']); + $result->expects($this->any()) + ->method('__toString') + ->will($this->returnValue($name)); + $result->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)); + $result->expects($this->any()) + ->method('getCode') + ->will($this->returnValue($code)); + return $result; + } + + /** + * @param string $name + * @return ShippingMethodsConfigsRuleDestinationPostalCode + */ + protected function createPostalCode($name) + { + return (new ShippingMethodsConfigsRuleDestinationPostalCode())->setName($name); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodsConfigsRuleMockTrait.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodsConfigsRuleMockTrait.php new file mode 100644 index 000000000..e2d61e71e --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodsConfigsRuleMockTrait.php @@ -0,0 +1,19 @@ +createMock(ShippingMethodsConfigsRule::class); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodsConfigsRuleTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodsConfigsRuleTest.php new file mode 100644 index 000000000..3571f3c54 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Entity/ShippingMethodsConfigsRuleTest.php @@ -0,0 +1,32 @@ +shippingMethodProvider = new CompositeShippingMethodProvider(); + + $this->provider = $this->getMockBuilder(ShippingMethodProviderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testGetMethods() + { + $shippingMethods = $this->shippingMethodProvider->getShippingMethods(); + $this->assertInternalType('array', $shippingMethods); + $this->assertEmpty($shippingMethods); + } + + public function testRegistry() + { + $method = $this->createMock(ShippingMethodInterface::class); + + $this->provider->expects($this->once()) + ->method('getShippingMethods') + ->willReturn(['test_name' => $method]); + + $this->provider->expects($this->once()) + ->method('getShippingMethod') + ->with('test_name') + ->willReturn($method); + + $this->provider->expects($this->once()) + ->method('hasShippingMethod') + ->with('test_name') + ->willReturn(true); + + $this->shippingMethodProvider->addProvider($this->provider); + $this->assertEquals($method, $this->shippingMethodProvider->getShippingMethod('test_name')); + $this->assertEquals(['test_name' => $method], $this->shippingMethodProvider->getShippingMethods()); + } + + public function testRegistryWrongMethod() + { + $this->assertNull($this->shippingMethodProvider->getShippingMethod('wrong_name')); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/BasicMethodRemovalEventDispatcherTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/BasicMethodRemovalEventDispatcherTest.php new file mode 100644 index 000000000..77ef49394 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/BasicMethodRemovalEventDispatcherTest.php @@ -0,0 +1,37 @@ +eventDispatcher = $this->createMock(EventDispatcherInterface::class); + + $this->dispatcher = new BasicMethodRemovalEventDispatcher($this->eventDispatcher); + } + + public function testDispatch() + { + $methodId = 'method'; + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with(MethodRemovalEvent::NAME, new MethodRemovalEvent($methodId)); + + $this->dispatcher->dispatch($methodId); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/BasicMethodRenamingEventDispatcherTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/BasicMethodRenamingEventDispatcherTest.php new file mode 100644 index 000000000..5d0cc13b9 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/BasicMethodRenamingEventDispatcherTest.php @@ -0,0 +1,38 @@ +eventDispatcher = $this->createMock(EventDispatcherInterface::class); + + $this->dispatcher = new BasicMethodRenamingEventDispatcher($this->eventDispatcher); + } + + public function testDispatch() + { + $oldId = 'old_id'; + $newId = 'new_id'; + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with(MethodRenamingEvent::NAME, new MethodRenamingEvent($oldId, $newId)); + + $this->dispatcher->dispatch($oldId, $newId); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/BasicMethodTypeRemovalEventDispatcherTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/BasicMethodTypeRemovalEventDispatcherTest.php new file mode 100644 index 000000000..0a7b7222f --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/BasicMethodTypeRemovalEventDispatcherTest.php @@ -0,0 +1,38 @@ +eventDispatcher = $this->createMock(EventDispatcherInterface::class); + + $this->dispatcher = new BasicMethodTypeRemovalEventDispatcher($this->eventDispatcher); + } + + public function testDispatch() + { + $methodId = 'method'; + $typeId = 'type'; + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with(MethodTypeRemovalEvent::NAME, new MethodTypeRemovalEvent($methodId, $typeId)); + + $this->dispatcher->dispatch($methodId, $typeId); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/MethodRemovalEventTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/MethodRemovalEventTest.php new file mode 100644 index 000000000..d256a00d6 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/MethodRemovalEventTest.php @@ -0,0 +1,17 @@ +assertSame($methodId, $event->getMethodIdentifier()); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/MethodRenamingEventTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/MethodRenamingEventTest.php new file mode 100644 index 000000000..deddb5e06 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/MethodRenamingEventTest.php @@ -0,0 +1,19 @@ +assertSame($oldId, $event->getOldMethodIdentifier()); + $this->assertSame($newId, $event->getNewMethodIdentifier()); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/MethodTypeRemovalEventTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/MethodTypeRemovalEventTest.php new file mode 100644 index 000000000..fc17ab750 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Event/MethodTypeRemovalEventTest.php @@ -0,0 +1,19 @@ +assertSame($methodId, $event->getMethodIdentifier()); + $this->assertSame($typeId, $event->getTypeIdentifier()); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/EventListener/IntegrationRemovalListenerTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/EventListener/IntegrationRemovalListenerTest.php new file mode 100644 index 000000000..384af205e --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/EventListener/IntegrationRemovalListenerTest.php @@ -0,0 +1,96 @@ +channelType = 'shipping_method'; + $this->identifierGenerator = $this->createMock(IntegrationIdentifierGeneratorInterface::class); + $this->dispatcher = $this->createMock(MethodRemovalEventDispatcherInterface::class); + + $this->listener = new IntegrationRemovalListener( + $this->channelType, + $this->identifierGenerator, + $this->dispatcher + ); + } + + public function testPreRemove() + { + /** @var Channel|\PHPUnit\Framework\MockObject\MockObject $channel */ + $channel = $this->createMock(Channel::class); + $channel->expects(static::once()) + ->method('getType') + ->willReturn($this->channelType); + + /** @var ChannelDeleteEvent|\PHPUnit\Framework\MockObject\MockObject $event */ + $event = $this->createMock(ChannelDeleteEvent::class); + $event->expects(static::any()) + ->method('getChannel') + ->willReturn($channel); + + $identifier = 'method'; + + $this->identifierGenerator->expects(static::once()) + ->method('generateIdentifier') + ->with($channel) + ->willReturn($identifier); + + $this->dispatcher->expects(static::once()) + ->method('dispatch') + ->with($identifier); + + $this->listener->onRemove($event); + } + + public function testPreRemoveOtherType() + { + /** @var Channel|\PHPUnit\Framework\MockObject\MockObject $channel */ + $channel = $this->createMock(Channel::class); + $channel->expects(static::once()) + ->method('getType') + ->willReturn('other_type'); + + /** @var ChannelDeleteEvent|\PHPUnit\Framework\MockObject\MockObject $event */ + $event = $this->createMock(ChannelDeleteEvent::class); + $event->expects(static::any()) + ->method('getChannel') + ->willReturn($channel); + + $this->identifierGenerator->expects(static::never()) + ->method('generateIdentifier'); + + $this->dispatcher->expects(static::never()) + ->method('dispatch'); + + $this->listener->onRemove($event); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/EventListener/MethodRenamingListenerTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/EventListener/MethodRenamingListenerTest.php new file mode 100644 index 000000000..fd9691c2f --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/EventListener/MethodRenamingListenerTest.php @@ -0,0 +1,59 @@ +shippingMethodConfigRepository = $this->createMock(ShippingMethodConfigRepository::class); + $this->listener = new MethodRenamingListener($this->shippingMethodConfigRepository); + } + + public function testOnMethodRename() + { + $oldId = 'old_name'; + $newId = 'new_name'; + + /** @var MethodRenamingEvent|\PHPUnit\Framework\MockObject\MockObject $event */ + $event = $this->createMock(MethodRenamingEvent::class); + $event->expects(static::any()) + ->method('getOldMethodIdentifier') + ->willReturn($oldId); + + $event->expects(static::any()) + ->method('getNewMethodIdentifier') + ->willReturn($newId); + + $config1 = $this->createMock(ShippingMethodConfig::class); + $config1->expects(static::once()) + ->method('setMethod') + ->with($newId); + $config2 = $this->createMock(ShippingMethodConfig::class); + $config2->expects(static::once()) + ->method('setMethod') + ->with($newId); + + $this->shippingMethodConfigRepository->expects(static::once()) + ->method('findByMethod') + ->with($oldId) + ->willReturn([$config1, $config2]); + + $this->listener->onMethodRename($event); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/EventListener/ShippingMethodDisableIntegrationListenerTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/EventListener/ShippingMethodDisableIntegrationListenerTest.php new file mode 100644 index 000000000..5fb07e858 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/EventListener/ShippingMethodDisableIntegrationListenerTest.php @@ -0,0 +1,111 @@ +channelType = 'integration_shipping_method'; + + $this->methodIdentifierGenerator = $this->createMock( + IntegrationIdentifierGeneratorInterface::class + ); + $this->handler = $this->createMock( + ShippingMethodDisableHandlerInterface::class + ); + $this->event = $this->createMock( + ChannelDisableEvent::class + ); + $this->listener = new ShippingMethodDisableIntegrationListener( + $this->channelType, + $this->methodIdentifierGenerator, + $this->handler + ); + } + + public function testOnIntegrationDisable() + { + $methodIdentifier = 'method_1'; + $channel = $this->createMock(Channel::class); + + $this->event + ->expects(static::once()) + ->method('getChannel') + ->willReturn($channel); + + $channel + ->expects(static::once()) + ->method('getType') + ->willReturn($this->channelType); + + $this->methodIdentifierGenerator + ->expects(static::once()) + ->method('generateIdentifier') + ->with($channel) + ->willReturn($methodIdentifier); + + $this->handler + ->expects(static::once()) + ->method('handleMethodDisable') + ->with($methodIdentifier); + + $this->listener->onIntegrationDisable($this->event); + } + + public function testOnIntegrationDisableWithAnotherType() + { + $channel = $this->createMock(Channel::class); + + $this->event + ->expects(static::once()) + ->method('getChannel') + ->willReturn($channel); + + $channel + ->expects(static::once()) + ->method('getType') + ->willReturn('another_type'); + + $this->methodIdentifierGenerator + ->expects(static::never()) + ->method('generateIdentifier'); + + $this->handler + ->expects(static::never()) + ->method('handleMethodDisable'); + + $this->listener->onIntegrationDisable($this->event); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Handler/RulesShippingMethodDisableHandlerDecoratorTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Handler/RulesShippingMethodDisableHandlerDecoratorTest.php new file mode 100644 index 000000000..f3a46bda1 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Handler/RulesShippingMethodDisableHandlerDecoratorTest.php @@ -0,0 +1,151 @@ +handler = $this->createMock(ShippingMethodDisableHandlerInterface::class); + $this->repository = $this->createMock(ShippingMethodsConfigsRuleRepository::class); + $this->shippingMethodProvider = $this->createMock(ShippingMethodProviderInterface::class); + + $this->decorator = new RulesShippingMethodDisableHandlerDecorator( + $this->handler, + $this->repository, + $this->shippingMethodProvider + ); + } + + /** + * @param string $disabledMethodId + * @param array $configs + * @param array $registryMap + * + * @dataProvider testHandleMethodDisableProvider + */ + public function testHandleMethodDisable($disabledMethodId, $configs, $registryMap) + { + $this->handler->expects(self::once())->method('handleMethodDisable')->with($disabledMethodId); + + $configMocks = []; + $registryMapValues = []; + $methods = []; + foreach ($registryMap as $methodId => $enabled) { + $methods[$methodId] = $this->createMock(ShippingMethodInterface::class); + $methods[$methodId]->expects(self::any())->method('isEnabled')->willReturn($enabled); + $registryMapValues[] = [$methodId, $methods[$methodId]]; + } + + $rules = []; + foreach ($configs as $configName => $config) { + $methodConfigs = []; + foreach ($config['methods'] as $methodId) { + $methodConfig = $this->createMock(ShippingMethodConfig::class); + $methodConfig->expects(self::once())->method('getMethod')->willReturn($methodId); + $methodConfigs[] = $methodConfig; + } + $rules[$configName] = $this->createMock(Rule::class); + $rules[$configName]->expects(self::exactly($config['rule_disabled']))->method('setEnabled')->with(false); + + $configMock = $this->createMock(ShippingMethodsConfigsRule::class); + $configMock->expects(self::once()) + ->method('getMethodConfigs') + ->willReturn($methodConfigs); + $configMock->expects(self::any()) + ->method('getRule') + ->willReturn($rules[$configName]); + $configMocks[] = $configMock; + } + + $this->shippingMethodProvider + ->method('getShippingMethod') + ->will($this->returnValueMap($registryMapValues)); + + $this->repository->expects(self::once()) + ->method('getEnabledRulesByMethod') + ->willReturn($configMocks); + + $this->decorator->handleMethodDisable($disabledMethodId); + } + + /** + * @return array + */ + public function testHandleMethodDisableProvider() + { + return [ + 'a_few_methods' => + [ + 'methodId' => 'method1', + 'configs' => + [ + 'config1' => + [ + 'methods' => ['method1', 'method2'], + 'rule_disabled' => 1, + ], + 'config2' => + [ + 'methods' => ['method1', 'method3'], + 'rule_disabled' => 0, + ] + ], + 'registry_map' => + [ + 'method1' => true, + 'method2' => false, + 'method3' => true, + ], + ], + 'only_method' => + [ + 'methodId' => 'method1', + 'configs' => + [ + 'config1' => + [ + 'methods' => ['method1'], + 'rule_disabled' => 1, + ], + ], + 'registry_map' => + [ + 'method1' => true, + ], + ], + ]; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Provider/Integration/ChannelShippingMethodProviderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Provider/Integration/ChannelShippingMethodProviderTest.php new file mode 100644 index 000000000..b8b7b9012 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Provider/Integration/ChannelShippingMethodProviderTest.php @@ -0,0 +1,136 @@ +doctrineHelper = $this->createMock(DoctrineHelper::class); + $repository = $this->createMock(ChannelRepository::class); + + $this->doctrineHelper + ->method('getEntityRepository') + ->with('OroIntegrationBundle:Channel') + ->willReturn($repository); + + $loadedChannel = $this->createChannel('ch_enabled'); + $fetchedChannel = $this->createChannel('ch_disabled'); + + $this->enabledMethod = $this->createMock(ShippingMethodInterface::class); + $this->enabledMethod + ->method('getIdentifier') + ->willReturn('ups_10'); + + $this->disabledMethod = $this->createMock(ShippingMethodInterface::class); + $this->disabledMethod + ->method('getIdentifier') + ->willReturn('ups_20'); + + $this->methodFactory = $this->createMock(IntegrationShippingMethodFactoryInterface::class); + $this->methodFactory + ->method('create') + ->will($this->returnValueMap([ + [$loadedChannel, $this->enabledMethod], + [$fetchedChannel, $this->disabledMethod], + ])); + + $this->provider = new ChannelShippingMethodProvider(static::TYPE, $this->doctrineHelper, $this->methodFactory); + + $doctrineEvent = $this->createLifecycleEventArgsMock(); + $this->provider->postLoad($loadedChannel, $doctrineEvent); + + $repository + ->method('findByTypeAndExclude') + ->will(static::returnCallback(function () use ($fetchedChannel, $doctrineEvent) { + $this->provider->postLoad($fetchedChannel, $doctrineEvent); + return [$fetchedChannel]; + })); + } + + public function testGetShippingMethods() + { + $methods = $this->provider->getShippingMethods(); + static::assertCount(2, $methods); + $actualMethod = reset($methods); + static::assertSame($this->enabledMethod, $actualMethod); + } + + public function testGetShippingMethod() + { + $method = $this->provider->getShippingMethod($this->enabledMethod->getIdentifier()); + static::assertInstanceOf(ShippingMethodInterface::class, $method); + } + + public function testHasShippingMethod() + { + static::assertTrue($this->provider->hasShippingMethod($this->enabledMethod->getIdentifier())); + } + + public function testHasShippingMethodFalse() + { + static::assertFalse($this->provider->hasShippingMethod('wrong')); + } + + /** + * @param string $name + * + * @return Channel + */ + private function createChannel($name) + { + return $this->getEntity( + Channel::class, + ['id' => 20, 'name' => $name, 'type' => static::TYPE] + ); + } + + /** + * @return LifecycleEventArgs|\PHPUnit\Framework\MockObject\MockObject + */ + private function createLifecycleEventArgsMock() + { + return $this->createMock(LifecycleEventArgs::class); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Provider/Label/Type/BasicMethodTypeLabelsProviderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Provider/Label/Type/BasicMethodTypeLabelsProviderTest.php new file mode 100644 index 000000000..700fdaf14 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Provider/Label/Type/BasicMethodTypeLabelsProviderTest.php @@ -0,0 +1,110 @@ +methodProvider = $this->createMock(ShippingMethodProviderInterface::class); + + $this->provider = new BasicMethodTypeLabelsProvider($this->methodProvider); + } + + public function testGetLabels() + { + $methodId = 'method_id'; + $typeId1 = 'type_id_1'; + $typeId2 = 'type_id_2'; + + $label1 = 'Label 1'; + $label2 = 'Label 2'; + + $type1 = $this->createMock(ShippingMethodTypeInterface::class); + $type1->expects(static::once()) + ->method('getLabel') + ->willReturn($label1); + + $type2 = $this->createMock(ShippingMethodTypeInterface::class); + $type2->expects(static::once()) + ->method('getLabel') + ->willReturn($label2); + + $method = $this->createMock(ShippingMethodInterface::class); + $method->expects(static::at(0)) + ->method('getType') + ->with($typeId1) + ->willReturn($type1); + + $method->expects(static::at(1)) + ->method('getType') + ->with($typeId2) + ->willReturn($type2); + + $this->methodProvider->expects(static::once()) + ->method('getShippingMethod') + ->with($methodId) + ->willReturn($method); + + $this->provider->getLabels($methodId, [$typeId1, $typeId2]); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Shipping method with identifier: method_id, does not exist. + */ + public function testGetLabelsNoMethod() + { + $methodId = 'method_id'; + + $this->methodProvider->expects(static::once()) + ->method('getShippingMethod') + ->with($methodId) + ->willReturn(null); + + $this->provider->getLabels($methodId, []); + } + + public function testGetLabelsNoType() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'Shipping method with identifier: method_id does not contain type with identifier: type_id.' + ); + + $methodId = 'method_id'; + $typeId = 'type_id'; + + $method = $this->createMock(ShippingMethodInterface::class); + $method->expects(static::once()) + ->method('getType') + ->with($typeId) + ->willReturn(null); + + $this->methodProvider->expects(static::once()) + ->method('getShippingMethod') + ->with($methodId) + ->willReturn($method); + + $this->provider->getLabels($methodId, [$typeId]); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Provider/Type/NonDeletable/ShippingRulesNonDeletableMethodTypeIdentifiersProviderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Provider/Type/NonDeletable/ShippingRulesNonDeletableMethodTypeIdentifiersProviderTest.php new file mode 100644 index 000000000..9f358d3ae --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Provider/Type/NonDeletable/ShippingRulesNonDeletableMethodTypeIdentifiersProviderTest.php @@ -0,0 +1,87 @@ +methodTypeConfigRepository = $this->createMock(ShippingMethodTypeConfigRepository::class); + + $this->provider = new ShippingRulesNonDeletableMethodTypeIdentifiersProvider($this->methodTypeConfigRepository); + } + + public function testGetMethodTypeIdentifiers() + { + $typeId1 = 'type_1'; + $typeId2 = 'type_2'; + $disabledTypeId = 'disabled_type'; + + $type1 = $this->createMock(ShippingMethodTypeInterface::class); + $type1->expects(static::once()) + ->method('getIdentifier') + ->willReturn($typeId1); + + $type2 = $this->createMock(ShippingMethodTypeInterface::class); + $type2->expects(static::once()) + ->method('getIdentifier') + ->willReturn($typeId2); + + $methodId = 'method_id'; + $shippingMethod = $this->createMethodMock(); + $shippingMethod->expects(static::once()) + ->method('getIdentifier') + ->willReturn($methodId); + + $shippingMethod->expects(static::once()) + ->method('getTypes') + ->willReturn([$type1, $type2]); + + $methodTypeConfig1 = $this->createMock(ShippingMethodTypeConfig::class); + $methodTypeConfig1->expects(static::once()) + ->method('getType') + ->willReturn($typeId1); + + $methodTypeConfig2 = $this->createMock(ShippingMethodTypeConfig::class); + $methodTypeConfig2->expects(static::once()) + ->method('getType') + ->willReturn($disabledTypeId); + + $this->methodTypeConfigRepository->expects(static::once()) + ->method('findEnabledByMethodIdentifier') + ->with($methodId) + ->willReturn([$methodTypeConfig1, $methodTypeConfig2]); + + $actualNonDeletableTypeIds = $this->provider->getMethodTypeIdentifiers($shippingMethod); + + static::assertCount(1, $actualNonDeletableTypeIds); + static::assertContains($disabledTypeId, $actualNonDeletableTypeIds); + } + + /** + * @return ShippingMethodInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function createMethodMock() + { + return $this->createMock(ShippingMethodInterface::class); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/ShippingMethodViewCollectionTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/ShippingMethodViewCollectionTest.php new file mode 100644 index 000000000..5b648ddaf --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/ShippingMethodViewCollectionTest.php @@ -0,0 +1,599 @@ +createCollection(); + + $methodId = 'someMethodId'; + + $view = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $addResult = $collection->addMethodView($methodId, $view); + + $actualView = $collection->getMethodView($methodId); + + $this->assertEquals($collection, $addResult); + $this->assertNotNull($actualView); + $this->assertEquals($view, $actualView); + } + + public function testGetMethodViewWhenNotExists() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + + $actualView = $collection->getMethodView($methodId); + + $this->assertNull($actualView); + } + + public function testAddMethodViewWhenAlreadyExists() + { + $methodId = 'someMethodId'; + + $collection = $this->createCollection(); + + $view = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $view); + + $view2 = [ + 'someField3' => 'someValue4', + 'someField4' => 'someValue4', + 'sortOrder' => 1 + ]; + + $addMethodViewResult = $collection->addMethodView($methodId, $view2); + + $actualView = $collection->getMethodView($methodId); + + $this->assertNotNull($actualView); + $this->assertEquals($view, $actualView); + $this->assertEquals($collection, $addMethodViewResult); + } + + public function testHasMethodView() + { + $methodId = 'someMethodId'; + $collection = $this->createCollection(); + + $collection->addMethodView($methodId, []); + + $this->assertTrue($collection->hasMethodView($methodId)); + } + + public function testHasMethodViewNotExists() + { + $collection = $this->createCollection(); + + $this->assertFalse($collection->hasMethodView('someMethodId')); + } + + public function testRemoveMethodView() + { + $methodId = 'someMethodId'; + $collection = $this->createCollection(); + + $collection->addMethodView($methodId, []); + + $this->assertTrue($collection->hasMethodView($methodId)); + + $removeResult = $collection->removeMethodView($methodId); + + $this->assertEquals($collection, $removeResult); + $this->assertFalse($collection->hasMethodView($methodId)); + } + + public function testRemoveMethodViewWhenNotExists() + { + $methodId = 'someMethodId'; + $collection = $this->createCollection(); + + $removeResult = $collection->removeMethodView($methodId); + + $this->assertEquals($collection, $removeResult); + $this->assertFalse($collection->hasMethodView($methodId)); + } + + public function testAddAndGetMethodTypeView() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $methodView); + + $methodTypeView = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $addMethodTypeViewResult = $collection->addMethodTypeView($methodId, $methodTypeId, $methodTypeView); + + $actualMethodTypeView = $collection->getMethodTypeView($methodId, $methodTypeId); + + $this->assertEquals($collection, $addMethodTypeViewResult); + $this->assertEquals($methodTypeView, $actualMethodTypeView); + } + + public function testGetMethodTypeViewWhenNotExists() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $collection->addMethodView($methodId, []); + + $actualMethodTypeView = $collection->getMethodTypeView($methodId, $methodTypeId); + + $this->assertNull($actualMethodTypeView); + } + + public function testGetMethodTypeViewWhenMethodTypeNotExists() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $actualMethodTypeView = $collection->getMethodTypeView($methodId, $methodTypeId); + + $this->assertNull($actualMethodTypeView); + } + + public function testAddMethodTypeViewWhenAlreadyExists() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $methodView); + + $methodTypeView1 = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId, $methodTypeView1); + + $methodTypeView2 = [ + 'someTypeField3' => 'someTypeValue3', + 'someTypeField4' => 'someTypeValue4', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId, $methodTypeView2); + + $actualMethodTypeView = $collection->getMethodTypeView($methodId, $methodTypeId); + $this->assertEquals($methodTypeView1, $actualMethodTypeView); + } + + public function testAddMethodTypesViews() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $methodView); + + $methodTypeId = 'someMethodTypeId'; + + $methodTypeView1 = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId, $methodTypeView1); + + $methodTypeId2 = 'someOtherMethodTypeId'; + + $methodTypeView2 = [ + 'someTypeField3' => 'someTypeValue3', + 'someTypeField4' => 'someTypeValue4', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId2, $methodTypeView2); + + $this->assertEquals($methodTypeView1, $collection->getMethodTypeView($methodId, $methodTypeId)); + $this->assertEquals($methodTypeView2, $collection->getMethodTypeView($methodId, $methodTypeId2)); + } + + public function testHasMethodTypeView() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $collection->addMethodView($methodId, []); + $collection->addMethodTypeView($methodId, $methodTypeId, []); + + $this->assertTrue($collection->hasMethodTypeView($methodId, $methodTypeId)); + } + + public function testHasMethodTypeViewWhenNotExists() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $collection->addMethodView($methodId, []); + + $this->assertFalse($collection->hasMethodTypeView($methodId, $methodTypeId)); + } + + public function testHasMethodTypeViewWhenMethodNotExists() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $this->assertFalse($collection->hasMethodTypeView($methodId, $methodTypeId)); + } + + public function testRemoveMethodTypeView() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $collection->addMethodView($methodId, []); + $collection->addMethodTypeView($methodId, $methodTypeId, []); + + $this->assertTrue($collection->hasMethodTypeView($methodId, $methodTypeId)); + + $removeResult = $collection->removeMethodTypeView($methodId, $methodTypeId); + + $this->assertEquals($collection, $removeResult); + $this->assertNull($collection->getMethodTypeView($methodId, $methodTypeId)); + } + + public function testRemoveMethodTypeViewWhenNotExists() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $collection->addMethodView($methodId, []); + + $this->assertFalse($collection->hasMethodTypeView($methodId, $methodTypeId)); + + $removeResult = $collection->removeMethodTypeView($methodId, $methodTypeId); + + $this->assertEquals($collection, $removeResult); + $this->assertNull($collection->getMethodTypeView($methodId, $methodTypeId)); + } + + public function testRemoveMethodTypeViewWhenMethodNotExists() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + + $this->assertFalse($collection->hasMethodTypeView($methodId, $methodTypeId)); + + $removeResult = $collection->removeMethodTypeView($methodId, $methodTypeId); + + $this->assertEquals($collection, $removeResult); + $this->assertNull($collection->getMethodTypeView($methodId, $methodTypeId)); + } + + public function testGetAllMethodsViews() + { + $collection = $this->createCollection(); + + $this->assertEquals([], $collection->getAllMethodsViews()); + + $methodId = 'someMethodId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $methodView); + + $methodTypeId = 'someMethodTypeId'; + + $methodTypeView1 = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId, $methodTypeView1); + + $methodTypeId2 = 'someMethodTypeId2'; + + $methodTypeView2 = [ + 'someTypeField3' => 'someTypeValue3', + 'someTypeField4' => 'someTypeValue4', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId2, $methodTypeView2); + + $this->assertEquals([$methodId => $methodView], $collection->getAllMethodsViews()); + + $methodId2 = 'someOtherMethodId'; + + $methodView2 = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId2, $methodView2); + + $methodTypeId3 = 'someMethodTypeId3'; + + $methodTypeView3 = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $collection->addMethodTypeView($methodId2, $methodTypeId3, $methodTypeView3); + + $methodTypeId4 = 'someMethodTypeId4'; + + $methodTypeView4 = [ + 'someTypeField3' => 'someTypeValue3', + 'someTypeField4' => 'someTypeValue4', + ]; + + $collection->addMethodTypeView($methodId2, $methodTypeId4, $methodTypeView4); + + $this->assertEquals([$methodId => $methodView, $methodId2 => $methodView2], $collection->getAllMethodsViews()); + } + + public function testGetAllMethodsTypesViews() + { + $collection = $this->createCollection(); + + $this->assertEquals([], $collection->getAllMethodsViews()); + + $methodId = 'someMethodId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $methodView); + + $methodTypeId = 'someMethodTypeId'; + + $methodTypeView1 = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId, $methodTypeView1); + + $methodTypeId2 = 'someMethodTypeId2'; + + $methodTypeView2 = [ + 'someTypeField3' => 'someTypeValue3', + 'someTypeField4' => 'someTypeValue4', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId2, $methodTypeView2); + + $this->assertEquals( + [ + $methodId => [ + $methodTypeId => $methodTypeView1, + $methodTypeId2 => $methodTypeView2, + ], + ], + $collection->getAllMethodsTypesViews() + ); + + $methodId2 = 'someOtherMethodId'; + + $methodView2 = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId2, $methodView2); + + $methodTypeId3 = 'someMethodTypeId3'; + + $methodTypeView3 = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $collection->addMethodTypeView($methodId2, $methodTypeId3, $methodTypeView3); + + $methodTypeId4 = 'someMethodTypeId4'; + + $methodTypeView4 = [ + 'someTypeField3' => 'someTypeValue3', + 'someTypeField4' => 'someTypeValue4', + ]; + + $collection->addMethodTypeView($methodId2, $methodTypeId4, $methodTypeView4); + + $this->assertEquals( + [ + $methodId => [ + $methodTypeId => $methodTypeView1, + $methodTypeId2 => $methodTypeView2, + ], + $methodId2 => [ + $methodTypeId3 => $methodTypeView3, + $methodTypeId4 => $methodTypeView4, + ], + ], + $collection->getAllMethodsTypesViews() + ); + + $collection->clear(); + + $this->assertEquals([], $collection->getAllMethodsTypesViews()); + $this->assertEquals([], $collection->getAllMethodsViews()); + } + + public function testToArray() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId, $methodView); + + $methodTypeId = 'someMethodTypeId'; + + $methodTypeView1 = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId, $methodTypeView1); + + $methodTypeId2 = 'someMethodTypeId2'; + + $methodTypeView2 = [ + 'someTypeField3' => 'someTypeValue3', + 'someTypeField4' => 'someTypeValue4', + ]; + + $collection->addMethodTypeView($methodId, $methodTypeId2, $methodTypeView2); + + $methodId2 = 'someOtherMethodId'; + + $methodView2 = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + + $collection->addMethodView($methodId2, $methodView2); + + $methodTypeId3 = 'someMethodTypeId3'; + + $methodTypeView3 = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $collection->addMethodTypeView($methodId2, $methodTypeId3, $methodTypeView3); + + $methodTypeId4 = 'someMethodTypeId4'; + + $methodTypeView4 = [ + 'someTypeField3' => 'someTypeValue3', + 'someTypeField4' => 'someTypeValue4', + ]; + + $collection->addMethodTypeView($methodId2, $methodTypeId4, $methodTypeView4); + + $methodView[ShippingMethodViewCollection::TYPES_FIELD] = [ + $methodTypeId => $methodTypeView1, + $methodTypeId2 => $methodTypeView2, + ]; + $methodView2[ShippingMethodViewCollection::TYPES_FIELD] = [ + $methodTypeId3 => $methodTypeView3, + $methodTypeId4 => $methodTypeView4, + ]; + + $this->assertEquals( + [ + $methodId => $methodView, + $methodId2 => $methodView2 + ], + $collection->toArray() + ); + } + + public function testIsEmpty() + { + $collection = $this->createCollection(); + + $methodId = 'someMethodId'; + + $methodView = [ + 'someField1' => 'someValue1', + 'someField2' => 'someValue2', + 'sortOrder' => 1 + ]; + $methodTypeView = [ + 'someTypeField1' => 'someTypeValue1', + 'someTypeField2' => 'someTypeValue2', + ]; + + $this->assertTrue($collection->isEmpty()); + + $collection->addMethodView($methodId, $methodView); + + $collection->addMethodTypeView($methodId, 'someMethodTypeId', $methodTypeView); + + $this->assertFalse($collection->isEmpty()); + + $collection->clear(); + + $this->assertTrue($collection->isEmpty()); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/ShippingMethodViewFactoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/ShippingMethodViewFactoryTest.php new file mode 100644 index 000000000..254ab5da7 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/ShippingMethodViewFactoryTest.php @@ -0,0 +1,225 @@ +shippingMethodProviderMock = $this + ->getMockBuilder(ShippingMethodProviderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->shippingMethodViewFactory = new ShippingMethodViewFactory($this->shippingMethodProviderMock); + } + + public function testCreateMethodView() + { + $methodId = 'someId'; + $isGrouped = true; + $label = 'someLabel'; + $sortOrder = 5; + + $expected = [ + 'identifier' => $methodId, + 'isGrouped' => $isGrouped, + 'label' => $label, + 'sortOrder' => $sortOrder, + ]; + + $actual = $this->shippingMethodViewFactory->createMethodView($methodId, $label, $isGrouped, $sortOrder); + + $this->assertEquals($expected, $actual); + } + + public function testCreateMethodViewTest() + { + $methodId = 'someId'; + $label = 'someLabel'; + $sortOrder = 5; + $price = Price::create(5, 'USD'); + + $expected = [ + 'identifier' => $methodId, + 'label' => $label, + 'sortOrder' => $sortOrder, + 'price' => $price, + ]; + + $actual = $this->shippingMethodViewFactory->createMethodTypeView($methodId, $label, $sortOrder, $price); + + $this->assertEquals($expected, $actual); + } + + public function testCreateMethodViewByShippingMethod() + { + $methodId = 'someId'; + $isGrouped = true; + $label = 'someLabel'; + $sortOrder = 5; + + $methodMock = $this->getMockBuilder(ShippingMethodInterface::class)->getMock(); + + $methodMock + ->expects($this->once()) + ->method('getLabel') + ->willReturn($label); + + $methodMock + ->expects($this->once()) + ->method('isGrouped') + ->willReturn($isGrouped); + + $methodMock + ->expects($this->once()) + ->method('getSortOrder') + ->willReturn($sortOrder); + + $this->shippingMethodProviderMock + ->expects($this->once()) + ->method('getShippingMethod') + ->with($methodId) + ->willReturn($methodMock); + + $expected = [ + 'identifier' => $methodId, + 'isGrouped' => $isGrouped, + 'label' => $label, + 'sortOrder' => $sortOrder, + ]; + + $actual = $this->shippingMethodViewFactory->createMethodViewByShippingMethod($methodId); + + $this->assertEquals($expected, $actual); + } + + public function createMethodViewByShippingMethodWithNullMethod() + { + $methodId = 'someMethodId'; + + $this->shippingMethodProviderMock + ->expects($this->once()) + ->method('getShippingMethod') + ->with($methodId) + ->willReturn(null); + + $actual = $this->shippingMethodViewFactory->createMethodViewByShippingMethod($methodId); + + $this->assertEquals(null, $actual); + } + + public function testCreateMethodTypeViewByShippingMethodAndPrice() + { + $methodId = 'someId'; + $methodTypeId = 'someMethodTypeId'; + $label = 'someLabel'; + $sortOrder = 5; + $price = Price::create(5, 'USD'); + + $methodTypeMock = $this->getMockBuilder(ShippingMethodTypeInterface::class)->getMock(); + + $methodTypeMock + ->expects($this->once()) + ->method('getLabel') + ->willReturn($label); + + $methodTypeMock + ->expects($this->once()) + ->method('getSortOrder') + ->willReturn($sortOrder); + + $methodMock = $this->getMockBuilder(ShippingMethodInterface::class)->getMock(); + + $methodMock + ->expects($this->once()) + ->method('getType') + ->willReturn($methodTypeMock); + + $this->shippingMethodProviderMock + ->expects($this->once()) + ->method('getShippingMethod') + ->with($methodId) + ->willReturn($methodMock); + + $expected = [ + 'identifier' => $methodTypeId, + 'label' => $label, + 'sortOrder' => $sortOrder, + 'price' => $price, + ]; + + $actual = $this->shippingMethodViewFactory->createMethodTypeViewByShippingMethodAndPrice( + $methodId, + $methodTypeId, + $price + ); + + $this->assertEquals($expected, $actual); + } + + public function testCreateMethodTypeViewByShippingMethodAndPriceWithNullMethod() + { + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + $price = Price::create(5, 'USD'); + + $this->shippingMethodProviderMock + ->expects($this->once()) + ->method('getShippingMethod') + ->with($methodId) + ->willReturn(null); + + $actual = $this->shippingMethodViewFactory->createMethodTypeViewByShippingMethodAndPrice( + $methodId, + $methodTypeId, + $price + ); + + $this->assertEquals(null, $actual); + } + + public function testCreateMethodTypeViewByShippingMethodAndPriceWithNullMethodType() + { + $methodId = 'someMethodId'; + $methodTypeId = 'someMethodTypeId'; + $price = Price::create(5, 'USD'); + + $methodMock = $this->getMockBuilder(ShippingMethodInterface::class)->getMock(); + + $methodMock + ->expects($this->once()) + ->method('getType') + ->willReturn(null); + + $this->shippingMethodProviderMock + ->expects($this->once()) + ->method('getShippingMethod') + ->with($methodId) + ->willReturn($methodMock); + + $actual = $this->shippingMethodViewFactory->createMethodTypeViewByShippingMethodAndPrice( + $methodId, + $methodTypeId, + $price + ); + + $this->assertEquals(null, $actual); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Stub/TrackingAwareShippingMethodStub.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Stub/TrackingAwareShippingMethodStub.php new file mode 100644 index 000000000..73f1ce9f2 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Stub/TrackingAwareShippingMethodStub.php @@ -0,0 +1,81 @@ +shippingMethodProvider = $this->createMock(ShippingMethodProviderInterface::class); + $this->trackingAwareShippingMethodsProvider = + new TrackingAwareShippingMethodsProvider($this->shippingMethodProvider); + } + + /** + * @dataProvider getTrackingAwareShippingMethodsProvider + * + * @param array $methods + * @param int $trackingAwareCount + */ + public function testGetTrackingAwareShippingMethods(array $methods, $trackingAwareCount) + { + $this->shippingMethodProvider->expects(static::once()) + ->method('getShippingMethods') + ->willReturn($methods); + + static::assertCount( + $trackingAwareCount, + $this->trackingAwareShippingMethodsProvider->getTrackingAwareShippingMethods() + ); + } + + /** + * @return array + */ + public function getTrackingAwareShippingMethodsProvider() + { + return [ + [ + 'methods' => [ + $this->mockShippingMethod(ShippingMethodInterface::class, 'method1'), + $this->mockShippingMethod(ShippingMethodInterface::class, 'method2'), + $this->mockShippingMethod(TrackingAwareShippingMethodStub::class, 'method3') + ], + 'trackingAwareCount' => 1, + ], + [ + 'methods' => [ + $this->mockShippingMethod(ShippingMethodInterface::class, 'method1'), + $this->mockShippingMethod(ShippingMethodInterface::class, 'method2'), + $this->mockShippingMethod(TrackingAwareShippingMethodStub::class, 'method3'), + $this->mockShippingMethod(TrackingAwareShippingMethodStub::class, 'method4') + ], + 'trackingAwareCount' => 2, + ], + [ + 'methods' => [ + $this->mockShippingMethod(ShippingMethodInterface::class, 'method1'), + $this->mockShippingMethod(ShippingMethodInterface::class, 'method2'), + $this->mockShippingMethod(TrackingAwareShippingMethodStub::class, 'method3'), + $this->mockShippingMethod(TrackingAwareShippingMethodStub::class, 'method4'), + $this->mockShippingMethod(TrackingAwareShippingMethodStub::class, 'method5'), + + ], + 'trackingAwareCount' => 3, + ] + + ]; + } + + /** + * @param string $class + * @param string $identifier + * + * @return \PHPUnit\Framework\MockObject\MockObject + */ + protected function mockShippingMethod($class, $identifier) + { + $method = $this->createMock($class); + $method->expects(static::any()) + ->method('getIdentifier') + ->willReturn($identifier); + + return $method; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Basic/BasicShippingMethodValidatorTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Basic/BasicShippingMethodValidatorTest.php new file mode 100644 index 000000000..cbea426e3 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Basic/BasicShippingMethodValidatorTest.php @@ -0,0 +1,47 @@ +commonShippingMethodValidatorResultFactory = $this->createMock( + Common\CommonShippingMethodValidatorResultFactoryInterface::class + ); + + $this->validator = new BasicShippingMethodValidator($this->commonShippingMethodValidatorResultFactory); + } + + public function testValidate() + { + /** @var ShippingMethodInterface $shippingMethod */ + $shippingMethod = $this->createMock(ShippingMethodInterface::class); + + $result = $this->createMock(ShippingMethodValidatorResultInterface::class); + + $this->commonShippingMethodValidatorResultFactory->expects(static::once()) + ->method('createSuccessResult') + ->willReturn($result); + + static::assertSame($result, $this->validator->validate($shippingMethod)); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/EnabledShippingMethodsByRules/EnabledShippingMethodsByRulesShippingMethodValidatorDecoratorTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/EnabledShippingMethodsByRules/EnabledShippingMethodsByRulesShippingMethodValidatorDecoratorTest.php new file mode 100644 index 000000000..8007e9a13 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/EnabledShippingMethodsByRules/EnabledShippingMethodsByRulesShippingMethodValidatorDecoratorTest.php @@ -0,0 +1,307 @@ +parentShippingMethodValidator = $this->createMock(ShippingMethodValidatorInterface::class); + $this->errorFactory = $this->createMock( + Common\CommonShippingMethodValidatorResultErrorFactoryInterface::class + ); + $this->nonDeletableTypeIdentifiersProvider = $this->createMock( + NonDeletableMethodTypeIdentifiersProviderInterface::class + ); + $this->methodTypeLabelsProvider = $this->createMock(MethodTypeLabelsProviderInterface::class); + $this->translator = $this->createMock(TranslatorInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->validator = + new Validator\EnabledShippingMethodsByRules\EnabledShippingMethodsByRulesShippingMethodValidatorDecorator( + $this->parentShippingMethodValidator, + $this->errorFactory, + $this->nonDeletableTypeIdentifiersProvider, + $this->methodTypeLabelsProvider, + $this->translator, + $this->logger + ); + } + + public function testValidateNoIdentifier() + { + $method = $this->getShippingMethodMock(); + + $result = $this->createMock(ShippingMethodValidatorResultInterface::class); + + $this->parentShippingMethodValidator->expects(static::once()) + ->method('validate') + ->with($method) + ->willReturn($result); + + $this->nonDeletableTypeIdentifiersProvider->expects(static::once()) + ->method('getMethodTypeIdentifiers') + ->with($method) + ->willReturn([]); + + static::assertSame($result, $this->validator->validate($method)); + } + + public function testValidateLabelException() + { + $methodId = 'method_1'; + + $method = $this->getShippingMethodMock(); + $method->expects(static::once()) + ->method('getIdentifier') + ->willReturn($methodId); + + $result = $this->createMock(ShippingMethodValidatorResultInterface::class); + + $this->parentShippingMethodValidator->expects(static::once()) + ->method('validate') + ->with($method) + ->willReturn($result); + + $typeIdentifiers = [ + 'type_1', + ]; + + $this->nonDeletableTypeIdentifiersProvider->expects(static::once()) + ->method('getMethodTypeIdentifiers') + ->with($method) + ->willReturn($typeIdentifiers); + + $errorMessage = 'Error message'; + + $exception = new InvalidArgumentException($errorMessage); + + $this->methodTypeLabelsProvider->expects(static::once()) + ->method('getLabels') + ->with($methodId, $typeIdentifiers) + ->willThrowException($exception); + + $this->logger->expects(static::once()) + ->method('error') + ->with( + $errorMessage, + [ + 'method_identifier' => $methodId, + 'type_identifiers' => $typeIdentifiers, + ] + ); + + static::assertSame($result, $this->validator->validate($method)); + } + + public function testValidate() + { + $methodId = 'method_1'; + + $method = $this->getShippingMethodMock(); + $method->expects(static::once()) + ->method('getIdentifier') + ->willReturn($methodId); + + $result = $this->createMock(ShippingMethodValidatorResultInterface::class); + + $this->parentShippingMethodValidator->expects(static::once()) + ->method('validate') + ->with($method) + ->willReturn($result); + + $typeIdentifiers = [ + 'type_1', + 'type_2', + ]; + + $typeLabels = [ + 'Label 1', + 'Label 2', + ]; + + $this->nonDeletableTypeIdentifiersProvider->expects(static::once()) + ->method('getMethodTypeIdentifiers') + ->with($method) + ->willReturn($typeIdentifiers); + + $this->methodTypeLabelsProvider->expects(static::once()) + ->method('getLabels') + ->with($methodId, $typeIdentifiers) + ->willReturn($typeLabels); + + $translatedMessage = 'validation message'; + + $this->translator->expects(static::once()) + ->method('trans') + ->with( + 'marello.shipping.method_type.used.error', + ['%types%' => implode(', ', $typeLabels)] + ) + ->willReturn($translatedMessage); + + $clonedAndBuiltErrorCollection = $this->createErrorCollectionMock($result, $translatedMessage); + + $errorResult = $this->createErrorResultMock($result, $clonedAndBuiltErrorCollection); + + static::assertSame($errorResult, $this->validator->validate($method)); + } + + /** + * @param \PHPUnit\Framework\MockObject\MockObject $result + * @param string $translatedMessage + * + * @return Validator\Result\Error\Collection\ShippingMethodValidatorResultErrorCollectionInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function createErrorCollectionMock(\PHPUnit\Framework\MockObject\MockObject $result, $translatedMessage) + { + $errorCollection = $this->createMock( + Validator\Result\Error\Collection\ShippingMethodValidatorResultErrorCollectionInterface::class + ); + + $builder = $this->createMock( + Validator\Result\Error\Collection\Builder\Common\ + CommonShippingMethodValidatorResultErrorCollectionBuilderInterface::class + ); + + $errorCollection->expects(static::once()) + ->method('createCommonBuilder') + ->willReturn($builder); + + $result->expects(static::any()) + ->method('getErrors') + ->willReturn($errorCollection); + + $error = $this->createMock(Validator\Result\Error\ShippingMethodValidatorResultErrorInterface::class); + + $this->errorFactory->expects(static::once()) + ->method('createError') + ->with($translatedMessage) + ->willReturn($error); + + $builder->expects(static::once()) + ->method('cloneAndBuild') + ->with($errorCollection) + ->willReturn($builder); + + $builder->expects(static::once()) + ->method('addError') + ->with($error) + ->willReturn($builder); + + $clonedAndBuiltErrorCollection = $this->createMock( + Validator\Result\Error\Collection\ShippingMethodValidatorResultErrorCollectionInterface::class + ); + + $builder->expects(static::once()) + ->method('getCollection') + ->willReturn($clonedAndBuiltErrorCollection); + + return $clonedAndBuiltErrorCollection; + } + + /** + * @param \PHPUnit\Framework\MockObject\MockObject $result + * @param Validator\Result\Error\Collection\ShippingMethodValidatorResultErrorCollectionInterface $errorCollection + * + * @return ShippingMethodValidatorResultInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function createErrorResultMock( + \PHPUnit\Framework\MockObject\MockObject $result, + Validator\Result\Error\Collection\ShippingMethodValidatorResultErrorCollectionInterface $errorCollection + ) { + $resultFactory = $this->createMock( + Validator\Result\Factory\Common\CommonShippingMethodValidatorResultFactoryInterface::class + ); + + $result->expects(static::once()) + ->method('createCommonFactory') + ->willReturn($resultFactory); + + $errorResult = $this->createMock(ShippingMethodValidatorResultInterface::class); + + $resultFactory->expects(static::once()) + ->method('createErrorResult') + ->with($errorCollection) + ->willReturn($errorResult); + + return $errorResult; + } + + /** + * @param string $methodIdentifier + * @param string[] $methodTypeIdentifiers + * + * @return string[] + */ + private function getShippingMethodTypesLabels($methodIdentifier, array $methodTypeIdentifiers) + { + try { + return $this->methodTypeLabelsProvider->getLabels($methodIdentifier, $methodTypeIdentifiers); + } catch (InvalidArgumentException $exception) { + $this->logger->error($exception->getMessage(), [ + 'method_identifier' => $methodIdentifier, + 'type_identifiers' => $methodTypeIdentifiers, + ]); + + return []; + } + } + + /** + * @return ShippingMethodInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function getShippingMethodMock() + { + return $this->createMock(ShippingMethodInterface::class); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/Collection/Builder/Common/Doctrine/DoctrineCommonShippingMethodValidatorResultErrorCollectionBuilderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/Collection/Builder/Common/Doctrine/DoctrineCommonShippingMethodValidatorResultErrorCollectionBuilderTest.php new file mode 100644 index 000000000..43a5078e8 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/Collection/Builder/Common/Doctrine/DoctrineCommonShippingMethodValidatorResultErrorCollectionBuilderTest.php @@ -0,0 +1,41 @@ +createMock(Error\ShippingMethodValidatorResultErrorInterface::class); + + $builder = new Builder\Common\Doctrine\DoctrineCommonShippingMethodValidatorResultErrorCollectionBuilder(); + + static::assertSame($builder, $builder->addError($error)); + + static::assertEquals( + new Error\Collection\Doctrine\DoctrineShippingMethodValidatorResultErrorCollection([$error]), + $builder->getCollection() + ); + } + + public function testCloneAndBuild() + { + /** @var Error\ShippingMethodValidatorResultErrorInterface $error */ + $error = $this->createMock(Error\ShippingMethodValidatorResultErrorInterface::class); + + $errorCollection = new Error\Collection\Doctrine\DoctrineShippingMethodValidatorResultErrorCollection([$error]); + + $builder = new Builder\Common\Doctrine\DoctrineCommonShippingMethodValidatorResultErrorCollectionBuilder(); + + static::assertSame($builder, $builder->cloneAndBuild($errorCollection)); + + static::assertEquals( + $errorCollection, + $builder->getCollection() + ); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/Collection/Doctrine/DoctrineShippingMethodValidatorResultErrorCollectionTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/Collection/Doctrine/DoctrineShippingMethodValidatorResultErrorCollectionTest.php new file mode 100644 index 000000000..0a8000caa --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/Collection/Doctrine/DoctrineShippingMethodValidatorResultErrorCollectionTest.php @@ -0,0 +1,18 @@ +createCommonBuilder() + ); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/Factory/Common/ParameterBag/ParameterBagCommonShippingMethodValidatorResultErrorFactoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/Factory/Common/ParameterBag/ParameterBagCommonShippingMethodValidatorResultErrorFactoryTest.php new file mode 100644 index 000000000..b971c5efc --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/Factory/Common/ParameterBag/ParameterBagCommonShippingMethodValidatorResultErrorFactoryTest.php @@ -0,0 +1,22 @@ + $message, + ] + ), $factory->createError($message)); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/ParameterBag/ParameterBagShippingMethodValidatorResultErrorTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/ParameterBag/ParameterBagShippingMethodValidatorResultErrorTest.php new file mode 100644 index 000000000..d5c6f6f05 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Error/ParameterBag/ParameterBagShippingMethodValidatorResultErrorTest.php @@ -0,0 +1,17 @@ + $message, + ]); + static::assertEquals($message, $error->getMessage()); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Factory/Common/ParameterBag/ParameterBagCommonShippingMethodValidatorResultFactoryTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Factory/Common/ParameterBag/ParameterBagCommonShippingMethodValidatorResultFactoryTest.php new file mode 100644 index 000000000..84f2ebbb0 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/Factory/Common/ParameterBag/ParameterBagCommonShippingMethodValidatorResultFactoryTest.php @@ -0,0 +1,43 @@ +factory = new Factory\Common\ParameterBag\ParameterBagCommonShippingMethodValidatorResultFactory(); + } + + public function testCreateSuccessResult() + { + static::assertEquals(new ParameterBagShippingMethodValidatorResult( + [ + 'errors' => new Error\Collection\Doctrine\DoctrineShippingMethodValidatorResultErrorCollection(), + ] + ), $this->factory->createSuccessResult()); + } + + public function testCreateErrorResult() + { + /** @var Error\Collection\ShippingMethodValidatorResultErrorCollectionInterface $errors */ + $errors = $this->createMock(Error\Collection\ShippingMethodValidatorResultErrorCollectionInterface::class); + static::assertEquals(new ParameterBagShippingMethodValidatorResult( + [ + 'errors' => $errors, + ] + ), $this->factory->createErrorResult($errors)); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/ParameterBag/ParameterBagShippingMethodValidatorResultTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/ParameterBag/ParameterBagShippingMethodValidatorResultTest.php new file mode 100644 index 000000000..4e28dd3f2 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Method/Validator/Result/ParameterBag/ParameterBagShippingMethodValidatorResultTest.php @@ -0,0 +1,28 @@ +createCommonFactory() + ); + } + + public function testGetErrors() + { + $errors = new \ArrayObject(); + $result = new ParameterBagShippingMethodValidatorResult([ + 'errors' => $errors, + ]); + static::assertSame($errors, $result->getErrors()); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/BasicShippingMethodChoicesProviderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/BasicShippingMethodChoicesProviderTest.php new file mode 100644 index 000000000..f2b8e4249 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/BasicShippingMethodChoicesProviderTest.php @@ -0,0 +1,135 @@ +shippingMethodProvider = $this->createMock(ShippingMethodProviderInterface::class); + $this->translator = $this->createMock(TranslatorInterface::class); + $this->choicesProvider = new BasicShippingMethodChoicesProvider( + $this->shippingMethodProvider, + $this->translator + ); + } + + /** + * @param array $methods + * @param array $result + * @param bool $translate + * + * @dataProvider methodsProvider + */ + public function testGetMethods($methods, $result, $translate = false) + { + $translation = [ + ['flat rate', [], null, null, 'flat rate translated'], + ['ups', [], null, null, 'ups translated'], + ]; + + $this->shippingMethodProvider->expects($this->once()) + ->method('getShippingMethods') + ->willReturn($methods); + + $this->translator->expects($this->any()) + ->method('trans') + ->will($this->returnValueMap($translation)); + + $this->assertEquals($result, $this->choicesProvider->getMethods($translate)); + } + + /** + * @return array + */ + public function methodsProvider() + { + return + [ + 'some_methods' => + [ + 'methods' => + [ + 'ups' => $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'ups', + 'sortOrder' => 1, + 'label' => 'ups', + 'isEnabled' => false, + 'types' => [], + ] + ), + 'flat_rate' => $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'flat_rate', + 'sortOrder' => 1, + 'label' => 'flat rate', + 'isEnabled' => true, + 'types' => [], + ] + ), + ], + 'result' => ['ups' => 'ups', 'flat_rate' => 'flat rate'], + 'translate' => false, + ], + 'some_methods_with_translation' => + [ + 'methods' => + [ + 'flat_rate' => $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'flat_rate', + 'sortOrder' => 1, + 'label' => 'flat rate', + 'isEnabled' => true, + 'types' => [], + ] + ), + 'ups' => $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'ups', + 'sortOrder' => 1, + 'label' => 'ups', + 'isEnabled' => false, + 'types' => [], + ] + ), + ], + 'result' => ['flat_rate' => 'flat rate translated', 'ups' => 'ups translated'], + 'translate' => true, + ], + 'no_methods' => + [ + 'methods' => [], + 'result' => [], + ], + ]; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Cache/ShippingPriceCacheTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Cache/ShippingPriceCacheTest.php new file mode 100644 index 000000000..548b822cd --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Cache/ShippingPriceCacheTest.php @@ -0,0 +1,152 @@ +cacheProvider = $this->getMockBuilder(CacheProvider::class) + ->setMethods(['fetch', 'contains', 'save', 'deleteAll'])->getMockForAbstractClass(); + + $this->keyGenerator = $this->createMock(ShippingContextCacheKeyGenerator::class); + $this->keyGenerator->expects(static::any()) + ->method('generateKey') + ->will(static::returnCallback(function (ShippingContextInterface $context) { + return ($context->getSourceEntity() ? get_class($context->getSourceEntity()) : '') + .'_'.$context->getSourceEntityIdentifier(); + })); + + $this->cache = new ShippingPriceCache($this->cacheProvider, $this->keyGenerator); + } + + /** + * @dataProvider hasPriceDataProvider + * @param boolean $isContains + * @param boolean $hasPrice + */ + public function testHasPrice($isContains, $hasPrice) + { + $context = $this->createShippingContext([]); + + $this->cacheProvider->expects(static::once()) + ->method('contains') + ->with('_flat_rateprimary') + ->willReturn($isContains); + + static::assertEquals($hasPrice, $this->cache->hasPrice($context, 'flat_rate', 'primary')); + } + + + public function hasPriceDataProvider() + { + return [ + [ + 'isContains' => true, + 'hasPrice' => true, + ], + [ + 'isContains' => false, + 'hasPrice' => false, + ] + ]; + } + + /** + * @dataProvider getPriceDataProvider + * @param boolean $isContains + * @param Price|null $price + */ + public function testGetPrice($isContains, Price $price = null) + { + $context = $this->createShippingContext([]); + $this->cacheProvider->expects(static::any()) + ->method('contains') + ->with('_flat_rateprimary') + ->willReturn($isContains); + $this->cacheProvider->expects(static::any()) + ->method('fetch') + ->with('_flat_rateprimary') + ->willReturn($isContains ? $price : false); + + static::assertSame($price, $this->cache->getPrice($context, 'flat_rate', 'primary')); + } + + public function getPriceDataProvider() + { + return [ + [ + 'isContains' => true, + 'price' => Price::create(5, 'USD'), + ], + [ + 'isContains' => false, + 'price' => null, + ] + ]; + } + + public function testSavePrice() + { + $context = $this->createShippingContext([ + ShippingContext::FIELD_SOURCE_ENTITY => new \stdClass(), + ShippingContext::FIELD_SOURCE_ENTITY_ID => 1 + ]); + + $price = Price::create(10, 'USD'); + + $this->cacheProvider->expects(static::once()) + ->method('save') + ->with('stdClass_1flat_rateprimary', $price, ShippingPriceCache::CACHE_LIFETIME) + ->willReturn($price); + + static::assertEquals($this->cache, $this->cache->savePrice($context, 'flat_rate', 'primary', $price)); + } + + public function testDeleteAllPrices() + { + $this->cacheProvider->expects(static::once()) + ->method('deleteAll'); + + $this->cache->deleteAllPrices(); + } + + /** + * @param array $params + * + * @return ShippingContext + */ + private function createShippingContext(array $params) + { + $actualParams = array_merge([ + ShippingContext::FIELD_LINE_ITEMS => new DoctrineShippingLineItemCollection([]) + ], $params); + + return new ShippingContext($actualParams); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/EnabledMethodsShippingPriceProviderDecoratorTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/EnabledMethodsShippingPriceProviderDecoratorTest.php new file mode 100644 index 000000000..d811d6816 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/EnabledMethodsShippingPriceProviderDecoratorTest.php @@ -0,0 +1,158 @@ +shippingPriceProvider = $this->createMock(ShippingPriceProviderInterface::class); + $this->shippingMethodProvider = $this->createMock(ShippingMethodProviderInterface::class); + $this->decorator = new EnabledMethodsShippingPriceProviderDecorator( + $this->shippingPriceProvider, + $this->shippingMethodProvider + ); + } + + /** + * @param array $methods + * @param array $methodViews + * @param array $expectedMethodViews + * @dataProvider getApplicableMethodsViewsProvider + */ + public function testGetApplicableMethodsViews($methods, $methodViews, $expectedMethodViews) + { + $context = $this->createMock(ShippingContext::class); + + $methodViewCollection = new ShippingMethodViewCollection(); + foreach ($methodViews as $id => $view) { + $methodViewCollection ->addMethodView($id, $view); + } + + $expectedCollection = new ShippingMethodViewCollection(); + foreach ($expectedMethodViews as $id => $view) { + $expectedCollection ->addMethodView($id, $view); + } + + $this->shippingPriceProvider->expects($this->any()) + ->method('getApplicableMethodsViews') + ->with($context) + ->willReturn($methodViewCollection); + + $this->shippingMethodProvider->expects($this->any()) + ->method('getShippingMethod') + ->will($this->returnCallback(function ($methodId) use ($methods) { + return array_key_exists($methodId, $methods) ? $methods[$methodId] : null; + })); + + $this->assertEquals($expectedCollection, $this->decorator->getApplicableMethodsViews($context)); + } + + /** + * @return array + */ + public function getApplicableMethodsViewsProvider() + { + return [ + 'all_methods_enabled' => [ + 'methods' => [ + 'flat_rate' => $this->getEntity(ShippingMethodStub::class, [ + 'identifier' => 'flat_rate', + 'sortOrder' => 1, + 'isEnabled' => true, + 'types' => [] + ]), + 'ups' => $this->getEntity(PriceAwareShippingMethodStub::class, [ + 'identifier' => 'ups', + 'sortOrder' => 2, + 'isEnabled' => true, + 'types' => [] + ]) + ], + 'method_views' => [ + 'flat_rate' => ['flat_rate', false, 'flat_rate', 1], + 'ups' => ['ups', false, 'ups', 2], + ], + 'expected_method_views' => [ + 'flat_rate' => ['flat_rate', false, 'flat_rate', 1], + 'ups' => ['ups', false, 'ups', 2], + ] + ], + 'all_methods_disabled' => [ + 'methods' => [ + 'flat_rate' => $this->getEntity(ShippingMethodStub::class, [ + 'identifier' => 'flat_rate', + 'sortOrder' => 1, + 'isEnabled' => false, + 'types' => [] + ]), + 'ups' => $this->getEntity(PriceAwareShippingMethodStub::class, [ + 'identifier' => 'ups', + 'sortOrder' => 2, + 'isEnabled' => false, + 'types' => [] + ]) + ], + 'method_views' => [ + 'flat_rate' => ['flat_rate', false, 'flat_rate', 1], + 'ups' => ['ups', false, 'ups', 2], + ], + 'expected_method_views' => [] + ], + 'some_methods_enabled' => [ + 'methods' => [ + 'flat_rate' => $this->getEntity(ShippingMethodStub::class, [ + 'identifier' => 'flat_rate', + 'sortOrder' => 1, + 'isEnabled' => true, + 'types' => [] + ]), + 'ups' => $this->getEntity(PriceAwareShippingMethodStub::class, [ + 'identifier' => 'ups', + 'sortOrder' => 2, + 'isEnabled' => false, + 'types' => [] + ]) + ], + 'method_views' => [ + 'flat_rate' => ['flat_rate', false, 'flat_rate', 1], + 'ups' => ['ups', false, 'ups', 2], + ], + 'expected_method_views' => [ + 'flat_rate' => ['flat_rate', false, 'flat_rate', 1], + ] + ], + 'no_methods' => [ + 'methods' => [], + 'method_views' => [], + 'expected_method_views' => [] + ] + ]; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/EnabledShippingMethodChoicesProviderDecoratorTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/EnabledShippingMethodChoicesProviderDecoratorTest.php new file mode 100644 index 000000000..58ad6aa23 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/EnabledShippingMethodChoicesProviderDecoratorTest.php @@ -0,0 +1,178 @@ +shippingMethodProvider = $this->createMock(ShippingMethodProviderInterface::class); + $this->choicesProvider = $this->createMock(ShippingMethodChoicesProviderInterface::class); + $this->enabledChoicesProvider = new EnabledShippingMethodChoicesProviderDecorator( + $this->shippingMethodProvider, + $this->choicesProvider + ); + } + + /** + * @param array $registryMap + * @param array $choices + * @param array $result + * + * @dataProvider methodsProvider + */ + public function testGetMethods($registryMap, $choices, $result) + { + $this->shippingMethodProvider->expects($this->any()) + ->method('getShippingMethod') + ->will($this->returnValueMap($registryMap)); + + $this->choicesProvider->expects($this->once()) + ->method('getMethods') + ->willReturn($choices); + + $this->assertEquals($result, $this->enabledChoicesProvider->getMethods()); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function methodsProvider() + { + return + [ + 'all_methods_enabled' => + [ + 'methods_map' => + [ + [ + 'flat_rate', + $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'flat_rate', + 'sortOrder' => 1, + 'label' => 'flat rate', + 'isEnabled' => true, + 'types' => [], + ] + ), + ], + [ + 'ups', + $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'ups', + 'sortOrder' => 1, + 'label' => 'ups', + 'isEnabled' => true, + 'types' => [], + ] + ), + ], + ], + 'choices' => ['flat_rate' => 'flat rate', 'ups' => 'ups'], + 'result' => ['ups' => 'ups', 'flat_rate' => 'flat rate'], + ], + 'some_methods_disabled' => + [ + 'methods_map' => + [ + [ + 'flat_rate', + $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'flat_rate', + 'sortOrder' => 1, + 'label' => 'flat rate', + 'isEnabled' => true, + 'types' => [], + ] + ), + ], + [ + 'ups', + $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'ups', + 'sortOrder' => 1, + 'label' => 'ups', + 'isEnabled' => false, + 'types' => [], + ] + ), + ], + ], + 'choices' => ['flat_rate' => 'flat rate', 'ups' => 'ups'], + 'result' => ['flat_rate' => 'flat rate',], + ], + 'all_disabled_methods' => + [ + 'methods_map' => + [ + [ + 'flat_rate', + $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'flat_rate', + 'sortOrder' => 1, + 'label' => 'flat rate', + 'isEnabled' => false, + 'types' => [], + ] + ), + ], + [ + 'ups', + $this->getEntity( + ShippingMethodStub::class, + [ + 'identifier' => 'ups', + 'sortOrder' => 1, + 'label' => 'ups', + 'isEnabled' => false, + 'types' => [], + ] + ), + ], + ], + 'choices' => ['flat rate' => 'flat_rate', 'ups' => 'ups'], + 'result' => [], + ], + 'no_methods' => + [ + 'methods' => [], + 'choices' => [], + 'result' => [], + ], + ]; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/Basic/BasicMethodsConfigsRulesByContextProviderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/Basic/BasicMethodsConfigsRulesByContextProviderTest.php new file mode 100644 index 000000000..da4a768e3 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/Basic/BasicMethodsConfigsRulesByContextProviderTest.php @@ -0,0 +1,114 @@ +repository = $this->createMock(ShippingMethodsConfigsRuleRepository::class); + + $this->filtrationService = $this->createMock(MethodsConfigsRulesFiltrationServiceInterface::class); + + $this->provider = new BasicMethodsConfigsRulesByContextProvider( + $this->filtrationService, + $this->repository + ); + } + + public function testGetAllFilteredShippingMethodsConfigsWithShippingAddress() + { + $currency = 'USD'; + $address = $this->createAddressMock(); + $rulesFromDb = [ + $this->createShippingMethodsConfigsRuleMock(), + $this->createShippingMethodsConfigsRuleMock(), + ]; + + $this->repository->expects(static::once()) + ->method('getByDestinationAndCurrency') + ->with($address, $currency) + ->willReturn($rulesFromDb); + + $this->repository->expects(static::never()) + ->method('getByCurrencyWithoutDestination'); + + $context = $this->createShippingContextMock(); + $context->method('getCurrency') + ->willReturn($currency); + $context->method('getShippingAddress') + ->willReturn($address); + + $expectedRules = [$this->createShippingMethodsConfigsRuleMock()]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredShippingMethodsConfigsRules') + ->with($rulesFromDb) + ->willReturn($expectedRules); + + static::assertSame( + $expectedRules, + $this->provider->getShippingMethodsConfigsRules($context) + ); + } + + public function testGetAllFilteredShippingMethodsConfigsWithoutShippingAddress() + { + $currency = 'USD'; + $rulesFromDb = [$this->createShippingMethodsConfigsRuleMock()]; + + $this->repository->expects(static::once()) + ->method('getByCurrencyWithoutDestination') + ->with($currency) + ->willReturn($rulesFromDb); + + $context = $this->createShippingContextMock(); + $context->method('getCurrency') + ->willReturn($currency); + + $expectedRules = [$this->createShippingMethodsConfigsRuleMock()]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredShippingMethodsConfigsRules') + ->with($rulesFromDb) + ->willReturn($expectedRules); + + static::assertSame( + $expectedRules, + $this->provider->getShippingMethodsConfigsRules($context) + ); + } + + /** + * @return AddressInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function createAddressMock() + { + return $this->createMock(AddressInterface::class); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/RegardlessDestination/RegardlessDestinationMethodsConfigsRulesByContextProviderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/RegardlessDestination/RegardlessDestinationMethodsConfigsRulesByContextProviderTest.php new file mode 100644 index 000000000..0b6dbe2db --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/MethodsConfigsRule/Context/RegardlessDestination/RegardlessDestinationMethodsConfigsRulesByContextProviderTest.php @@ -0,0 +1,115 @@ +repository = $this->createMock(ShippingMethodsConfigsRuleRepository::class); + + $this->filtrationService = $this->createMock(MethodsConfigsRulesFiltrationServiceInterface::class); + + $this->provider = new RegardlessDestination\RegardlessDestinationMethodsConfigsRulesByContextProvider( + $this->filtrationService, + $this->repository + ); + } + + public function testGetAllFilteredShippingMethodsConfigsWithShippingAddress() + { + $currency = 'USD'; + $address = $this->createAddressMock(); + $rulesFromDb = [$this->createShippingMethodsConfigsRuleMock()]; + + $this->repository->expects(static::once()) + ->method('getByDestinationAndCurrency') + ->with($address, $currency) + ->willReturn($rulesFromDb); + + $this->repository->expects(static::never()) + ->method('getByCurrency'); + + $context = $this->createShippingContextMock(); + $context->method('getCurrency') + ->willReturn($currency); + $context->method('getShippingAddress') + ->willReturn($address); + + $expectedRules = [ + $this->createShippingMethodsConfigsRuleMock(), + $this->createShippingMethodsConfigsRuleMock(), + ]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredShippingMethodsConfigsRules') + ->with($rulesFromDb) + ->willReturn($expectedRules); + + static::assertSame( + $expectedRules, + $this->provider->getShippingMethodsConfigsRules($context) + ); + } + + public function testGetAllFilteredShippingMethodsConfigsWithoutShippingAddress() + { + $currency = 'USD'; + $rulesFromDb = [$this->createShippingMethodsConfigsRuleMock()]; + + $this->repository->expects(static::once()) + ->method('getByCurrency') + ->with($currency) + ->willReturn($rulesFromDb); + + $context = $this->createShippingContextMock(); + $context->method('getCurrency') + ->willReturn($currency); + + $expectedRules = [$this->createShippingMethodsConfigsRuleMock()]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredShippingMethodsConfigsRules') + ->with($rulesFromDb) + ->willReturn($expectedRules); + + static::assertSame( + $expectedRules, + $this->provider->getShippingMethodsConfigsRules($context) + ); + } + + /** + * @return AddressInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function createAddressMock() + { + return $this->createMock(AddressInterface::class); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/PriceAwareShippingMethodStub.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/PriceAwareShippingMethodStub.php new file mode 100644 index 000000000..e0bc41d18 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/PriceAwareShippingMethodStub.php @@ -0,0 +1,19 @@ +setIdentifier(self::METHOD_TYPE_IDENTIFIER); + + $method = new ShippingMethodStub(); + $method->setIdentifier(self::METHOD_IDENTIFIER) + ->setIsGrouped(false) + ->setTypes([$type]); + + $this->method = $method; + } + + /** + * {@inheritdoc} + */ + public function getShippingMethods() + { + return [$this->method->getIdentifier() => $this->method]; + } + + /** + * {@inheritdoc} + */ + public function getShippingMethod($name) + { + if ($name === $this->method->getIdentifier()) { + return $this->method; + } + return null; + } + + /** + * {@inheritdoc} + */ + public function hasShippingMethod($name) + { + return $name === $this->method->getIdentifier(); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/ShippingMethodStub.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/ShippingMethodStub.php new file mode 100644 index 000000000..52149a94c --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/ShippingMethodStub.php @@ -0,0 +1,181 @@ +types; + } + + /** + * @param ShippingMethodTypeStub[] $types + * @return $this + */ + public function setTypes($types) + { + $this->types = $types; + + return $this; + } + + /** + * @return string + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * @param string $identifier + * @return null|ShippingMethodTypeStub + */ + public function getType($identifier) + { + foreach ($this->types as $type) { + if ($type->getIdentifier() === $identifier) { + return $type; + } + } + + return null; + } + + /** + * @param string $identifier + * @return $this + */ + public function setIdentifier($identifier) + { + $this->identifier = $identifier; + + return $this; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label ?: $this->identifier . '.label'; + } + + /** + * @param string $label + * @return $this + */ + public function setLabel($label) + { + $this->label = $label; + + return $this; + } + + /** + * @return int + */ + public function getSortOrder() + { + return $this->sortOrder; + } + + /** + * @param int $sortOrder + * @return $this + */ + public function setSortOrder($sortOrder) + { + $this->sortOrder = $sortOrder; + + return $this; + } + + /** + * @return string + */ + public function getOptionsConfigurationFormType() + { + return $this->optionsConfigurationFormType; + } + + /** + * @param string $optionsConfigurationFormType + * @return $this + */ + public function setOptionsConfigurationFormType($optionsConfigurationFormType) + { + $this->optionsConfigurationFormType = $optionsConfigurationFormType; + + return $this; + } + + /** + * @return boolean + */ + public function isGrouped() + { + return $this->isGrouped; + } + + /** + * @return boolean + */ + public function isEnabled() + { + return $this->isEnabled; + } + + /** + * @param boolean $isGrouped + * @return $this + */ + public function setIsGrouped($isGrouped) + { + $this->isGrouped = $isGrouped; + + return $this; + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/ShippingMethodTypeConfigTypeOptionsStub.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/ShippingMethodTypeConfigTypeOptionsStub.php new file mode 100644 index 000000000..52235875a --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/ShippingMethodTypeConfigTypeOptionsStub.php @@ -0,0 +1,22 @@ +add('price', TextType::class) + ->add('handling_fee', TextType::class) + ->add('type', TextType::class); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/ShippingMethodTypeStub.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/ShippingMethodTypeStub.php new file mode 100644 index 000000000..99432c419 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/Stub/ShippingMethodTypeStub.php @@ -0,0 +1,120 @@ +identifier; + } + + /** + * @param string $identifier + * @return $this + */ + public function setIdentifier($identifier) + { + $this->identifier = $identifier; + return $this; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label ?: $this->identifier . '.label'; + } + + /** + * @param string $label + * @return $this + */ + public function setLabel($label) + { + $this->label = $label; + return $this; + } + + /** + * @return int + */ + public function getSortOrder() + { + return $this->sortOrder; + } + + /** + * @param int $sortOrder + * @return $this + */ + public function setSortOrder($sortOrder) + { + $this->sortOrder = $sortOrder; + return $this; + } + + /** + * @return string + */ + public function getOptionsConfigurationFormType() + { + return $this->optionsConfigurationFormType; + } + + /** + * @param string $optionsConfigurationFormType + * @return $this + */ + public function setOptionsConfigurationFormType($optionsConfigurationFormType) + { + $this->optionsConfigurationFormType = $optionsConfigurationFormType; + return $this; + } + + /** + * {@inheritdoc} + */ + public function calculatePrice(ShippingContextInterface $context, array $methodOptions, array $typeOptions) + { + return $typeOptions['price']; + } + + /** + * @inheritDoc + */ + public function createShipment(ShippingContextInterface $context, $method, $type) + { + // TODO: Implement createShipment() method. + } + + +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/test/ShippingPriceProviderTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/test/ShippingPriceProviderTest.php new file mode 100644 index 000000000..aacba5fb1 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Provider/test/ShippingPriceProviderTest.php @@ -0,0 +1,630 @@ +shippingRulesProvider = $this->getMockBuilder(MethodsConfigsRulesByContextProviderInterface::class) + ->disableOriginalConstructor()->getMock(); + + $methods = [ + 'flat_rate' => $this->getEntity(ShippingMethodStub::class, [ + 'identifier' => 'flat_rate', + 'sortOrder' => 1, + 'types' => [ + 'primary' => $this->getEntity(ShippingMethodTypeStub::class, [ + 'identifier' => 'primary', + 'sortOrder' => 1, + ]) + ] + ]), + 'integration_method' => $this->getEntity(PriceAwareShippingMethodStub::class, [ + 'identifier' => 'integration_method', + 'sortOrder' => 2, + 'isGrouped' => true, + 'types' => [ + 'ground' => $this->getEntity(ShippingMethodTypeStub::class, [ + 'identifier' => 'ground', + 'sortOrder' => 1, + ]), + 'air' => $this->getEntity(ShippingMethodTypeStub::class, [ + 'identifier' => 'air', + 'sortOrder' => 2, + ]) + ] + ]) + ]; + + $this->shippingMethodProvider = $this->createMock(ShippingMethodProviderInterface::class); + $this->shippingMethodProvider->expects($this->any()) + ->method('getShippingMethod') + ->will($this->returnCallback(function ($methodId) use ($methods) { + return array_key_exists($methodId, $methods) ? $methods[$methodId] : null; + })); + + $this->priceCache = $this->getMockBuilder(ShippingPriceCache::class) + ->disableOriginalConstructor()->getMock(); + + $viewFactory = new ShippingMethodViewFactory($this->shippingMethodProvider); + + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); + + $this->shippingPriceProvider = new ShippingPriceProvider( + $this->shippingRulesProvider, + $this->shippingMethodProvider, + $this->priceCache, + $viewFactory, + $this->eventDispatcher + ); + } + + /** + * @dataProvider getApplicableShippingMethodsConfigsRulesProvider + * + * @param array $shippingRules + * @param array $expectedData + */ + public function testGetApplicableMethodsViews(array $shippingRules, array $expectedData) + { + $shippingLineItems = [new ShippingLineItem([])]; + + $sourceEntity = new \stdClass(); + + $context = new ShippingContext([ + ShippingContext::FIELD_LINE_ITEMS => new DoctrineShippingLineItemCollection($shippingLineItems), + ShippingContext::FIELD_CURRENCY => 'USD', + ShippingContext::FIELD_SOURCE_ENTITY => $sourceEntity + ]); + + $this->shippingRulesProvider->expects($this->once()) + ->method('getShippingMethodsConfigsRules') + ->with($context) + ->willReturn($shippingRules); + + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with(ApplicableMethodsEvent::NAME); + + $this->assertEquals( + $expectedData, + $this->shippingPriceProvider + ->getApplicableMethodsViews($context) + ->toArray() + ); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function getApplicableShippingMethodsConfigsRulesProvider() + { + return [ + 'one rule' => [ + 'shippingRule' => [ + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'flat_rate', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'primary', + 'options' => [ + 'price' => Price::create(12, 'USD'), + ], + ]) + ], + ]) + ] + ]), + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'integration_method', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'ground', + 'options' => [ + 'aware_price' => null, + ], + ]) + ], + ]) + ] + ]), + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'unknown_method', + ]) + ] + ]) + ], + 'expectedData' => [ + 'flat_rate' => [ + 'identifier' => 'flat_rate', + 'label' => 'flat_rate.label', + 'sortOrder' => 1, + 'isGrouped' => false, + 'types' => [ + 'primary' => [ + 'identifier' => 'primary', + 'label' => 'primary.label', + 'sortOrder' => 1, + 'price' => Price::create(12, 'USD') + ] + ] + ] + ] + ], + 'several rules with same methods ans diff types' => [ + 'shippingRule' => [ + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'integration_method', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'ground', + 'options' => [ + 'aware_price' => Price::create(2, 'USD'), + ], + ]), + ], + ]), + ] + ]), + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'flat_rate', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'primary', + 'options' => [ + 'price' => Price::create(1, 'USD'), + ], + ]) + ], + ]) + ] + ]), + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'integration_method', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'ground', + 'options' => [ + 'aware_price' => Price::create(3, 'USD'), + ], + ]), + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'air', + 'options' => [ + 'aware_price' => Price::create(4, 'USD'), + ], + ]), + ], + ]), + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'flat_rate', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'primary', + 'options' => [ + 'price' => Price::create(5, 'USD'), + ], + ]) + ], + ]) + ] + ]), + ], + 'expectedData' => [ + 'flat_rate' => [ + 'identifier' => 'flat_rate', + 'label' => 'flat_rate.label', + 'sortOrder' => 1, + 'isGrouped' => false, + 'types' => [ + 'primary' => [ + 'identifier' => 'primary', + 'label' => 'primary.label', + 'sortOrder' => 1, + 'price' => Price::create(1, 'USD'), + ] + ] + ], + 'integration_method' => [ + 'identifier' => 'integration_method', + 'label' => 'integration_method.label', + 'sortOrder' => 2, + 'isGrouped' => true, + 'types' => [ + 'ground' => [ + 'identifier' => 'ground', + 'label' => 'ground.label', + 'sortOrder' => 1, + 'price' => Price::create(2, 'USD'), + ], + 'air' => [ + 'identifier' => 'air', + 'label' => 'air.label', + 'sortOrder' => 2, + 'price' => Price::create(4, 'USD'), + ] + ] + ] + ] + ], + ]; + } + + public function testGetApplicableMethodsViewsCache() + { + $shippingLineItems = [new ShippingLineItem([])]; + + $context = new ShippingContext([ + ShippingContext::FIELD_LINE_ITEMS => new DoctrineShippingLineItemCollection($shippingLineItems), + ShippingContext::FIELD_CURRENCY => 'USD' + ]); + + $this->shippingRulesProvider->expects(static::exactly(2)) + ->method('getShippingMethodsConfigsRules') + ->with($context) + ->willReturn([ + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'flat_rate', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'primary', + 'options' => [ + 'price' => Price::create(1, 'USD'), + ], + ]) + ], + ]) + ] + ]) + ]); + $price = Price::create(1, 'USD'); + + $expectedData = [ + 'flat_rate' => [ + 'identifier' => 'flat_rate', + 'label' => 'flat_rate.label', + 'sortOrder' => 1, + 'isGrouped' => false, + 'types' => [ + 'primary' => [ + 'identifier' => 'primary', + 'label' => 'primary.label', + 'sortOrder' => 1, + 'price' => $price + ] + ] + ] + ]; + + $this->priceCache->expects(static::at(0)) + ->method('hasPrice') + ->with($context, 'flat_rate', 'primary') + ->willReturn(false); + + $this->priceCache->expects(static::at(1)) + ->method('savePrice') + ->with($context, 'flat_rate', 'primary', Price::create(1, 'USD')) + ->willReturn(true); + + $this->priceCache->expects(static::at(2)) + ->method('hasPrice') + ->with($context, 'flat_rate', 'primary') + ->willReturn(true); + + $this->priceCache->expects(static::at(3)) + ->method('getPrice') + ->with($context, 'flat_rate', 'primary') + ->willReturn(Price::create(2, 'USD')); + + $this->assertEquals( + $expectedData, + $this->shippingPriceProvider->getApplicableMethodsViews($context)->toArray() + ); + $price->setValue(2); + $this->assertEquals( + $expectedData, + $this->shippingPriceProvider->getApplicableMethodsViews($context)->toArray() + ); + } + + /** + * @dataProvider getPriceDataProvider + * + * @param string $methodId + * @param string $typeId + * @param array $shippingRules + * @param Price|null $expectedPrice + */ + public function testGetPrice($methodId, $typeId, array $shippingRules, Price $expectedPrice = null) + { + $shippingLineItems = [new ShippingLineItem([])]; + + $context = new ShippingContext([ + ShippingContext::FIELD_LINE_ITEMS => new DoctrineShippingLineItemCollection($shippingLineItems), + ShippingContext::FIELD_CURRENCY => 'USD' + ]); + + $this->shippingRulesProvider->expects($this->once()) + ->method('getShippingMethodsConfigsRules') + ->with($context) + ->willReturn($shippingRules); + + $this->priceCache->expects($this->exactly($expectedPrice ? 1 : 0))->method('savePrice'); + + $this->assertEquals($expectedPrice, $this->shippingPriceProvider->getPrice($context, $methodId, $typeId)); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function getPriceDataProvider() + { + return [ + 'no rule' => [ + 'methodId' => 'integration_method', + 'typeId' => 'ground', + 'shippingRules' => [ + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'flat_rate', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'primary', + 'options' => [ + 'price' => Price::create(12, 'USD'), + ], + ]) + ], + ]) + ] + ]), + ], + 'expectedData' => null, + ], + 'one rule' => [ + 'methodId' => 'flat_rate', + 'typeId' => 'primary', + 'shippingRules' => [ + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'flat_rate', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'primary', + 'options' => [ + 'price' => Price::create(12, 'USD'), + ], + ]) + ], + ]) + ] + ]), + ], + 'expectedData' => Price::create(12, 'USD'), + ], + 'no price' => [ + 'methodId' => 'flat_rate', + 'typeId' => 'primary', + 'shippingRules' => [ + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'flat_rate', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'primary', + 'options' => [ + 'price' => null + ], + ]) + ], + ]) + ] + ]), + ], + 'expectedData' => null, + ], + 'several rules with same methods ans types' => [ + 'methodId' => 'integration_method', + 'typeId' => 'ground', + 'shippingRules' => [ + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'integration_method', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'ground', + 'options' => [ + 'price' => Price::create(1, 'USD'), + ], + ]) + ], + ]), + ] + ]), + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'integration_method', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'ground', + 'options' => [ + 'price' => Price::create(2, 'USD'), + ], + ]) + ], + ]), + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => 'integration_method', + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => 'air', + 'options' => [ + 'price' => Price::create(3, 'USD'), + ], + ]) + ], + ]) + ] + ]) + ], + 'expectedData' => Price::create(1, 'USD'), + ], + ]; + } + + public function testGetPriceCache() + { + $methodId = 'flat_rate'; + $typeId = 'primary'; + + $shippingLineItems = [new ShippingLineItem([])]; + + $context = new ShippingContext([ + ShippingContext::FIELD_LINE_ITEMS => new DoctrineShippingLineItemCollection($shippingLineItems), + ShippingContext::FIELD_CURRENCY => 'USD' + ]); + + $this->shippingRulesProvider->expects($this->exactly(2)) + ->method('getShippingMethodsConfigsRules') + ->with($context) + ->willReturn([ + $this->getEntity(ShippingMethodsConfigsRule::class, [ + 'methodConfigs' => [ + $this->getEntity(ShippingMethodConfig::class, [ + 'method' => $methodId, + 'typeConfigs' => [ + $this->getEntity(ShippingMethodTypeConfig::class, [ + 'enabled' => true, + 'type' => $typeId, + 'options' => [ + 'price' => Price::create(1, 'USD'), + ], + ]) + ], + ]) + ] + ]) + ]); + + $expectedPrice = Price::create(1, 'USD'); + + $this->priceCache->expects(static::at(0)) + ->method('hasPrice') + ->with($context, 'flat_rate', 'primary') + ->willReturn(false); + + $this->priceCache->expects(static::at(1)) + ->method('savePrice') + ->with($context, 'flat_rate', 'primary', Price::create(1, 'USD')) + ->willReturn(true); + + $this->priceCache->expects(static::at(2)) + ->method('hasPrice') + ->with($context, 'flat_rate', 'primary') + ->willReturn(true); + + $this->priceCache->expects(static::at(3)) + ->method('getPrice') + ->with($context, 'flat_rate', 'primary') + ->willReturn(Price::create(2, 'USD')); + + $this->assertEquals($expectedPrice, $this->shippingPriceProvider->getPrice($context, $methodId, $typeId)); + $expectedPrice->setValue(2); + $this->assertEquals($expectedPrice, $this->shippingPriceProvider->getPrice($context, $methodId, $typeId)); + } + + public function testGetPriceNoMethodAndType() + { + $shippingLineItems = [new ShippingLineItem([])]; + + $context = new ShippingContext([ + ShippingContext::FIELD_LINE_ITEMS => new DoctrineShippingLineItemCollection($shippingLineItems), + ShippingContext::FIELD_CURRENCY => 'USD' + ]); + + $this->assertNull($this->shippingPriceProvider->getPrice($context, 'unknown_method', 'primary')); + $this->assertNull($this->shippingPriceProvider->getPrice($context, 'flat_rate', 'unknown_method')); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/RuleFiltration/Basic/BasicMethodsConfigsRulesFiltrationServiceTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/RuleFiltration/Basic/BasicMethodsConfigsRulesFiltrationServiceTest.php new file mode 100644 index 000000000..bf5a47985 --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/RuleFiltration/Basic/BasicMethodsConfigsRulesFiltrationServiceTest.php @@ -0,0 +1,96 @@ +filtrationService = $this->createMock(RuleFiltrationServiceInterface::class); + $this->shippingContextToRuleValuesConverter = $this + ->createMock(ShippingContextToRulesValuesConverterInterface::class); + + $this->basicMethodsConfigsRulesFiltrationService = new BasicMethodsConfigsRulesFiltrationService( + $this->filtrationService, + $this->shippingContextToRuleValuesConverter + ); + } + + /** + * {@inheritDoc} + */ + public function testGetFilteredShippingMethodsConfigsRules() + { + $configRules = [ + $this->createShippingMethodsConfigsRule(), + $this->createShippingMethodsConfigsRule(), + ]; + $context = $this->createContextMock(); + $values = [ + 'currency' => 'USD', + ]; + + $this->shippingContextToRuleValuesConverter->expects(static::once()) + ->method('convert') + ->with($context) + ->willReturn($values); + + $expectedConfigRules = [ + $this->createShippingMethodsConfigsRule(), + $this->createShippingMethodsConfigsRule(), + ]; + + $this->filtrationService->expects(static::once()) + ->method('getFilteredRuleOwners') + ->with($configRules, $values) + ->willReturn($expectedConfigRules); + + static::assertEquals( + $expectedConfigRules, + $this->basicMethodsConfigsRulesFiltrationService->getFilteredShippingMethodsConfigsRules( + $configRules, + $context + ) + ); + } + + /** + * @return ShippingContextInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private function createContextMock() + { + return $this->createMock(ShippingContextInterface::class); + } + + /** + * @return ShippingMethodsConfigsRule|\PHPUnit\Framework\MockObject\MockObject + */ + private function createShippingMethodsConfigsRule() + { + return $this->createMock(ShippingMethodsConfigsRule::class); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Tests/Unit/Tools/FilteredDatagridRouteHelperTest.php b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Tools/FilteredDatagridRouteHelperTest.php new file mode 100644 index 000000000..4b5f78ced --- /dev/null +++ b/src/Marello/Bundle/ShippingBundle/Tests/Unit/Tools/FilteredDatagridRouteHelperTest.php @@ -0,0 +1,67 @@ +datagridRouteHelper = $this->createMock(DatagridRouteHelper::class); + + $this->gridRouteName = 'route_name'; + $this->gridName = 'grid_name'; + + $this->helper = new FilteredDatagridRouteHelper( + $this->gridRouteName, + $this->gridName, + $this->datagridRouteHelper + ); + } + + /** + * {@inheritDoc} + */ + protected function tearDown() + { + unset($this->datagridRouteHelper, $this->helper); + } + + public function testGenerate() + { + $this->datagridRouteHelper->expects($this->once())->method('generate')->with( + $this->gridRouteName, + $this->gridName, + ['f' => ['filterName' => ['value' => ['' => '10']]]], + RouterInterface::ABSOLUTE_PATH + )->willReturn('generatedURL'); + + $this->assertEquals('generatedURL', $this->helper->generate(['filterName' => 10])); + } +} diff --git a/src/Marello/Bundle/ShippingBundle/Translator/ShippingMethodLabelTranslator.php b/src/Marello/Bundle/ShippingBundle/Translator/ShippingMethodLabelTranslator.php index c1f219755..eefd453f0 100755 --- a/src/Marello/Bundle/ShippingBundle/Translator/ShippingMethodLabelTranslator.php +++ b/src/Marello/Bundle/ShippingBundle/Translator/ShippingMethodLabelTranslator.php @@ -3,7 +3,7 @@ namespace Marello\Bundle\ShippingBundle\Translator; use Marello\Bundle\ShippingBundle\Formatter\ShippingMethodLabelFormatter; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; class ShippingMethodLabelTranslator { diff --git a/src/Marello/Bundle/ShippingBundle/Twig/ShippingMethodExtension.php b/src/Marello/Bundle/ShippingBundle/Twig/ShippingMethodExtension.php index 388c649bf..e37601cb3 100755 --- a/src/Marello/Bundle/ShippingBundle/Twig/ShippingMethodExtension.php +++ b/src/Marello/Bundle/ShippingBundle/Twig/ShippingMethodExtension.php @@ -6,8 +6,10 @@ use Marello\Bundle\ShippingBundle\Event\ShippingMethodConfigDataEvent; use Marello\Bundle\ShippingBundle\Formatter\ShippingMethodLabelFormatter; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; -class ShippingMethodExtension extends \Twig_Extension +class ShippingMethodExtension extends AbstractExtension { const SHIPPING_METHOD_EXTENSION_NAME = 'marello_shipping_method'; const DEFAULT_METHOD_CONFIG_TEMPLATE @@ -92,23 +94,23 @@ public function isShippingMethodEnabled($methodIdentifier) public function getFunctions() { return [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_get_shipping_method_label', [$this->shippingMethodLabelFormatter, 'formatShippingMethodLabel'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_get_shipping_method_type_label', [$this->shippingMethodLabelFormatter, 'formatShippingMethodTypeLabel'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_shipping_method_with_type_label', [$this->shippingMethodLabelFormatter, 'formatShippingMethodWithTypeLabel'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_shipping_method_config_template', [$this, 'getShippingMethodConfigRenderData'] ), - new \Twig_SimpleFunction( + new TwigFunction( 'marello_shipping_method_enabled', [$this, 'isShippingMethodEnabled'] ) diff --git a/src/Marello/Bundle/SupplierBundle/Controller/SupplierController.php b/src/Marello/Bundle/SupplierBundle/Controller/SupplierController.php index e6f8dc310..414a8fb41 100644 --- a/src/Marello/Bundle/SupplierBundle/Controller/SupplierController.php +++ b/src/Marello/Bundle/SupplierBundle/Controller/SupplierController.php @@ -3,22 +3,26 @@ namespace Marello\Bundle\SupplierBundle\Controller; use Marello\Bundle\AddressBundle\Form\Type\AddressType; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Marello\Bundle\SupplierBundle\Entity\Supplier; use Marello\Bundle\AddressBundle\Entity\MarelloAddress; +use Symfony\Component\Routing\Annotation\Route; -class SupplierController extends Controller +class SupplierController extends AbstractController { /** - * @Config\Route("/", name="marello_supplier_supplier_index") - * @Config\Template + * @Route( + * path="/", + * name="marello_supplier_supplier_index" + * ) + * @Template * @AclAncestor("marello_supplier_view") */ public function indexAction() @@ -27,8 +31,12 @@ public function indexAction() } /** - * @Config\Route("/view/{id}", requirements={"id"="\d+"}, name="marello_supplier_supplier_view") - * @Config\Template + * @Route( + * path="/view/{id}", + * requirements={"id"="\d+"}, + * name="marello_supplier_supplier_view" + * ) + * @Template * @AclAncestor("marello_supplier_view") * * @param Supplier $supplier @@ -41,32 +49,36 @@ public function viewAction(Supplier $supplier) } /** - * @Config\Route("/create", name="marello_supplier_supplier_create") - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Route( + * path="/create", + * methods={"GET", "POST"}, + * name="marello_supplier_supplier_create" + * ) + * @Template * @AclAncestor("marello_supplier_create") * - * @param Request $request - * * @return array */ - public function createAction(Request $request) + public function createAction() { return $this->update(new Supplier()); } /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_supplier_supplier_update") - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Route( + * path="/update/{id}", + * methods={"GET", "POST"}, + * requirements={"id"="\d+"}, + * name="marello_supplier_supplier_update" + * ) + * @Template * @AclAncestor("marello_supplier_update") * - * @param Request $request - * @param Supplier $supplier + * @param Supplier $supplier * * @return array */ - public function updateAction(Request $request, Supplier $supplier) + public function updateAction(Supplier $supplier) { return $this->update($supplier); } @@ -112,21 +124,20 @@ protected function update(Supplier $supplier = null) } /** - * @Config\Route( - * "/widget/address/{id}/{typeId}", + * @Route( + * path="/widget/address/{id}/{typeId}", + * methods={"GET", "POST"}, * requirements={"id"="\d+","typeId"="\d+"}, * name="marello_supplier_supplier_address" * ) - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Template("MarelloSupplierBundle:Supplier/widget:address.html.twig") * @AclAncestor("marello_supplier_update") * - * @param Request $request * @param MarelloAddress $address * * @return array */ - public function addressAction(Request $request, MarelloAddress $address) + public function addressAction(MarelloAddress $address) { return [ 'supplierAddress' => $address @@ -134,9 +145,13 @@ public function addressAction(Request $request, MarelloAddress $address) } /** - * @Config\Route("/update/address/{id}", requirements={"id"="\d+"}, name="marello_supplier_supplier_updateaddress") - * @Config\Method({"GET", "POST"}) - * @Config\Template("MarelloSupplierBundle:Supplier:widget/updateAddress.html.twig") + * @Route( + * path="/update/address/{id}", + * methods={"GET", "POST"}, + * requirements={"id"="\d+"}, + * name="marello_supplier_supplier_updateaddress" + * ) + * @Template("MarelloSupplierBundle:Supplier:widget/updateAddress.html.twig") * @AclAncestor("marello_supplier_update") * * @param Request $request @@ -163,8 +178,11 @@ public function updateAddressAction(Request $request, MarelloAddress $address) } /** - * @Config\Route("/get-supplier-default-data", name="marello_supplier_supplier_get_default_data") - * @Config\Method({"GET"}) + * @Route( + * path="/get-supplier-default-data", + * methods={"GET"}, + * name="marello_supplier_supplier_get_default_data" + * ) * @AclAncestor("marello_supplier_view") * * {@inheritdoc} diff --git a/src/Marello/Bundle/SupplierBundle/EventListener/Doctrine/SupplierDropshipEventListener.php b/src/Marello/Bundle/SupplierBundle/EventListener/Doctrine/SupplierDropshipEventListener.php index 67aab76cf..6b5d124e6 100644 --- a/src/Marello/Bundle/SupplierBundle/EventListener/Doctrine/SupplierDropshipEventListener.php +++ b/src/Marello/Bundle/SupplierBundle/EventListener/Doctrine/SupplierDropshipEventListener.php @@ -62,4 +62,18 @@ public function preUpdate(PreUpdateEventArgs $args) } } } + + /** + * @param LifecycleEventArgs $args + */ + public function preRemove(LifecycleEventArgs $args) + { + $entity = $args->getEntity(); + if ($entity instanceof Supplier && $entity->getCanDropship() === true) { + $this->eventDispatcher->dispatch( + SupplierDropshipEvent::NAME, + new SupplierDropshipEvent($entity, false) + ); + } + } } diff --git a/src/Marello/Bundle/SupplierBundle/Migrations/Data/ORM/UpdateCurrentSupplierWithCurrency.php b/src/Marello/Bundle/SupplierBundle/Migrations/Data/ORM/UpdateCurrentSupplierWithCurrency.php index 186519522..068ab926a 100644 --- a/src/Marello/Bundle/SupplierBundle/Migrations/Data/ORM/UpdateCurrentSupplierWithCurrency.php +++ b/src/Marello/Bundle/SupplierBundle/Migrations/Data/ORM/UpdateCurrentSupplierWithCurrency.php @@ -4,10 +4,12 @@ use Doctrine\Common\Persistence\ObjectManager; use Marello\Bundle\SupplierBundle\Entity\Supplier; -use Symfony\Bridge\Doctrine\Tests\Fixtures\ContainerAwareFixture; +use Symfony\Component\DependencyInjection\ContainerAwareTrait; -class UpdateCurrentSupplierWithCurrency extends ContainerAwareFixture +class UpdateCurrentSupplierWithCurrency { + use ContainerAwareTrait; + /** * @var ObjectManager */ diff --git a/src/Marello/Bundle/SupplierBundle/Provider/SupplierProvider.php b/src/Marello/Bundle/SupplierBundle/Provider/SupplierProvider.php index 4cbf29230..220d6cdbe 100644 --- a/src/Marello/Bundle/SupplierBundle/Provider/SupplierProvider.php +++ b/src/Marello/Bundle/SupplierBundle/Provider/SupplierProvider.php @@ -7,19 +7,30 @@ use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\ProductBundle\Entity\ProductSupplierRelation; use Marello\Bundle\SupplierBundle\Entity\Supplier; +use Oro\Bundle\CurrencyBundle\Model\LocaleSettings; +use Oro\Bundle\LocaleBundle\Twig\NumberExtension; class SupplierProvider { - /** @var ObjectManager $manager */ + /** + * @var ObjectManager + */ protected $manager; + /** + * @var LocaleSettings + */ + protected $localeSettings; + /** * SupplierProvider constructor. * @param ObjectManager $manager + * @param LocaleSettings $localeSettings */ - public function __construct(ObjectManager $manager) + public function __construct(ObjectManager $manager, LocaleSettings $localeSettings) { $this->manager = $manager; + $this->localeSettings = $localeSettings; } /** @@ -55,7 +66,8 @@ public function getSupplierDefaultDataById($supplierId) return [ 'name' => $supplier->getName(), 'priority' => $supplier->getPriority(), - 'canDropship' => $supplier->getCanDropship() + 'canDropship' => $supplier->getCanDropship(), + 'currency' => $this->localeSettings->getCurrencySymbolByCurrency($supplier->getCurrency()) ]; } } diff --git a/src/Marello/Bundle/SupplierBundle/Resources/config/form.yml b/src/Marello/Bundle/SupplierBundle/Resources/config/form.yml index b8d6cf8ef..4e65560fe 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/config/form.yml +++ b/src/Marello/Bundle/SupplierBundle/Resources/config/form.yml @@ -19,7 +19,7 @@ services: ## handlers marello_supplier.form.handler.supplier: class: Marello\Bundle\SupplierBundle\Form\Handler\SupplierHandler - scope: request + public: true arguments: - '@marello_supplier.supplier.form' - '@request_stack' diff --git a/src/Marello/Bundle/SupplierBundle/Resources/config/jsmodules.yml b/src/Marello/Bundle/SupplierBundle/Resources/config/jsmodules.yml new file mode 100644 index 000000000..2819e8ff8 --- /dev/null +++ b/src/Marello/Bundle/SupplierBundle/Resources/config/jsmodules.yml @@ -0,0 +1,4 @@ +dynamic-imports: + marellosupplier: + - marellosupplier/js/app/views/product-supplier-view + - marellosupplier/js/app/views/product-suppliers-view diff --git a/src/Marello/Bundle/SupplierBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/SupplierBundle/Resources/config/oro/datagrids.yml index e6e35e317..c5f979a2e 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/SupplierBundle/Resources/config/oro/datagrids.yml @@ -39,7 +39,7 @@ datagrids: currency: data_name: s.currency default: - priority: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC" + priority: "ASC" filters: columns: name: @@ -105,7 +105,7 @@ datagrids: query: select: - p.id - - p.name + - p.denormalizedDefaultName - p.sku - psr.quantityOfUnit - psr.cost @@ -126,8 +126,8 @@ datagrids: label: marello.product.sku.label frontend_type: string name: - data_name: name - label: marello.product.name.label + data_name: denormalizedDefaultName + label: marello.product.names.label frontend_type: string quantityOfUnit: data_name: quantityOfUnit @@ -142,13 +142,13 @@ datagrids: sku: data_name: p.sku name: - data_name: p.name + data_name: p.denormalizedDefaultName quantityOfUnit: data_name: psr.quantityOfUnit cost: data_name: psr.cost default: - sku: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC" + sku: "ASC" filters: columns: sku: @@ -156,7 +156,7 @@ datagrids: data_name: p.sku name: type: string - data_name: p.name + data_name: p.denormalizedDefaultName quantityOfUnit: type: string data_name: psr.quantityOfUnit diff --git a/src/Marello/Bundle/SupplierBundle/Resources/config/oro/twig.yml b/src/Marello/Bundle/SupplierBundle/Resources/config/oro/twig.yml new file mode 100644 index 000000000..5d341df99 --- /dev/null +++ b/src/Marello/Bundle/SupplierBundle/Resources/config/oro/twig.yml @@ -0,0 +1,2 @@ +bundles: + - MarelloSupplierBundle:Form:fields.html.twig diff --git a/src/Marello/Bundle/SupplierBundle/Resources/config/services.yml b/src/Marello/Bundle/SupplierBundle/Resources/config/services.yml index 409a43984..d0d993d2e 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/SupplierBundle/Resources/config/services.yml @@ -1,20 +1,19 @@ -parameters: - marello_supplier.supplier.entity.class: Marello\Bundle\SupplierBundle\Entity\Supplier - services: # Autocomplete search handler marello_supplier.supplier.form.autocomplete.search_handler: parent: oro_form.autocomplete.search_handler arguments: - - '%marello_supplier.supplier.entity.class%' - - ["name"] + - 'Marello\Bundle\SupplierBundle\Entity\Supplier' + - ['name'] tags: - { name: oro_form.autocomplete.search_handler, alias: suppliers, acl_resource: marello_product_view } marello_supplier.provider.supplier: class: Marello\Bundle\SupplierBundle\Provider\SupplierProvider + public: true arguments: - '@doctrine.orm.entity_manager' + - '@oro_currency.locale_settings' marello_supplier.twig.supplier_extension: class: Marello\Bundle\SupplierBundle\Twig\SupplierExtension @@ -34,4 +33,5 @@ services: - '@event_dispatcher' tags: - { name: doctrine.event_listener, event: prePersist } - - { name: doctrine.event_listener, event: preUpdate } \ No newline at end of file + - { name: doctrine.event_listener, event: preUpdate } + - { name: doctrine.event_listener, event: preRemove } \ No newline at end of file diff --git a/src/Marello/Bundle/SupplierBundle/Resources/public/js/app/views/product-supplier-view.js b/src/Marello/Bundle/SupplierBundle/Resources/public/js/app/views/product-supplier-view.js index ca5884085..b9db258b4 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/public/js/app/views/product-supplier-view.js +++ b/src/Marello/Bundle/SupplierBundle/Resources/public/js/app/views/product-supplier-view.js @@ -1,7 +1,7 @@ define(function(require) { 'use strict'; - var ProductSupplierView, + const $ = require('jquery'), _ = require('underscore'), mediator = require('oroui/js/mediator'), @@ -12,7 +12,7 @@ define(function(require) { * @extends marellolayout.app.views.AbstractItemView * @class marellosupplier.app.views.ProductSupplierView */ - ProductSupplierView = AbstractItemView.extend({ + const ProductSupplierView = AbstractItemView.extend({ options: { priority: 0, canDropship: false @@ -78,11 +78,21 @@ define(function(require) { this.options.canDropship = data.canDropship; } + if (data.currency.length !== 0) { + this.options.currency = ' ' + data.currency; + } + this.fieldsByName.priority .val(this.options.priority); this.fieldsByName.canDropship .prop('checked', this.options.canDropship); + + var parent = $(this.fieldsByName.cost).parent(); + parent.contents().filter(function(){ + return (this.nodeType == 3); + }).remove(); + parent.append(this.options.currency); }, /** diff --git a/src/Marello/Bundle/SupplierBundle/Resources/public/js/app/views/product-suppliers-view.js b/src/Marello/Bundle/SupplierBundle/Resources/public/js/app/views/product-suppliers-view.js index dfd306f62..f88ac7258 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/public/js/app/views/product-suppliers-view.js +++ b/src/Marello/Bundle/SupplierBundle/Resources/public/js/app/views/product-suppliers-view.js @@ -1,7 +1,7 @@ define(function(require) { 'use strict'; - var ProductSuppliersView, + const $ = require('jquery'), _ = require('underscore'), routing = require('routing'), @@ -13,13 +13,13 @@ define(function(require) { * @extends marellolayout.app.views.AbstractItemsView * @class marellosupplier.app.views.ProductSuppliersView */ - ProductSuppliersView = AbstractItemsView.extend({ + const ProductSuppliersView = AbstractItemsView.extend({ /** * @property {Object} */ options: { suppliers: {}, - supplierDataRoute: 'marello_supplier_supplier_get_default_data', + supplierDataRoute: 'marello_supplier_supplier_get_default_data' }, /** diff --git a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/create.html.twig b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/create.html.twig index ba161a25c..9852690f7 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/create.html.twig +++ b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/create.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} {% set formAction = path('marello_supplier_supplier_update', { 'id': form.vars.value.id }) %} diff --git a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/update.html.twig b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/update.html.twig index 7b8dde849..1921f34f3 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/update.html.twig +++ b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} {% set formAction = path('marello_supplier_supplier_update', { 'id': form.vars.value.id }) %} diff --git a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/view.html.twig b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/view.html.twig index c5103a646..ef4544177 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/view.html.twig +++ b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/view.html.twig @@ -16,7 +16,7 @@ {% block navButtons %} {{ parent() }} - {% if resource_granted('EDIT', entity) %} + {% if is_granted('EDIT', entity) %} {{ UI.buttonSeparator() }} {{ UI.editButton({ 'path': path('marello_supplier_supplier_update', {'id': entity.id}), @@ -43,7 +43,7 @@ {{ UI.renderProperty('marello.supplier.supplier_is_active.label'|trans, entity.isActive ? 'marello.supplier.supplier_is_active.value.yes'|trans : 'marello.supplier.supplier_is_active.value.no'|trans) }} {{ UI.renderProperty('marello.supplier.currency.label'|trans, entity.currency) }} {{ UI.renderProperty('marello.supplier.po_send_by.label'|trans, entity.poSendBy) }} - {% set supplierViewGranted = resource_granted('marello_supplier_update') %} + {% set supplierViewGranted = is_granted('marello_supplier_update') %} {%- if supplierViewGranted -%} {%- set supplierData -%} diff --git a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/widget/address.html.twig b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/widget/address.html.twig index bafcacbd7..7b0f1a522 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/widget/address.html.twig +++ b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/widget/address.html.twig @@ -1,35 +1,29 @@ {% import 'OroUIBundle::macros.html.twig' as UI %} {% import 'MarelloAddressBundle::macros.html.twig' as address %} {% set label = 'marello.supplier.supplier_address.label' %} -
+
- {% if marello_supplier_supplier_address is not defined or resource_granted(marello_supplier_supplier_address) %} + {% if marello_supplier_supplier_address is not defined or is_granted(marello_supplier_supplier_address) %}
{% endif %} -
{{ UI.renderHtmlProperty(label|trans, address.renderAddress(supplierAddress)) }} {{ UI.renderProperty('marello.order.address.phone.label'|trans, supplierAddress.phone) }}
-
\ No newline at end of file diff --git a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/widget/updateAddress.html.twig b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/widget/updateAddress.html.twig index 2d31c2695..ecb08078a 100644 --- a/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/widget/updateAddress.html.twig +++ b/src/Marello/Bundle/SupplierBundle/Resources/views/Supplier/widget/updateAddress.html.twig @@ -1,23 +1,25 @@ {% form_theme form with ['OroFormBundle:Form:fields.html.twig', _self] %} -
- {% if saved %} - - {% else %} +{% if saved %} + {% set widgetResponse = { + widget: { + trigger: [{ + eventBroker: 'widget', + name: 'formSave', + args: [form.vars.value.id], + }], + } + } %} + + {{ widgetResponse|json_encode|raw }} +{% else %} +
{% if not form.vars.valid and form_errors(form) %} -
-
- {{ form_errors(form) }} +
+
+ {{ form_errors(form) }} +
-
{% endif %} -
@@ -30,5 +32,5 @@
{{ oro_form_js_validation(form) }} - {% endif %} -
+
+{% endif %} diff --git a/src/Marello/Bundle/SupplierBundle/Tests/Functional/Controller/SupplierControllerTest.php b/src/Marello/Bundle/SupplierBundle/Tests/Functional/Controller/SupplierControllerTest.php index f2cb45238..17b2556bf 100644 --- a/src/Marello/Bundle/SupplierBundle/Tests/Functional/Controller/SupplierControllerTest.php +++ b/src/Marello/Bundle/SupplierBundle/Tests/Functional/Controller/SupplierControllerTest.php @@ -113,14 +113,14 @@ public function testLinkedProductToSupplier() 'marello-supplier-products-grid', [ 'marello-supplier-products-grid[supplierId]' => $supplier->getId(), - 'marello-supplier-products-grid[_filter][name][value]' => $product->getName() + 'marello-supplier-products-grid[_filter][name][value]' => $product->getDenormalizedDefaultName() ] ); $result = $this->getJsonResponseContent($response, Response::HTTP_OK); $result = reset($result['data']); - $this->assertContains($product->getName(), $result['name']); + $this->assertContains($product->getDenormalizedDefaultName(), $result['name']); $this->assertContains($product->getSku(), $result['sku']); } diff --git a/src/Marello/Bundle/SupplierBundle/Twig/SupplierExtension.php b/src/Marello/Bundle/SupplierBundle/Twig/SupplierExtension.php index 1270e4367..fd355be60 100644 --- a/src/Marello/Bundle/SupplierBundle/Twig/SupplierExtension.php +++ b/src/Marello/Bundle/SupplierBundle/Twig/SupplierExtension.php @@ -4,8 +4,10 @@ use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\SupplierBundle\Provider\SupplierProvider; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; -class SupplierExtension extends \Twig_Extension +class SupplierExtension extends AbstractExtension { const NAME = 'marello_supplier'; @@ -40,7 +42,7 @@ public function getName() public function getFunctions() { return [ - new \Twig_SimpleFunction( + new TwigFunction( 'marello_supplier_get_supplier_ids', [$this, 'getSuppliersIds'] ), diff --git a/src/Marello/Bundle/TaxBundle/Controller/TaxCodeController.php b/src/Marello/Bundle/TaxBundle/Controller/TaxCodeController.php index b0150b526..9fbb7543b 100644 --- a/src/Marello/Bundle/TaxBundle/Controller/TaxCodeController.php +++ b/src/Marello/Bundle/TaxBundle/Controller/TaxCodeController.php @@ -5,18 +5,22 @@ use Marello\Bundle\TaxBundle\Entity\TaxCode; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Oro\Bundle\SecurityBundle\Annotation\Acl; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Routing\Annotation\Route; /** * Class TaxCodeController * @package Marello\Bundle\TaxBundle\Controller */ -class TaxCodeController extends Controller +class TaxCodeController extends AbstractController { /** - * @Config\Route("/", name="marello_tax_taxcode_index") - * @Config\Template + * @Route( + * path="/", + * name="marello_tax_taxcode_index" + * ) + * @Template * @AclAncestor("marello_tax_taxcode_view") */ public function indexAction() @@ -25,8 +29,12 @@ public function indexAction() } /** - * @Config\Route("/view/{id}", requirements={"id"="\d+"}, name="marello_tax_taxcode_view") - * @Config\Template + * @Route( + * path="/view/{id}", + * requirements={"id"="\d+"}, + * name="marello_tax_taxcode_view" + * ) + * @Template * @Acl( * id="marello_tax_taxcode_view", * type="entity", @@ -44,9 +52,12 @@ public function viewAction(TaxCode $taxCode) } /** - * @Config\Route("/create", name="marello_tax_taxcode_create") - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Route( + * path="/create", + * methods={"GET", "POST"}, + * name="marello_tax_taxcode_create" + * ) + * @Template * @Acl( * id="marello_tax_taxcode_create", * type="entity", @@ -62,9 +73,13 @@ public function createAction() } /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_tax_taxcode_update") - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Route( + * path="/update/{id}", + * methods={"GET", "POST"}, + * requirements={"id"="\d+"}, + * name="marello_tax_taxcode_update" + * ) + * @Template * @Acl( * id="marello_tax_taxcode_update", * type="entity", diff --git a/src/Marello/Bundle/TaxBundle/Controller/TaxJurisdictionController.php b/src/Marello/Bundle/TaxBundle/Controller/TaxJurisdictionController.php index 08a30b3a8..f936434df 100755 --- a/src/Marello/Bundle/TaxBundle/Controller/TaxJurisdictionController.php +++ b/src/Marello/Bundle/TaxBundle/Controller/TaxJurisdictionController.php @@ -6,20 +6,24 @@ use Marello\Bundle\TaxBundle\Form\Type\TaxJurisdictionType; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Oro\Bundle\SecurityBundle\Annotation\Acl; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; /** * Class TaxJurisdictionController * @package Marello\Bundle\TaxBundle\Controller */ -class TaxJurisdictionController extends Controller +class TaxJurisdictionController extends AbstractController { /** - * @Config\Route("/", name="marello_tax_taxjurisdiction_index") - * @Config\Template + * @Route( + * path="/", + * name="marello_tax_taxjurisdiction_index" + * ) + * @Template * @AclAncestor("marello_tax_taxjurisdiction_view") * * @return array @@ -32,8 +36,12 @@ public function indexAction() } /** - * @Config\Route("/view/{id}", name="marello_tax_taxjurisdiction_view", requirements={"id"="\d+"}) - * @Config\Template + * @Route( + * path="/view/{id}", + * name="marello_tax_taxjurisdiction_view", + * requirements={"id"="\d+"} + * ) + * @Template * @Acl( * id="marello_tax_taxjurisdiction_view", * type="entity", @@ -52,8 +60,11 @@ public function viewAction(TaxJurisdiction $taxJurisdiction) } /** - * @Config\Route("/create", name="marello_tax_taxjurisdiction_create") - * @Config\Template("MarelloTaxBundle:TaxJurisdiction:update.html.twig") + * @Route( + * path="/create", + * name="marello_tax_taxjurisdiction_create" + * ) + * @Template("MarelloTaxBundle:TaxJurisdiction:update.html.twig") * @Acl( * id="marello_tax_taxjurisdiction_create", * type="entity", @@ -70,8 +81,12 @@ public function createAction(Request $request) } /** - * @Config\Route("/update/{id}", name="marello_tax_taxjurisdiction_update", requirements={"id"="\d+"}) - * @Config\Template + * @Route( + * path="/update/{id}", + * name="marello_tax_taxjurisdiction_update", + * requirements={"id"="\d+"} + * ) + * @Template * @Acl( * id="marello_tax_taxjurisdiction_update", * type="entity", diff --git a/src/Marello/Bundle/TaxBundle/Controller/TaxRateController.php b/src/Marello/Bundle/TaxBundle/Controller/TaxRateController.php index 8c442fd6d..c474ca233 100644 --- a/src/Marello/Bundle/TaxBundle/Controller/TaxRateController.php +++ b/src/Marello/Bundle/TaxBundle/Controller/TaxRateController.php @@ -5,18 +5,22 @@ use Marello\Bundle\TaxBundle\Entity\TaxRate; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; use Oro\Bundle\SecurityBundle\Annotation\Acl; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Routing\Annotation\Route; /** * Class TaxRateController * @package Marello\Bundle\TaxBundle\Controller */ -class TaxRateController extends Controller +class TaxRateController extends AbstractController { /** - * @Config\Route("/", name="marello_tax_taxrate_index") - * @Config\Template + * @Route( + * path="/", + * name="marello_tax_taxrate_index" + * ) + * @Template * @AclAncestor("marello_tax_taxrate_view") */ public function indexAction() @@ -25,8 +29,12 @@ public function indexAction() } /** - * @Config\Route("/view/{id}", requirements={"id"="\d+"}, name="marello_tax_taxrate_view") - * @Config\Template + * @Route( + * path="/view/{id}", + * requirements={"id"="\d+"}, + * name="marello_tax_taxrate_view" + * ) + * @Template * @Acl( * id="marello_tax_taxrate_view", * type="entity", @@ -44,9 +52,12 @@ public function viewAction(TaxRate $taxRate) } /** - * @Config\Route("/create", name="marello_tax_taxrate_create") - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Route( + * path="/create", + * methods={"GET", "POST"}, + * name="marello_tax_taxrate_create" + * ) + * @Template * @Acl( * id="marello_tax_taxrate_create", * type="entity", @@ -62,9 +73,13 @@ public function createAction() } /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_tax_taxrate_update") - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Route( + * path="/update/{id}", + * methods={"GET", "POST"}, + * requirements={"id"="\d+"}, + * name="marello_tax_taxrate_update" + * ) + * @Template * @Acl( * id="marello_tax_taxrate_update", * type="entity", diff --git a/src/Marello/Bundle/TaxBundle/Controller/TaxRuleController.php b/src/Marello/Bundle/TaxBundle/Controller/TaxRuleController.php index b4ca93b52..b6e764edf 100644 --- a/src/Marello/Bundle/TaxBundle/Controller/TaxRuleController.php +++ b/src/Marello/Bundle/TaxBundle/Controller/TaxRuleController.php @@ -5,18 +5,22 @@ use Marello\Bundle\TaxBundle\Entity\TaxRule; use Oro\Bundle\SecurityBundle\Annotation\Acl; use Oro\Bundle\SecurityBundle\Annotation\AclAncestor; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Config; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Routing\Annotation\Route; /** * Class TaxRuleController * @package Marello\Bundle\TaxBundle\Controller */ -class TaxRuleController extends Controller +class TaxRuleController extends AbstractController { /** - * @Config\Route("/", name="marello_tax_taxrule_index") - * @Config\Template + * @Route( + * path="/", + * name="marello_tax_taxrule_index" + * ) + * @Template * @AclAncestor("marello_tax_taxrule_view") */ public function indexAction() @@ -25,8 +29,12 @@ public function indexAction() } /** - * @Config\Route("/view/{id}", requirements={"id"="\d+"}, name="marello_tax_taxrule_view") - * @Config\Template + * @Route( + * path="/view/{id}", + * requirements={"id"="\d+"}, + * name="marello_tax_taxrule_view" + * ) + * @Template * @Acl( * id="marello_tax_taxrule_view", * type="entity", @@ -44,9 +52,12 @@ public function viewAction(TaxRule $taxRule) } /** - * @Config\Route("/create", name="marello_tax_taxrule_create") - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Route( + * path="/create", + * methods={"GET", "POST"}, + * name="marello_tax_taxrule_create" + * ) + * @Template * @Acl( * id="marello_tax_taxrule_create", * type="entity", @@ -62,9 +73,13 @@ public function createAction() } /** - * @Config\Route("/update/{id}", requirements={"id"="\d+"}, name="marello_tax_taxrule_update") - * @Config\Method({"GET", "POST"}) - * @Config\Template + * @Route( + * path="/update/{id}", + * methods={"GET", "POST"}, + * requirements={"id"="\d+"}, + * name="marello_tax_taxrule_update" + * ) + * @Template * @Acl( * id="marello_tax_taxrule_update", * type="entity", diff --git a/src/Marello/Bundle/TaxBundle/DependencyInjection/MarelloTaxExtension.php b/src/Marello/Bundle/TaxBundle/DependencyInjection/MarelloTaxExtension.php index 02467064f..09651c03c 100644 --- a/src/Marello/Bundle/TaxBundle/DependencyInjection/MarelloTaxExtension.php +++ b/src/Marello/Bundle/TaxBundle/DependencyInjection/MarelloTaxExtension.php @@ -15,7 +15,7 @@ class MarelloTaxExtension extends Extension public function load(array $config, ContainerBuilder $container) { $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); - $loader->load('service.yml'); + $loader->load('services.yml'); $loader->load('form.yml'); } } diff --git a/src/Marello/Bundle/TaxBundle/Entity/ZipCode.php b/src/Marello/Bundle/TaxBundle/Entity/ZipCode.php index 14ff0d0af..3552e432d 100755 --- a/src/Marello/Bundle/TaxBundle/Entity/ZipCode.php +++ b/src/Marello/Bundle/TaxBundle/Entity/ZipCode.php @@ -6,7 +6,6 @@ use Oro\Bundle\EntityBundle\EntityProperty\DatesAwareInterface; use Oro\Bundle\EntityBundle\EntityProperty\DatesAwareTrait; use Oro\Bundle\EntityConfigBundle\Metadata\Annotation\Config; -use Oro\Bundle\EntityConfigBundle\Metadata\Annotation\ConfigField; /** * @ORM\Entity diff --git a/src/Marello/Bundle/TaxBundle/EventListener/Datagrid/TaxCodeDatagridListener.php b/src/Marello/Bundle/TaxBundle/EventListener/Datagrid/TaxCodeDatagridListener.php new file mode 100644 index 000000000..61abc597a --- /dev/null +++ b/src/Marello/Bundle/TaxBundle/EventListener/Datagrid/TaxCodeDatagridListener.php @@ -0,0 +1,152 @@ +relatedEntityClass = $relatedEntityClass; + + $this->expressionBuilder = new Expr(); + } + + /** + * @param BuildBefore $event + */ + public function onBuildBefore(BuildBefore $event) + { + $config = $event->getConfig(); + + $this->addSelect($config); + $this->addJoin($config); + $this->addColumn($config); + $this->addSorter($config); + $this->addFilter($config); + } + + /** + * @param DatagridConfiguration $configuration + * @return string + * @throws \InvalidArgumentException when a root entity not found in the grid + */ + protected function getAlias(DatagridConfiguration $configuration) + { + $rootAlias = $configuration->getOrmQuery()->getRootAlias(); + if (!$rootAlias) { + throw new \InvalidArgumentException( + sprintf( + 'A root entity is missing for grid "%s"', + $configuration->getName() + ) + ); + } + + return $rootAlias; + } + + /** + * @return string + */ + protected function getColumnLabel() + { + return 'marello.tax.taxcode.entity_label'; + } + + /** + * @return string + */ + protected function getDataName() + { + return self::DATA_NAME; + } + + /** + * @return string + */ + protected function getJoinAlias() + { + return self::JOIN_ALIAS; + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSelect(DatagridConfiguration $config) + { + $config->getOrmQuery()->addSelect( + sprintf('%s.code AS %s', $this->getJoinAlias(), $this->getDataName()) + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addJoin(DatagridConfiguration $config) + { + $config->getOrmQuery()->addLeftJoin( + $this->getAlias($config).'.taxCode', + $this->getJoinAlias() + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addColumn(DatagridConfiguration $config) + { + $config->offsetSetByPath(sprintf('[columns][%s]', $this->getDataName()), ['label' => $this->getColumnLabel()]); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addSorter(DatagridConfiguration $config) + { + $config->offsetSetByPath( + sprintf('[sorters][columns][%s]', $this->getDataName()), + ['data_name' => $this->getDataName()] + ); + } + + /** + * @param DatagridConfiguration $config + */ + protected function addFilter(DatagridConfiguration $config) + { + $config->offsetSetByPath( + sprintf('[filters][columns][%s]', $this->getDataName()), + [ + 'type' => 'entity', + 'data_name' => $this->getAlias($config) . '.taxCode', + 'options' => [ + 'field_options' => [ + 'multiple' => true, + 'class' => $this->taxCodeClass, + 'choice_label' => 'code' + ] + ] + ] + ); + } +} diff --git a/src/Marello/Bundle/TaxBundle/Migrations/Schema/v1_3/MarelloTaxBundle.php b/src/Marello/Bundle/TaxBundle/Migrations/Schema/v1_3/MarelloTaxBundle.php index 6bd366187..85c07aa71 100644 --- a/src/Marello/Bundle/TaxBundle/Migrations/Schema/v1_3/MarelloTaxBundle.php +++ b/src/Marello/Bundle/TaxBundle/Migrations/Schema/v1_3/MarelloTaxBundle.php @@ -3,7 +3,6 @@ namespace Marello\Bundle\TaxBundle\Migrations\Schema\v1_3; use Doctrine\DBAL\Schema\Schema; -use Doctrine\DBAL\Types\Type; use Oro\Bundle\MigrationBundle\Migration\Migration; use Oro\Bundle\MigrationBundle\Migration\QueryBag; diff --git a/src/Marello/Bundle/TaxBundle/Provider/TaxCode/TaxCodesChoicesProvider.php b/src/Marello/Bundle/TaxBundle/Provider/TaxCode/TaxCodesChoicesProvider.php index fe8d7e57e..cc0516974 100755 --- a/src/Marello/Bundle/TaxBundle/Provider/TaxCode/TaxCodesChoicesProvider.php +++ b/src/Marello/Bundle/TaxBundle/Provider/TaxCode/TaxCodesChoicesProvider.php @@ -2,7 +2,6 @@ namespace Marello\Bundle\TaxBundle\Provider\TaxCode; -use Marello\Bundle\SupplierBundle\Entity\Supplier; use Marello\Bundle\TaxBundle\Entity\TaxCode; use Oro\Bundle\EntityBundle\ORM\DoctrineHelper; diff --git a/src/Marello/Bundle/TaxBundle/Provider/TaxSubtotalProvider.php b/src/Marello/Bundle/TaxBundle/Provider/TaxSubtotalProvider.php index 279f940e0..734731879 100644 --- a/src/Marello/Bundle/TaxBundle/Provider/TaxSubtotalProvider.php +++ b/src/Marello/Bundle/TaxBundle/Provider/TaxSubtotalProvider.php @@ -8,7 +8,7 @@ use Marello\Bundle\TaxBundle\Factory\TaxFactory; use Marello\Bundle\TaxBundle\Model\Result; use Marello\Bundle\TaxBundle\Model\Taxable; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; class TaxSubtotalProvider implements SubtotalProviderInterface { diff --git a/src/Marello/Bundle/TaxBundle/Resources/config/form.yml b/src/Marello/Bundle/TaxBundle/Resources/config/form.yml index 2213ce800..5b7e53959 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/config/form.yml +++ b/src/Marello/Bundle/TaxBundle/Resources/config/form.yml @@ -1,10 +1,3 @@ -parameters: - marello_tax.taxcode.entity.class: Marello\Bundle\TaxBundle\Entity\TaxCode - marello_tax.taxrate.entity.class: Marello\Bundle\TaxBundle\Entity\TaxRate - marello_tax.taxrule.entity.class: Marello\Bundle\TaxBundle\Entity\TaxRule - marello_tax.taxjurisdiction.entity.class: Marello\Bundle\TaxBundle\Entity\TaxJurisdiction - marello_tax.zipcode.entity.class: Marello\Bundle\TaxBundle\Entity\ZipCode - services: ## forms marello_tax.form.type.tax_code: @@ -58,7 +51,7 @@ services: marello_tax.taxcode.form.autocomplete.search_handler: parent: oro_form.autocomplete.search_handler arguments: - - '%marello_tax.taxcode.entity.class%' + - 'Marello\Bundle\TaxBundle\Entity\TaxCode' - ["code"] tags: - { name: oro_form.autocomplete.search_handler, alias: taxcodes, acl_resource: marello_tax_taxcode_view } @@ -66,7 +59,7 @@ services: marello_tax.taxrate.form.autocomplete.search_handler: parent: oro_form.autocomplete.search_handler arguments: - - '%marello_tax.taxrate.entity.class%' + - 'Marello\Bundle\TaxBundle\Entity\TaxRate' - ['code'] tags: - { name: oro_form.autocomplete.search_handler, alias: taxrates, acl_resource: marello_tax_taxrate_view } @@ -74,7 +67,7 @@ services: marello_tax.taxjurisdiction.form.autocomplete.search_handler: parent: oro_form.autocomplete.search_handler arguments: - - '%marello_tax.taxjurisdiction.entity.class%' + - 'Marello\Bundle\TaxBundle\Entity\TaxJurisdiction' - ['code'] tags: - { name: oro_form.autocomplete.search_handler, alias: taxjurisdictions, acl_resource: marello_tax_taxjurisdiction_view } @@ -101,7 +94,7 @@ services: ## handlers marello_tax.form.handler.taxcode: class: Marello\Bundle\TaxBundle\Form\Handler\TaxCodeHandler - scope: request + public: true arguments: - '@marello_tax.taxcode.form' - '@request_stack' @@ -109,7 +102,7 @@ services: marello_tax.form.handler.taxrate: class: Marello\Bundle\TaxBundle\Form\Handler\TaxRateHandler - scope: request + public: true arguments: - '@marello_tax.taxrate.form' - '@request_stack' @@ -117,7 +110,7 @@ services: marello_tax.form.handler.taxrule: class: Marello\Bundle\TaxBundle\Form\Handler\TaxRuleHandler - scope: request + public: true arguments: - '@marello_tax.taxrule.form' - '@request_stack' diff --git a/src/Marello/Bundle/TaxBundle/Resources/config/oro/datagrids.yml b/src/Marello/Bundle/TaxBundle/Resources/config/oro/datagrids.yml index dc42f38ea..8c3ade965 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/config/oro/datagrids.yml +++ b/src/Marello/Bundle/TaxBundle/Resources/config/oro/datagrids.yml @@ -21,7 +21,7 @@ datagrids: description: data_name: t.description default: - code: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC" + code: "ASC" filters: columns: code: @@ -69,7 +69,7 @@ datagrids: rate: data_name: t.rate default: - code: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC" + code: "ASC" filters: columns: code: @@ -130,7 +130,7 @@ datagrids: jurisdiction: data_name: jurisdiction default: - code: "%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC" + code: "ASC" filters: columns: code: diff --git a/src/Marello/Bundle/TaxBundle/Resources/config/service.yml b/src/Marello/Bundle/TaxBundle/Resources/config/services.yml similarity index 86% rename from src/Marello/Bundle/TaxBundle/Resources/config/service.yml rename to src/Marello/Bundle/TaxBundle/Resources/config/services.yml index 9c1576ff4..db423a671 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/config/service.yml +++ b/src/Marello/Bundle/TaxBundle/Resources/config/services.yml @@ -1,8 +1,3 @@ -parameters: - marello_tax.entity.tax_code.class: Marello\Bundle\TaxBundle\Entity\TaxCode - marello_tax.entity.tax_rate.class: Marello\Bundle\TaxBundle\Entity\TaxRate - marello_tax.entity.tax_rule.class: Marello\Bundle\TaxBundle\Entity\TaxRule - marello_tax.entity.tax_jurisdiction.class: Marello\Bundle\TaxBundle\Entity\TaxJurisdiction services: marello_tax.tax_rule.matcher.composite: class: Marello\Bundle\TaxBundle\Matcher\CompositeTaxRuleMatcher @@ -80,8 +75,8 @@ services: class: 'Marello\Bundle\TaxBundle\Resolver\CustomerAddressItemResolver' public: false arguments: - - "@marello_tax.resolver.row_total" - - "@marello_tax.tax_rule.matcher.composite" + - '@marello_tax.resolver.row_total' + - '@marello_tax.tax_rule.matcher.composite' tags: - { name: marello_tax.resolver, event: marello_tax.resolve, priority: -32 } @@ -89,7 +84,7 @@ services: class: 'Marello\Bundle\TaxBundle\Resolver\CustomerAddressResolver' public: false arguments: - - "@marello_tax.resolver.customer_address.item" + - '@marello_tax.resolver.customer_address.item' tags: - { name: marello_tax.resolver, event: marello_tax.resolve, priority: -32 } @@ -109,13 +104,13 @@ services: class: 'Marello\Bundle\TaxBundle\Event\TaxEventDispatcher' public: false arguments: - - "@event_dispatcher" + - '@event_dispatcher' marello_tax.order_tax.mapper.order_item_mapper: class: 'Marello\Bundle\TaxBundle\OrderTax\Mapper\OrderItemMapper' public: false arguments: - - '%marello_order.entity.order_item.class%' + - 'Marello\Bundle\OrderBundle\Entity\OrderItem' tags: - { name: marello_tax.tax_mapper } @@ -123,7 +118,7 @@ services: class: 'Marello\Bundle\TaxBundle\OrderTax\Mapper\OrderMapper' public: false arguments: - - '%marello_order.entity.order.class%' + - 'Marello\Bundle\OrderBundle\Entity\Order' calls: - ['setOrderItemMapper', ['@marello_tax.order_tax.mapper.order_item_mapper']] tags: @@ -131,5 +126,13 @@ services: marello_tax.provider.tax_codes_choices: class: 'Marello\Bundle\TaxBundle\Provider\TaxCode\TaxCodesChoicesProvider' + public: true arguments: - '@oro_entity.doctrine_helper' + + marello_tax.event_listener.datagrid.products_grid: + class: 'Marello\Bundle\TaxBundle\EventListener\Datagrid\TaxCodeDatagridListener' + arguments: + - 'Marello\Bundle\ProductBundle\Entity\Product' + tags: + - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.before.marello-products-grid, method: onBuildBefore } \ No newline at end of file diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/Form/fields.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/Form/fields.html.twig index 311d87dcf..37fef5db9 100755 --- a/src/Marello/Bundle/TaxBundle/Resources/views/Form/fields.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/Form/fields.html.twig @@ -1,7 +1,10 @@ {% block marello_tax_zip_code_collection_type_widget %} + {% import _self as fields %} + {% spaceless %} + {% set widgetContainerAttributes = block('widget_container_attributes') %} {% if prototype is defined %} - {% set prototype_html = _self.marello_tax_zip_code_collection_prototype(form) %} + {% set prototype_html = fields.marello_tax_zip_code_collection_prototype(form, widgetContainerAttributes) %} {% endif %} {% set attr = attr|merge({'class': (attr.class is defined ? attr.class ~ ' ' : '') ~ 'oro-item-collection collection-fields-list' }) %} {% set id = id ~ '_collection' %} @@ -20,7 +23,7 @@ {% if form.children|length %} {% for child in form.children %} - {{ _self.marello_tax_zip_code_collection_prototype(child) }} + {{ fields.marello_tax_zip_code_collection_prototype(child, widgetContainerAttributes) }} {% endfor %} {% elseif show_form_when_empty and prototype_html is defined %} {% for i in 0..(form.vars.row_count_initial - 1) %} @@ -37,7 +40,7 @@ {% endspaceless %} {% endblock %} -{% macro marello_tax_zip_code_collection_prototype(widget) %} +{% macro marello_tax_zip_code_collection_prototype(widget, attributes) %} {% if 'collection' in widget.vars.block_prefixes %} {% set form = widget.vars.prototype %} {% set name = widget.vars.full_name ~ '[' ~ widget.vars.prototype.vars.name ~ ']' %} @@ -45,7 +48,7 @@ {% set form = widget %} {% set name = widget.vars.full_name %} {% endif %} - {{ form_widget(form) }} diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/create.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/create.html.twig index 9a9a5845e..4ad51d8b5 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/create.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/create.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} {% set formAction = path('marello_tax_taxcode_update', { 'id': form.vars.value.id }) %} @@ -9,9 +10,9 @@ {% block navButtons %} {{ UI.cancelButton(path('marello_tax_taxcode_index')) }} - {% if resource_granted('marello_tax_taxcode_create') %} + {% if is_granted('marello_tax_taxcode_create') %} {% set html = '' %} - {% if resource_granted('marello_tax_taxcode_view') %} + {% if is_granted('marello_tax_taxcode_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_tax_taxcode_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/update.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/update.html.twig index 7344c9b59..500326969 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/update.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} {% set formAction = path('marello_tax_taxcode_update', { 'id': form.vars.value.id }) %} @@ -11,9 +12,9 @@ {{ parent() }} {{ UI.cancelButton(path('marello_tax_taxcode_index')) }} - {% if entity.id and resource_granted('marello_tax_taxcode_update') %} + {% if entity.id and is_granted('marello_tax_taxcode_update') %} {% set html = '' %} - {% if resource_granted('marello_tax_taxcode_view') %} + {% if is_granted('marello_tax_taxcode_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_tax_taxcode_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/view.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/view.html.twig index 92da230ec..4f0e82b61 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/view.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxCode/view.html.twig @@ -27,7 +27,7 @@
{{ UI.renderProperty('marello.tax.taxcode.code.label'|trans, entity.code) }} {{ UI.renderProperty('marello.tax.taxcode.description.label'|trans, entity.description) }} - {% set taxCodeViewGranted = resource_granted('marello_tax_taxcode_update') %} + {% set taxCodeViewGranted = is_granted('marello_tax_taxcode_update') %} {%- if taxCodeViewGranted -%} {%- set taxCodeData -%}
diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/index.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/index.html.twig index 081ae3916..12b9e8e68 100755 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/index.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/index.html.twig @@ -6,7 +6,7 @@ {% set pageTitle = 'marello.tax.taxjurisdiction.entity_plural_label'|trans %} {% block navButtons %} - {% if resource_granted('marello_tax_taxjurisdiction_create') %} + {% if is_granted('marello_tax_taxjurisdiction_create') %}
{{ UI.addButton({ 'path': path('marello_tax_taxjurisdiction_create'), diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/update.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/update.html.twig index 32722030a..7747902b2 100755 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/update.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/update.html.twig @@ -1,5 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} - +{% import 'OroUIBundle::macros.html.twig' as UI %} {% set formAction = entity.id ? path('marello_tax_taxjurisdiction_update', {id: entity.id}) : path('marello_tax_taxjurisdiction_create') %} {% oro_title_set({params : {"%taxCode%": entity.code|default('N/A'|trans), '%entityName%': 'marello.tax.taxjurisdiction.entity_label'|trans} }) %} @@ -8,9 +8,9 @@ {{ parent() }} {{ UI.cancelButton(path('marello_tax_taxjurisdiction_index')) }} - {% if entity.id and resource_granted('marello_tax_taxjurisdiction_update') or resource_granted('marello_tax_taxjurisdiction_create') %} + {% if entity.id and is_granted('marello_tax_taxjurisdiction_update') or is_granted('marello_tax_taxjurisdiction_create') %} {% set html = '' %} - {% if resource_granted('marello_tax_taxjurisdiction_view') %} + {% if is_granted('marello_tax_taxjurisdiction_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_tax_taxjurisdiction_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/view.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/view.html.twig index d898cca6c..51e290d07 100755 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/view.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxJurisdiction/view.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:view.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% oro_title_set({params : {"%taxCode%": entity.code } }) %} diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/create.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/create.html.twig index 9be9ec030..5c609bfba 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/create.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/create.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} {% set formAction = path('marello_tax_taxrate_update', { 'id': form.vars.value.id }) %} @@ -9,9 +10,9 @@ {% block navButtons %} {{ UI.cancelButton(path('marello_tax_taxrate_index')) }} - {% if resource_granted('marello_tax_taxrate_create') %} + {% if is_granted('marello_tax_taxrate_create') %} {% set html = '' %} - {% if resource_granted('marello_tax_taxrate_view') %} + {% if is_granted('marello_tax_taxrate_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_tax_taxrate_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/update.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/update.html.twig index 1c98f88df..c9b4371d8 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/update.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} {% set formAction = path('marello_tax_taxrate_update', { 'id': form.vars.value.id }) %} @@ -11,9 +12,9 @@ {{ parent() }} {{ UI.cancelButton(path('marello_tax_taxrate_index')) }} - {% if entity.id and resource_granted('marello_tax_taxrate_update') %} + {% if entity.id and is_granted('marello_tax_taxrate_update') %} {% set html = '' %} - {% if resource_granted('marello_tax_taxrate_view') %} + {% if is_granted('marello_tax_taxrate_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_tax_taxrate_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/view.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/view.html.twig index 38a0767b2..ae981ac11 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/view.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRate/view.html.twig @@ -27,7 +27,7 @@
{{ UI.renderProperty('marello.tax.taxrate.code.label'|trans, entity.code) }} {{ UI.renderProperty('marello.tax.taxrate.rate.label'|trans, entity.rate|oro_format_percent) }} - {% set taxRateViewGranted = resource_granted('marello_tax_taxrate_update') %} + {% set taxRateViewGranted = is_granted('marello_tax_taxrate_update') %} {%- if taxRateViewGranted -%} {%- set taxRateData -%}
diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/create.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/create.html.twig index 54a619e16..1ea25d47c 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/create.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/create.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} {% set formAction = path('marello_tax_taxrule_update', { 'id': form.vars.value.id }) %} @@ -9,9 +10,9 @@ {% block navButtons %} {{ UI.cancelButton(path('marello_tax_taxrule_index')) }} - {% if resource_granted('marello_tax_taxrule_create') %} + {% if is_granted('marello_tax_taxrule_create') %} {% set html = '' %} - {% if resource_granted('marello_tax_taxrule_view') %} + {% if is_granted('marello_tax_taxrule_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_tax_taxrule_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/update.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/update.html.twig index a85a23776..5df5dfe91 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/update.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/update.html.twig @@ -1,4 +1,5 @@ {% extends 'OroUIBundle:actions:update.html.twig' %} +{% import 'OroUIBundle::macros.html.twig' as UI %} {% form_theme form with 'OroFormBundle:Form:fields.html.twig' %} {% if form.vars.value.id %} {% set formAction = path('marello_tax_taxrule_update', { 'id': form.vars.value.id }) %} @@ -11,9 +12,9 @@ {{ parent() }} {{ UI.cancelButton(path('marello_tax_taxrule_index')) }} - {% if entity.id and resource_granted('marello_tax_taxrule_update') %} + {% if entity.id and is_granted('marello_tax_taxrule_update') %} {% set html = '' %} - {% if resource_granted('marello_tax_taxrule_view') %} + {% if is_granted('marello_tax_taxrule_view') %} {% set html = UI.saveAndCloseButton({ 'route': 'marello_tax_taxrule_view', 'params': {'id': '$id'} diff --git a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/view.html.twig b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/view.html.twig index 340216fb3..9d5f2fad5 100644 --- a/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/view.html.twig +++ b/src/Marello/Bundle/TaxBundle/Resources/views/TaxRule/view.html.twig @@ -28,7 +28,7 @@ {{ UI.renderProperty('marello.tax.taxrule.tax_code.label'|trans, entity.taxCode) }} {{ UI.renderHtmlProperty('marello.tax.taxrule.tax_rate.label'|trans, entity.taxRate.code ~ '(' ~ entity.taxRate.rate|oro_format_percent ~ ')' ) }} {{ UI.renderProperty('marello.tax.taxrule.tax_jurisdiction.label'|trans, entity.taxJurisdiction) }} - {% set taxRateViewGranted = resource_granted('marello_tax_taxrule_update') %} + {% set taxRateViewGranted = is_granted('marello_tax_taxrule_update') %} {%- if taxRateViewGranted -%} {%- set taxRateData -%} diff --git a/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxCodeDeleteOperationTest.php b/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxCodeDeleteOperationTest.php index 0c7e73852..a1183fc0e 100755 --- a/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxCodeDeleteOperationTest.php +++ b/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxCodeDeleteOperationTest.php @@ -3,6 +3,8 @@ namespace Marello\Bundle\TaxBundle\Tests\Functional\Operation; use Oro\Bundle\ActionBundle\Tests\Functional\ActionTestCase; + +use Marello\Bundle\TaxBundle\Entity\TaxCode; use Marello\Bundle\TaxBundle\Tests\Functional\DataFixtures\LoadTaxCodeData; class TaxCodeDeleteOperationTest extends ActionTestCase @@ -25,7 +27,7 @@ public function testDelete() $this->assertDeleteOperation( $productTaxCode->getId(), - 'marello_tax.taxcode.entity.class', + TaxCode::class, 'marello_tax_taxcode_index' ); } diff --git a/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxJurisdictionDeleteOperationTest.php b/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxJurisdictionDeleteOperationTest.php index 1e2bb0f8e..197de0c94 100755 --- a/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxJurisdictionDeleteOperationTest.php +++ b/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxJurisdictionDeleteOperationTest.php @@ -2,10 +2,12 @@ namespace Marello\Bundle\TaxBundle\Tests\Functional\Operation; -use Marello\Bundle\TaxBundle\Tests\Functional\DataFixtures\LoadTaxJurisdictionData; -use Marello\Bundle\TaxBundle\Tests\Functional\DataFixtures\LoadTaxRateData; use Oro\Bundle\ActionBundle\Tests\Functional\ActionTestCase; +use Marello\Bundle\TaxBundle\Entity\TaxJurisdiction; +use Marello\Bundle\TaxBundle\Tests\Functional\DataFixtures\LoadTaxRateData; +use Marello\Bundle\TaxBundle\Tests\Functional\DataFixtures\LoadTaxJurisdictionData; + class TaxJurisdictionDeleteOperationTest extends ActionTestCase { protected function setUp() @@ -28,7 +30,7 @@ public function testDelete() $this->assertDeleteOperation( $taxJurisdiction->getId(), - 'marello_tax.taxjurisdiction.entity.class', + TaxJurisdiction::class, 'marello_tax_taxjurisdiction_index' ); } diff --git a/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxRateDeleteOperationTest.php b/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxRateDeleteOperationTest.php index 7916b22a7..fde779d29 100755 --- a/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxRateDeleteOperationTest.php +++ b/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxRateDeleteOperationTest.php @@ -3,6 +3,8 @@ namespace Marello\Bundle\TaxBundle\Tests\Functional\Operation; use Oro\Bundle\ActionBundle\Tests\Functional\ActionTestCase; + +use Marello\Bundle\TaxBundle\Entity\TaxRate; use Marello\Bundle\TaxBundle\Tests\Functional\DataFixtures\LoadTaxRateData; class TaxRateDeleteOperationTest extends ActionTestCase @@ -25,7 +27,7 @@ public function testDelete() $this->assertDeleteOperation( $taxRate->getId(), - 'marello_tax.taxrate.entity.class', + TaxRate::class, 'marello_tax_taxrate_index' ); } diff --git a/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxRuleDeleteOperationTest.php b/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxRuleDeleteOperationTest.php index 0efafc8ed..5ef1ef5b2 100755 --- a/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxRuleDeleteOperationTest.php +++ b/src/Marello/Bundle/TaxBundle/Tests/Functional/Operation/TaxRuleDeleteOperationTest.php @@ -2,9 +2,11 @@ namespace Marello\Bundle\TaxBundle\Tests\Functional\Operation; -use Marello\Bundle\TaxBundle\Tests\Functional\DataFixtures\LoadTaxRuleData; use Oro\Bundle\ActionBundle\Tests\Functional\ActionTestCase; +use Marello\Bundle\TaxBundle\Entity\TaxRule; +use Marello\Bundle\TaxBundle\Tests\Functional\DataFixtures\LoadTaxRuleData; + class TaxRuleDeleteOperationTest extends ActionTestCase { protected function setUp() @@ -27,7 +29,7 @@ public function testDelete() $this->assertDeleteOperation( $taxRule->getId(), - 'marello_tax.taxrule.entity.class', + TaxRule::class, 'marello_tax_taxrule_index' ); } diff --git a/src/Marello/Bundle/TaxBundle/Tests/Unit/Calculator/ExcludedTaxCalculatorTest.php b/src/Marello/Bundle/TaxBundle/Tests/Unit/Calculator/ExcludedTaxCalculatorTest.php index 8e59110c4..725cc8ff5 100644 --- a/src/Marello/Bundle/TaxBundle/Tests/Unit/Calculator/ExcludedTaxCalculatorTest.php +++ b/src/Marello/Bundle/TaxBundle/Tests/Unit/Calculator/ExcludedTaxCalculatorTest.php @@ -23,10 +23,10 @@ public function calculateDataProvider() // edge cases [['31.96', '15.98', '15.98'], '15.98', '1'], [['47.94', '15.98', '31.96'], '15.98', '2'], - [['31.80', '15.98', '15.82'], '15.98', '0.99'], - [['16.00', '15.98', '0.02'], '15.98', '0.001'], - [['16.00', '15.98', '0.02'], '15.98', '0.0015'], - [['19.18', '15.98', '3.20'], '15.98', '-0.2'], + [['31.8', '15.98', '15.82'], '15.98', '0.99'], + [['16', '15.98', '0.02'], '15.98', '0.001'], + [['16', '15.98', '0.02'], '15.98', '0.0015'], + [['19.18', '15.98', '3.2'], '15.98', '-0.2'], ]; } diff --git a/src/Marello/Bundle/TaxBundle/Tests/Unit/Provider/TaxSubtotalProviderTest.php b/src/Marello/Bundle/TaxBundle/Tests/Unit/Provider/TaxSubtotalProviderTest.php index 6a788f78e..194ae112f 100644 --- a/src/Marello/Bundle/TaxBundle/Tests/Unit/Provider/TaxSubtotalProviderTest.php +++ b/src/Marello/Bundle/TaxBundle/Tests/Unit/Provider/TaxSubtotalProviderTest.php @@ -2,7 +2,7 @@ namespace Marello\Bundle\TaxBundle\Tests\Unit\Provider; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use PHPUnit\Framework\TestCase; diff --git a/src/Marello/Bundle/TaxBundle/Tests/Unit/Resolver/TotalResolverTest.php b/src/Marello/Bundle/TaxBundle/Tests/Unit/Resolver/TotalResolverTest.php index 2500ef25d..e2d6cfb01 100644 --- a/src/Marello/Bundle/TaxBundle/Tests/Unit/Resolver/TotalResolverTest.php +++ b/src/Marello/Bundle/TaxBundle/Tests/Unit/Resolver/TotalResolverTest.php @@ -117,11 +117,11 @@ public function resolveDataProvider() 'no shipping taxes' => [ 'items' => [ [ - Result::ROW => ResultElement::create('21.50', '20.00', '1.50'), + Result::ROW => ResultElement::create('21.5', '20', '1.5'), ], ], 'shippingResult' => null, - 'expectedTotalResult' => ResultElement::create('21.50', '20.00', '1.50'), + 'expectedTotalResult' => ResultElement::create('21.5', '20', '1.5'), ], ]; } diff --git a/src/Marello/Bundle/UPSBundle/Controller/AjaxUPSController.php b/src/Marello/Bundle/UPSBundle/Controller/AjaxUPSController.php index bee645245..60a1485c9 100644 --- a/src/Marello/Bundle/UPSBundle/Controller/AjaxUPSController.php +++ b/src/Marello/Bundle/UPSBundle/Controller/AjaxUPSController.php @@ -9,21 +9,22 @@ use Marello\Bundle\UPSBundle\Entity\Repository\ShippingServiceRepository; use Marello\Bundle\UPSBundle\Entity\UPSSettings; use Oro\Bundle\IntegrationBundle\Form\Type\ChannelType; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -class AjaxUPSController extends Controller +class AjaxUPSController extends AbstractController { /** - * @Route("/get-shipping-services-by-country/{code}", - * name="marello_ups_country_shipping_services", - * requirements={"code"="^[A-Z]{2}$"}) + * @Route( + * path="/get-shipping-services-by-country/{code}", + * methods={"GET"}, + * name="marello_ups_country_shipping_services", + * requirements={"code"="^[A-Z]{2}$"} + * ) * @ParamConverter("country", options={"id" = "code"}) - * @Method("GET") * @param Country $country * @return JsonResponse */ @@ -43,9 +44,12 @@ public function getShippingServicesByCountryAction(Country $country) } /** - * @Route("/validate-connection/{channelId}/", name="marello_ups_validate_connection") + * @Route( + * path="/validate-connection/{channelId}/", + * methods={"POST"}, + * name="marello_ups_validate_connection" + * ) * @ParamConverter("channel", class="OroIntegrationBundle:Channel", options={"id" = "channelId"}) - * @Method("POST") * * @param Request $request * @param Channel|null $channel diff --git a/src/Marello/Bundle/UPSBundle/Migrations/Data/ORM/Config/ChannelByTypeFactory.php b/src/Marello/Bundle/UPSBundle/Migrations/Data/ORM/Config/ChannelByTypeFactory.php index ef87990d2..74c61e923 100755 --- a/src/Marello/Bundle/UPSBundle/Migrations/Data/ORM/Config/ChannelByTypeFactory.php +++ b/src/Marello/Bundle/UPSBundle/Migrations/Data/ORM/Config/ChannelByTypeFactory.php @@ -6,7 +6,7 @@ use Marello\Bundle\UPSBundle\Provider\ChannelType; use Oro\Bundle\IntegrationBundle\Entity\Channel; use Oro\Bundle\OrganizationBundle\Entity\OrganizationInterface; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; class ChannelByTypeFactory { diff --git a/src/Marello/Bundle/UPSBundle/Resources/config/jsmodules.yml b/src/Marello/Bundle/UPSBundle/Resources/config/jsmodules.yml new file mode 100644 index 000000000..3303c13e7 --- /dev/null +++ b/src/Marello/Bundle/UPSBundle/Resources/config/jsmodules.yml @@ -0,0 +1,3 @@ +dynamic-imports: + marelloups: + - marelloups/js/app/components/ups-transport-settings-component \ No newline at end of file diff --git a/src/Marello/Bundle/UPSBundle/Resources/config/requirejs.yml b/src/Marello/Bundle/UPSBundle/Resources/config/requirejs.yml deleted file mode 100644 index b91462d5a..000000000 --- a/src/Marello/Bundle/UPSBundle/Resources/config/requirejs.yml +++ /dev/null @@ -1,3 +0,0 @@ -config: - paths: - 'marelloups/js/app/components/ups-transport-settings-component': 'bundles/marelloups/js/app/components/ups-transport-settings-component.js' diff --git a/src/Marello/Bundle/UPSBundle/Resources/config/services.yml b/src/Marello/Bundle/UPSBundle/Resources/config/services.yml index f0455d997..51076f5b6 100644 --- a/src/Marello/Bundle/UPSBundle/Resources/config/services.yml +++ b/src/Marello/Bundle/UPSBundle/Resources/config/services.yml @@ -34,6 +34,7 @@ services: marello_ups.method.identifier_generator.method: parent: oro_integration.generator.prefixed_identifier_generator + public: true arguments: - '%marello_ups.integration.channel.type%' diff --git a/src/Marello/Bundle/UPSBundle/Resources/public/js/app/components/ups-transport-settings-component.js b/src/Marello/Bundle/UPSBundle/Resources/public/js/app/components/ups-transport-settings-component.js index 228c9f4b5..45956c70d 100644 --- a/src/Marello/Bundle/UPSBundle/Resources/public/js/app/components/ups-transport-settings-component.js +++ b/src/Marello/Bundle/UPSBundle/Resources/public/js/app/components/ups-transport-settings-component.js @@ -3,14 +3,13 @@ define(function(require) { 'use strict'; - var UPSTransportSettingsComponent; - var $ = require('jquery'); - var _ = require('underscore'); - var routing = require('routing'); - var LoadingMaskView = require('oroui/js/app/views/loading-mask-view'); - var BaseComponent = require('oroui/js/app/components/base/component'); - - UPSTransportSettingsComponent = BaseComponent.extend({ + const $ = require('jquery'); + const _ = require('underscore'); + const routing = require('routing'); + const LoadingMaskView = require('oroui/js/app/views/loading-mask-view'); + const BaseComponent = require('oroui/js/app/components/base/component'); + + const UPSTransportSettingsComponent = BaseComponent.extend({ /** * @property {Object} */ diff --git a/src/Marello/Bundle/UPSBundle/Tests/Unit/Factory/PriceRequestFactoryTest.php b/src/Marello/Bundle/UPSBundle/Tests/Unit/Factory/PriceRequestFactoryTest.php index 4550e2af7..c256060fb 100644 --- a/src/Marello/Bundle/UPSBundle/Tests/Unit/Factory/PriceRequestFactoryTest.php +++ b/src/Marello/Bundle/UPSBundle/Tests/Unit/Factory/PriceRequestFactoryTest.php @@ -13,7 +13,7 @@ use Oro\Bundle\SecurityBundle\Encoder\SymmetricCrypterInterface; use Marello\Bundle\UPSBundle\Model\Package; -use Marello\Bundle\OrderBundle\Entity\Customer; +use Marello\Bundle\CustomerBundle\Entity\Customer; use Marello\Bundle\ProductBundle\Entity\Product; use Marello\Bundle\UPSBundle\Entity\UPSSettings; use Marello\Bundle\UPSBundle\Entity\ShippingService;