diff --git a/commerce_fee.info.yml b/commerce_fee.info.yml index 7314f25..5901717 100755 --- a/commerce_fee.info.yml +++ b/commerce_fee.info.yml @@ -2,13 +2,9 @@ name: Commerce Fee type: module description: 'Provides a UI for managing fees.' package: Commerce -# core: 8.x +core_version_requirement: ^8.7.7 || ^9 dependencies: - commerce:commerce - commerce:commerce_order - inline_entity_form:inline_entity_form - drupal:options - -version: '8.x-2.4' -core: '8.x' -project: 'commerce' diff --git a/commerce_fee.module b/commerce_fee.module index cf0c57d..945fdd9 100755 --- a/commerce_fee.module +++ b/commerce_fee.module @@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\views\Plugin\views\field\EntityField; /** * Implements hook_commerce_condition_info_alter(). @@ -67,6 +68,31 @@ function template_preprocess_commerce_fee(array &$variables) { } } +/** + * Implements hook_form_FORM_ID_alter(). + * + * Removes core's built-in formatters from views field options for + * fee start_date and end_date fields, since they perform timezone + * conversion. The "Default (Store timezone)" formatter should be used instead. + */ +function commerce_fee_form_views_ui_config_item_form_alter(&$form, FormStateInterface $form_state) { + /** @var \Drupal\views\Plugin\views\field\EntityField $handler */ + $handler = $form_state->get('handler'); + if ($handler instanceof EntityField && !empty($handler->definition['entity_type'])) { + $entity_type_id = $handler->definition['entity_type']; + $field_name = $handler->definition['field_name']; + /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */ + $field_manager = \Drupal::service('entity_field.manager'); + $field_definitions = $field_manager->getFieldStorageDefinitions($entity_type_id); + $field_definition = $field_definitions[$field_name]; + if ($entity_type_id == 'commerce_fee' && $field_definition->getType() == 'datetime') { + unset($form['options']['type']['#options']['datetime_custom']); + unset($form['options']['type']['#options']['datetime_default']); + unset($form['options']['type']['#options']['datetime_plain']); + } + } +} + /** * Implements hook_field_widget_form_alter(). */ diff --git a/commerce_fee.post_update.php b/commerce_fee.post_update.php new file mode 100644 index 0000000..412d999 --- /dev/null +++ b/commerce_fee.post_update.php @@ -0,0 +1,56 @@ +getStorage('commerce_fee'); + if (!isset($sandbox['current_count'])) { + $query = $fee_storage->getQuery(); + $sandbox['total_count'] = $query->count()->execute(); + $sandbox['current_count'] = 0; + + if (empty($sandbox['total_count'])) { + $sandbox['#finished'] = 1; + return; + } + } + + $query = $fee_storage->getQuery(); + $query->range($sandbox['current_count'], 50); + $result = $query->execute(); + if (empty($result)) { + $sandbox['#finished'] = 1; + return; + } + + /** @var \Drupal\commerce_fee\Entity\Fee[] $fees */ + $fees = $fee_storage->loadMultiple($result); + foreach ($fees as $fee) { + // Re-set each date to ensure it is stored in the updated format. + // Increase the end date by a day to match old inclusive loading + // (where an end date was valid until 23:59:59 of that day). + $start_date = $fee->getStartDate(); + $end_date = $fee->getEndDate(); + if ($end_date) { + $end_date = $end_date->modify('+1 day'); + } + $fee->setStartDate($start_date); + $fee->setEndDate($end_date); + + $fee->save(); + } + + $sandbox['current_count'] += 50; + if ($sandbox['current_count'] >= $sandbox['total_count']) { + $sandbox['#finished'] = 1; + } + else { + $sandbox['#finished'] = ($sandbox['total_count'] - $sandbox['current_count']) / $sandbox['total_count']; + } +} diff --git a/src/Entity/Fee.php b/src/Entity/Fee.php index 2b907d6..2291ca8 100755 --- a/src/Entity/Fee.php +++ b/src/Entity/Fee.php @@ -5,6 +5,7 @@ use Drupal\commerce\ConditionGroup; use Drupal\commerce\Entity\CommerceContentEntityBase; use Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface; +use Drupal\commerce\Plugin\Commerce\Condition\ParentEntityAwareInterface; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_fee\Plugin\Commerce\Fee\OrderItemFeeInterface; use Drupal\commerce_fee\Plugin\Commerce\Fee\FeeInterface as FeePluginInterface; @@ -12,44 +13,55 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; /** * Defines the fee entity class. * * @ContentEntityType( * id = "commerce_fee", - * label = @Translation("Fee"), - * label_collection = @Translation("Fees"), - * label_singular = @Translation("fee"), - * label_plural = @Translation("fees"), + * label = @Translation("Fee", context = "Commerce"), + * label_collection = @Translation("Fees", context = "Commerce"), + * label_singular = @Translation("fee", context = "Commerce"), + * label_plural = @Translation("fees", context = "Commerce"), * label_count = @PluralTranslation( * singular = "@count fee", * plural = "@count fees", + * context = "Commerce", * ), * handlers = { * "event" = "Drupal\commerce_fee\Event\FeeEvent", * "storage" = "Drupal\commerce_fee\FeeStorage", * "access" = "Drupal\entity\EntityAccessControlHandler", - * "permission_provider" = "Drupal\commerce\EntityPermissionProvider", + * "permission_provider" = "Drupal\entity\EntityPermissionProvider", * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", * "list_builder" = "Drupal\commerce_fee\FeeListBuilder", - * "views_data" = "Drupal\views\EntityViewsData", + * "views_data" = "Drupal\commerce_fee\FeeViewsData", * "form" = { * "default" = "Drupal\commerce_fee\Form\FeeForm", * "add" = "Drupal\commerce_fee\Form\FeeForm", * "edit" = "Drupal\commerce_fee\Form\FeeForm", + * "duplicate" = "Drupal\commerce_fee\Form\FeeForm", * "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm" * }, + * "local_task_provider" = { + * "default" = "Drupal\entity\Menu\DefaultEntityLocalTaskProvider", + * }, * "route_provider" = { - * "default" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider", + * "default" = "Drupal\entity\Routing\AdminHtmlRouteProvider", * "delete-multiple" = "Drupal\entity\Routing\DeleteMultipleRouteProvider", * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler" + * "translation" = "Drupal\commerce_fee\FeeTranslationHandler" * }, * base_table = "commerce_fee", * data_table = "commerce_fee_field_data", * admin_permission = "administer commerce_fee", * translatable = TRUE, + * translation = { + * "content_translation" = { + * "access_callback" = "content_translation_translate_access" + * }, + * }, * entity_keys = { * "id" = "fee_id", * "label" = "name", @@ -60,14 +72,29 @@ * links = { * "add-form" = "/fee/add", * "edit-form" = "/fee/{commerce_fee}/edit", + * "duplicate-form" = "/fee/{commerce_fee}/duplicate", * "delete-form" = "/fee/{commerce_fee}/delete", * "delete-multiple-form" = "/admin/commerce/fees/delete", * "collection" = "/admin/commerce/fees", + * "drupal:content-translation-overview" = "/fee/{commerce_fee}/translations", + * "drupal:content-translation-add" = "/fee/{commerce_fee}/translations/add/{source}/{target}", + * "drupal:content-translation-edit" = "/fee/{commerce_fee}/translations/edit/{language}", + * "drupal:content-translation-delete" = "/fee/{commerce_fee}/translations/delete/{language}", * }, * ) */ class Fee extends CommerceContentEntityBase implements FeeInterface { + /** + * {@inheritdoc} + */ + public function toUrl($rel = 'canonical', array $options = []) { + if ($rel == 'canonical') { + $rel = 'edit-form'; + } + return parent::toUrl($rel, $options); + } + /** * {@inheritdoc} */ @@ -83,6 +110,21 @@ public function setName($name) { return $this; } + /** + * {@inheritdoc} + */ + public function getDisplayName() { + return $this->get('display_name')->value; + } + + /** + * {@inheritdoc} + */ + public function setDisplayName($display_name) { + $this->set('display_name', $display_name); + return $this; + } + /** * {@inheritdoc} */ @@ -193,7 +235,11 @@ public function getConditions() { $conditions = []; foreach ($this->get('conditions') as $field_item) { /** @var \Drupal\commerce\Plugin\Field\FieldType\PluginItemInterface $field_item */ - $conditions[] = $field_item->getTargetInstance(); + $condition = $field_item->getTargetInstance(); + if ($condition instanceof ParentEntityAwareInterface) { + $condition->setParentEntity($this); + } + $conditions[] = $condition; } return $conditions; } @@ -232,25 +278,24 @@ public function setConditionOperator($condition_operator) { /** * {@inheritdoc} */ - public function getStartDate() { - // Can't use the ->date property because it resets the timezone to UTC. - return new DrupalDateTime($this->get('start_date')->value); + public function getStartDate($store_timezone = 'UTC') { + return new DrupalDateTime($this->get('start_date')->value, $store_timezone); } /** * {@inheritdoc} */ public function setStartDate(DrupalDateTime $start_date) { - $this->get('start_date')->value = $start_date->format('Y-m-d'); + $this->get('start_date')->value = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT); return $this; } /** * {@inheritdoc} */ - public function getEndDate() { + public function getEndDate($store_timezone = 'UTC') { if (!$this->get('end_date')->isEmpty()) { - return new DrupalDateTime($this->get('end_date')->value); + return new DrupalDateTime($this->get('end_date')->value, $store_timezone); } } @@ -258,8 +303,10 @@ public function getEndDate() { * {@inheritdoc} */ public function setEndDate(DrupalDateTime $end_date = NULL) { - $this->get('end_date')->value = $end_date ? $end_date->format('Y-m-d') : NULL; - return $this; + $this->get('end_date')->value = NULL; + if ($end_date) { + $this->get('end_date')->value = $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT); + } } /** @@ -290,12 +337,14 @@ public function available(OrderInterface $order) { if (!in_array($order->getStoreId(), $this->getStoreIds())) { return FALSE; } - $time = \Drupal::time()->getRequestTime(); - if ($this->getStartDate()->format('U') > $time) { + $date = $order->getCalculationDate(); + $store_timezone = $date->getTimezone()->getName(); + $start_date = $this->getStartDate($store_timezone); + if ($start_date->format('U') > $date->format('U')) { return FALSE; } - $end_date = $this->getEndDate(); - if ($end_date && $end_date->format('U') <= $time) { + $end_date = $this->getEndDate($store_timezone); + if ($end_date && $end_date->format('U') <= $date->format('U')) { return FALSE; } @@ -357,6 +406,24 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setDisplayConfigurable('view', TRUE) ->setDisplayConfigurable('form', TRUE); + $fields['display_name'] = BaseFieldDefinition::create('string') + ->setLabel(t('Display name')) + ->setDescription(t('If provided, shown on the order instead of "@translated".', [ + '@translated' => t('Fee'), + ])) + ->setTranslatable(TRUE) + ->setSettings([ + 'display_description' => TRUE, + 'default_value' => '', + 'max_length' => 255, + ]) + ->setDisplayOptions('form', [ + 'type' => 'string_textfield', + 'weight' => 0, + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + $fields['description'] = BaseFieldDefinition::create('string_long') ->setLabel(t('Description')) ->setDescription(t('Additional information about the fee to show to the customer')) @@ -379,7 +446,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setRequired(TRUE) ->setSetting('target_type', 'commerce_order_type') ->setSetting('handler', 'default') - ->setTranslatable(TRUE) ->setDisplayOptions('form', [ 'type' => 'commerce_entity_select', 'weight' => 2, @@ -392,7 +458,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setRequired(TRUE) ->setSetting('target_type', 'commerce_store') ->setSetting('handler', 'default') - ->setTranslatable(TRUE) ->setDisplayOptions('form', [ 'type' => 'commerce_entity_select', 'weight' => 2, @@ -438,10 +503,10 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setLabel(t('Start date')) ->setDescription(t('The date the fee becomes valid.')) ->setRequired(TRUE) - ->setSetting('datetime_type', 'date') + ->setSetting('datetime_type', 'datetime') ->setDefaultValueCallback('Drupal\commerce_fee\Entity\Fee::getDefaultStartDate') ->setDisplayOptions('form', [ - 'type' => 'datetime_default', + 'type' => 'commerce_store_datetime', 'weight' => 5, ]); @@ -449,9 +514,10 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setLabel(t('End date')) ->setDescription(t('The date after which the fee is invalid.')) ->setRequired(FALSE) - ->setSetting('datetime_type', 'date') + ->setSetting('datetime_type', 'datetime') + ->setSetting('datetime_optional_label', t('Provide an end date')) ->setDisplayOptions('form', [ - 'type' => 'commerce_end_date', + 'type' => 'commerce_store_datetime', 'weight' => 6, ]); @@ -482,21 +548,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { */ public static function getDefaultStartDate() { $timestamp = \Drupal::time()->getRequestTime(); - return gmdate('Y-m-d', $timestamp); - } - - /** - * Default value callback for 'end_date' base field definition. - * - * @see ::baseFieldDefinitions() - * - * @return int - * The default value (date string). - */ - public static function getDefaultEndDate() { - // Today + 1 year. - $timestamp = \Drupal::time()->getRequestTime(); - return gmdate('Y-m-d', $timestamp + 31536000); + return gmdate(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $timestamp); } } diff --git a/src/Entity/FeeInterface.php b/src/Entity/FeeInterface.php index bf53580..4fdbde3 100755 --- a/src/Entity/FeeInterface.php +++ b/src/Entity/FeeInterface.php @@ -16,6 +16,8 @@ interface FeeInterface extends ContentEntityInterface, EntityStoresInterface { /** * Gets the fee name. * + * This name is admin-facing. + * * @return string * The fee name. */ @@ -31,6 +33,27 @@ public function getName(); */ public function setName($name); + /** + * Gets the fee display name. + * + * This name is user-facing. + * Shown in the order total summary. + * + * @return string + * The fee display name. If empty, use t('Fee'). + */ + public function getDisplayName(); + + /** + * Sets the fee display name. + * + * @param string $display_name + * The fee display name. + * + * @return $this + */ + public function setDisplayName($display_name); + /** * Gets the fee description. * @@ -140,36 +163,58 @@ public function getConditionOperator(); public function setConditionOperator($condition_operator); /** - * Gets the fee start date. + * Gets the fee start date/time. + * + * The start date/time should always be used in the store timezone. + * Since the fee can belong to multiple stores, the timezone + * isn't known at load/save time, and is provided by the caller instead. + * + * Note that the returned date/time value is the same in any timezone, + * the "2019-10-17 10:00" stored value is returned as "2019-10-17 10:00 CET" + * for "Europe/Berlin" and "2019-10-17 10:00 ET" for "America/New_York". + * + * @param string $store_timezone + * The store timezone. E.g. "Europe/Berlin". * * @return \Drupal\Core\Datetime\DrupalDateTime - * The fee start date. + * The promotion start date/time. */ - public function getStartDate(); + public function getStartDate($store_timezone = 'UTC'); /** - * Sets the fee start date. + * Sets the fee start date/time. * * @param \Drupal\Core\Datetime\DrupalDateTime $start_date - * The fee start date. + * The fee start date/time. * * @return $this */ public function setStartDate(DrupalDateTime $start_date); /** - * Gets the fee end date. + * Gets the fee end date/time. + * + * The end date/time should always be used in the store timezone. + * Since the promotion can belong to multiple stores, the timezone + * isn't known at load/save time, and is provided by the caller instead. + * + * Note that the returned date/time value is the same in any timezone, + * the "2019-10-17 11:00" stored value is returned as "2019-10-17 11:00 CET" + * for "Europe/Berlin" and "2019-10-17 11:00 ET" for "America/New_York". + * + * @param string $store_timezone + * The store timezone. E.g. "Europe/Berlin". * * @return \Drupal\Core\Datetime\DrupalDateTime - * The fee end date. + * The fee end date/time. */ - public function getEndDate(); + public function getEndDate($store_timezone = 'UTC'); /** - * Sets the fee end date. + * Sets the fee end date/time. * * @param \Drupal\Core\Datetime\DrupalDateTime $end_date - * The fee end date. + * The fee end date/time. * * @return $this */ diff --git a/src/FeeListBuilder.php b/src/FeeListBuilder.php index f62e8d8..711709b 100755 --- a/src/FeeListBuilder.php +++ b/src/FeeListBuilder.php @@ -43,8 +43,8 @@ public function buildRow(EntityInterface $entity) { if (!$entity->isEnabled()) { $row['name'] .= ' (' . $this->t('Disabled') . ')'; } - $row['start_date'] = $entity->getStartDate()->format('M jS Y'); - $row['end_date'] = $entity->getEndDate() ? $entity->getEndDate()->format('M jS Y') : '—'; + $row['start_date'] = $entity->getStartDate()->format('M jS Y H:i:s'); + $row['end_date'] = $entity->getEndDate() ? $entity->getEndDate()->format('M jS Y H:i:s') : '—'; return $row + parent::buildRow($entity); } diff --git a/src/FeeStorage.php b/src/FeeStorage.php index 8a7b131..c81d822 100755 --- a/src/FeeStorage.php +++ b/src/FeeStorage.php @@ -8,9 +8,12 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface; use Drupal\Core\Database\Connection; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -33,21 +36,25 @@ class FeeStorage extends CommerceContentEntityStorage implements FeeStorageInter * The entity type definition. * @param \Drupal\Core\Database\Connection $database * The database connection to be used. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager. * @param \Drupal\Core\Cache\CacheBackendInterface $cache * The cache backend to be used. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache * The memory cache. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle info. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher. * @param \Drupal\Component\Datetime\TimeInterface $time * The time. */ - public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache, EventDispatcherInterface $event_dispatcher, TimeInterface $time) { - parent::__construct($entity_type, $database, $entity_manager, $cache, $language_manager, $memory_cache, $event_dispatcher); + public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityFieldManagerInterface $entity_field_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, TimeInterface $time) { + parent::__construct($entity_type, $database, $entity_field_manager, $cache, $language_manager, $memory_cache, $entity_type_bundle_info, $entity_type_manager, $event_dispatcher); $this->time = $time; } @@ -59,10 +66,12 @@ public static function createInstance(ContainerInterface $container, EntityTypeI return new static( $entity_type, $container->get('database'), - $container->get('entity.manager'), + $container->get('entity_field.manager'), $container->get('cache.entity'), $container->get('language_manager'), $container->get('entity.memory_cache'), + $container->get('entity_type.bundle.info'), + $container->get('entity_type.manager'), $container->get('event_dispatcher'), $container->get('datetime.time') ); @@ -72,15 +81,16 @@ public static function createInstance(ContainerInterface $container, EntityTypeI * {@inheritdoc} */ public function loadAvailable(OrderInterface $order) { - $today = gmdate('Y-m-d', $this->time->getRequestTime()); + $date = $order->getCalculationDate()->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT); + $query = $this->getQuery(); $or_condition = $query->orConditionGroup() - ->condition('end_date', $today, '>=') + ->condition('end_date', $date, '>') ->notExists('end_date'); $query ->condition('stores', [$order->getStoreId()], 'IN') ->condition('order_types', [$order->bundle()], 'IN') - ->condition('start_date', $today, '<=') + ->condition('start_date', $date, '<=') ->condition('status', TRUE) ->condition($or_condition); $result = $query->execute(); diff --git a/src/FeeTranslationHandler.php b/src/FeeTranslationHandler.php new file mode 100644 index 0000000..c105835 --- /dev/null +++ b/src/FeeTranslationHandler.php @@ -0,0 +1,40 @@ +hasValue('content_translation')) { + $translation = &$form_state->getValue('content_translation'); + /** @var \Drupal\commerce_fee\Entity\FeeInterface $entity */ + $translation['status'] = $entity->isEnabled(); + $translation['uid'] = 0; + } + parent::entityFormEntityBuild($entity_type, $entity, $form, $form_state); + } + +} diff --git a/src/FeeViewsData.php b/src/FeeViewsData.php new file mode 100644 index 0000000..ec3e20f --- /dev/null +++ b/src/FeeViewsData.php @@ -0,0 +1,26 @@ +entityManager->getStorage('commerce_store')->getQuery(); + $store_query = $this->entityTypeManager->getStorage('commerce_store')->getQuery(); if ($store_query->count()->execute() == 0) { $link = Link::createFromRoute('Add a new store.', 'entity.commerce_store.add_page'); $form['warning'] = [ @@ -32,11 +35,18 @@ public function buildForm(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { - /* @var \Drupal\commerce_fee\Entity\Fee $fee */ - $fee = $this->entity; - $form = parent::form($form, $form_state); + $form['#tree'] = TRUE; + + $translating = !$this->isDefaultFormLangcode($form_state); + $hide_non_translatable_fields = $this->entity->isDefaultTranslationAffectedOnly(); + // The second column is empty when translating with non-translatable + // fields hidden, so there's no reason to add it. + if ($translating && $hide_non_translatable_fields) { + return $form; + } + $form['#theme'] = ['commerce_fee_form']; $form['#attached']['library'][] = 'commerce_fee/form'; @@ -67,7 +77,6 @@ public function form(array $form, FormStateInterface $form_state) { 'start_date' => 'date_details', 'end_date' => 'date_details', ]; - foreach ($field_details_mapping as $field => $group) { if (isset($form[$field])) { $form[$field]['#group'] = $group; @@ -82,7 +91,8 @@ public function form(array $form, FormStateInterface $form_state) { */ public function save(array $form, FormStateInterface $form_state) { $this->entity->save(); - drupal_set_message($this->t('Saved the %label fee.', ['%label' => $this->entity->label()])); + $this->postSave($this->entity, $this->operation); + $this->messenger()->addMessage($this->t('Saved the %label fee.', ['%label' => $this->entity->label()])); $form_state->setRedirect('entity.commerce_fee.collection'); } diff --git a/src/Plugin/Commerce/Fee/OrderFixedAmount.php b/src/Plugin/Commerce/Fee/OrderFixedAmount.php index d92ae6b..7d37d22 100755 --- a/src/Plugin/Commerce/Fee/OrderFixedAmount.php +++ b/src/Plugin/Commerce/Fee/OrderFixedAmount.php @@ -2,8 +2,8 @@ namespace Drupal\commerce_fee\Plugin\Commerce\Fee; -use Drupal\commerce_fee\Entity\FeeInterface; use Drupal\commerce_order\Adjustment; +use Drupal\commerce_fee\Entity\FeeInterface; use Drupal\Core\Entity\EntityInterface; /** @@ -28,9 +28,9 @@ public function apply(EntityInterface $entity, FeeInterface $fee) { $this->assertEntity($entity); /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ $order = $entity; - $subtotal_price = $order->getSubTotalPrice(); + $total_price = $order->getTotalPrice(); $amount = $this->getAmount(); - if ($subtotal_price->getCurrencyCode() != $amount->getCurrencyCode()) { + if ($total_price->getCurrencyCode() != $amount->getCurrencyCode()) { return; } // Split the amount between order items. @@ -40,8 +40,7 @@ public function apply(EntityInterface $entity, FeeInterface $fee) { if (isset($amounts[$order_item->id()])) { $order_item->addAdjustment(new Adjustment([ 'type' => 'fee', - // @todo Change to label from UI when added in #2770731. - 'label' => t('Fee'), + 'label' => $fee->getDisplayName() ?: $this->t('Fee'), 'amount' => $amounts[$order_item->id()], 'source_id' => $fee->id(), ])); diff --git a/src/Plugin/Commerce/Fee/OrderItemFeeBase.php b/src/Plugin/Commerce/Fee/OrderItemFeeBase.php index 987170b..b9041ff 100644 --- a/src/Plugin/Commerce/Fee/OrderItemFeeBase.php +++ b/src/Plugin/Commerce/Fee/OrderItemFeeBase.php @@ -76,4 +76,11 @@ public function setConditions(array $conditions) { return $this; } + /** + * {@inheritdoc} + */ + public function getConditionOperator() { + return 'OR'; + } + } diff --git a/src/Plugin/Commerce/Fee/OrderItemFeeInterface.php b/src/Plugin/Commerce/Fee/OrderItemFeeInterface.php index 0d68974..42aef20 100644 --- a/src/Plugin/Commerce/Fee/OrderItemFeeInterface.php +++ b/src/Plugin/Commerce/Fee/OrderItemFeeInterface.php @@ -28,4 +28,12 @@ public function getConditions(); */ public function setConditions(array $conditions); + /** + * Gets the condition operator. + * + * @return string + * The condition operator. Possible values: AND, OR. + */ + public function getConditionOperator(); + } diff --git a/src/Plugin/Commerce/Fee/OrderItemFixedAmount.php b/src/Plugin/Commerce/Fee/OrderItemFixedAmount.php index 40edd21..b8c91b2 100755 --- a/src/Plugin/Commerce/Fee/OrderItemFixedAmount.php +++ b/src/Plugin/Commerce/Fee/OrderItemFixedAmount.php @@ -26,9 +26,9 @@ public function apply(EntityInterface $entity, FeeInterface $fee) { $this->assertEntity($entity); /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */ $order_item = $entity; - $total_price = $order_item->getTotalPrice(); + $adjusted_total_price = $order_item->getAdjustedTotalPrice(['fee']); $amount = $this->getAmount(); - if ($total_price->getCurrencyCode() != $amount->getCurrencyCode()) { + if ($adjusted_total_price->getCurrencyCode() != $amount->getCurrencyCode()) { return; } $adjustment_amount = $amount->multiply($order_item->getQuantity()); @@ -36,8 +36,7 @@ public function apply(EntityInterface $entity, FeeInterface $fee) { $order_item->addAdjustment(new Adjustment([ 'type' => 'fee', - // @todo Change to label from UI when added in #2770731. - 'label' => t('Fee'), + 'label' => $fee->getDisplayName() ?: $this->t('Fee'), 'amount' => $adjustment_amount, 'source_id' => $fee->id(), ])); diff --git a/src/Plugin/Commerce/Fee/OrderItemPercentage.php b/src/Plugin/Commerce/Fee/OrderItemPercentage.php index 80b244a..6c42fc8 100755 --- a/src/Plugin/Commerce/Fee/OrderItemPercentage.php +++ b/src/Plugin/Commerce/Fee/OrderItemPercentage.php @@ -32,8 +32,7 @@ public function apply(EntityInterface $entity, FeeInterface $fee) { $order_item->addAdjustment(new Adjustment([ 'type' => 'fee', - // @todo Change to label from UI when added in #2770731. - 'label' => t('Fee'), + 'label' => $fee->getDisplayName() ?: $this->t('Fee'), 'amount' => $adjustment_amount, 'percentage' => $percentage, 'source_id' => $fee->id(), diff --git a/src/Plugin/Commerce/Fee/OrderPercentage.php b/src/Plugin/Commerce/Fee/OrderPercentage.php index cf2b963..4a93441 100755 --- a/src/Plugin/Commerce/Fee/OrderPercentage.php +++ b/src/Plugin/Commerce/Fee/OrderPercentage.php @@ -2,8 +2,8 @@ namespace Drupal\commerce_fee\Plugin\Commerce\Fee; -use Drupal\commerce_fee\Entity\FeeInterface; use Drupal\commerce_order\Adjustment; +use Drupal\commerce_fee\Entity\FeeInterface; use Drupal\Core\Entity\EntityInterface; /** @@ -38,8 +38,7 @@ public function apply(EntityInterface $entity, FeeInterface $fee) { if (isset($amounts[$order_item->id()])) { $order_item->addAdjustment(new Adjustment([ 'type' => 'fee', - // @todo Change to label from UI when added in #2770731. - 'label' => t('Fee'), + 'label' => $promotion->getDisplayName() ?: $this->t('Fee'), 'amount' => $amounts[$order_item->id()], 'percentage' => $percentage, 'source_id' => $fee->id(), diff --git a/tests/src/FunctionalJavascript/FeeTest.php b/tests/src/FunctionalJavascript/FeeTest.php index ae05a31..faeb6fb 100755 --- a/tests/src/FunctionalJavascript/FeeTest.php +++ b/tests/src/FunctionalJavascript/FeeTest.php @@ -45,8 +45,10 @@ public function testCreateFee() { // Check the integrity of the form. $this->assertSession()->fieldExists('name[0][value]'); + $this->assertSession()->fieldExists('display_name[0][value]'); $name = $this->randomMachineName(8); $this->getSession()->getPage()->fillField('name[0][value]', $name); + $this->getSession()->getPage()->fillField('display_name[0][value]', 'Fee'); $this->getSession()->getPage()->selectFieldOption('plugin[0][target_plugin_id]', 'order_item_percentage'); $this->waitForAjaxToFinish(); $this->getSession()->getPage()->fillField('plugin[0][target_plugin_configuration][order_item_percentage][percentage]', '10.0'); @@ -74,7 +76,10 @@ public function testCreateFee() { $fee_count = $this->getSession()->getPage()->find('xpath', '//table/tbody/tr/td[text()="' . $name . '"]'); $this->assertEquals(count($fee_count), 1, 'fees exists in the table.'); + /** @var \Drupal\commerce_fee\Entity\FeeInterface $fee */ $fee = Fee::load(1); + $this->assertEquals($name, $fee->getName()); + $this->assertEquals('Discount', $fee->getDisplayName()); /** @var \Drupal\commerce\Plugin\Field\FieldType\PluginItem $plugin_field */ $plugin_field = $fee->get('plugin')->first(); $this->assertEquals('0.10', $plugin_field->target_plugin_configuration['percentage']); @@ -118,6 +123,28 @@ public function testCreateFeeWithEndDate() { $this->assertEquals('0.10', $plugin_field->target_plugin_configuration['percentage']); } + /** + * Tests updating the fee type when creating a promotion. + */ + public function testCreateFeeTypeSelection() { + $this->drupalGet('admin/commerce/fees'); + $this->clickLink('Add fee'); + + $fee_config_xpath = '//div[@data-drupal-selector="edit-fee-0-target-plugin-configuration"]'; + $fee_config_container = $this->xpath($fee_config_xpath); + $this->assertEmpty($fee_config_container); + + $this->getSession()->getPage()->selectFieldOption('fee[0][target_plugin_id]', 'order_item_percentage'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $fee_config_container = $this->xpath($fee_config_xpath); + $this->assertNotEmpty($fee_config_container); + + $this->getSession()->getPage()->selectFieldOption('fee[0][target_plugin_id]', ''); + $this->assertSession()->assertWaitOnAjaxRequest(); + $fee_config_container = $this->xpath($fee_config_xpath); + $this->assertEmpty($fee_config_container); + } + /** * Tests editing a fee. */ diff --git a/tests/src/Kernel/Entity/FeeTest.php b/tests/src/Kernel/Entity/FeeTest.php index c700650..865aeca 100755 --- a/tests/src/Kernel/Entity/FeeTest.php +++ b/tests/src/Kernel/Entity/FeeTest.php @@ -59,6 +59,8 @@ protected function setUp() { /** * @covers ::getName * @covers ::setName + * @covers ::getDisplayName + * @covers ::setDisplayName * @covers ::getDescription * @covers ::setDescription * @covers ::getOrderTypes @@ -89,6 +91,9 @@ public function testFee() { $fee->setName('My Fee'); $this->assertEquals('My Fee', $fee->getName()); + $fee->setDisplayName('50%'); + $this->assertEquals('50%', $fee->getDisplayName()); + $fee->setDescription('My Fee Description'); $this->assertEquals('My Fee Description', $fee->getDescription()); diff --git a/tests/src/Kernel/FeeCartTest.php b/tests/src/Kernel/FeeCartTest.php index acbd913..75b7d23 100755 --- a/tests/src/Kernel/FeeCartTest.php +++ b/tests/src/Kernel/FeeCartTest.php @@ -6,31 +6,14 @@ use Drupal\commerce_price\Price; use Drupal\commerce_product\Entity\Product; use Drupal\commerce_product\Entity\ProductVariation; -use Drupal\Tests\commerce_cart\Kernel\CartManagerTestTrait; -use Drupal\Tests\commerce\Kernel\CommerceKernelTestBase; +use Drupal\Tests\commerce_cart\Kernel\CartKernelTestBase; /** * Tests the integration between fees and carts. * * @group commerce */ -class FeeCartTest extends CommerceKernelTestBase { - - use CartManagerTestTrait; - - /** - * The cart manager. - * - * @var \Drupal\commerce_cart\CartManager - */ - protected $cartManager; - - /** - * The cart provider. - * - * @var \Drupal\commerce_cart\CartProvider - */ - protected $cartProvider; +class FeeCartTest extends CartKernelTestBase { /** * Modules to enable. @@ -71,8 +54,6 @@ protected function setUp() { * Tests adding a product with a fee to the cart. */ public function testFeeCart() { - $this->installCommerceCart(); - $variation = ProductVariation::create([ 'type' => 'default', 'sku' => strtolower($this->randomMachineName()), diff --git a/tests/src/Kernel/FeeOrderProcessorTest.php b/tests/src/Kernel/FeeOrderProcessorTest.php index f8ba176..7cdaf9d 100755 --- a/tests/src/Kernel/FeeOrderProcessorTest.php +++ b/tests/src/Kernel/FeeOrderProcessorTest.php @@ -64,7 +64,7 @@ protected function setUp() { $this->order = Order::create([ 'type' => 'default', - 'state' => 'completed', + 'state' => 'draft', 'mail' => 'test@example.com', 'ip_address' => '127.0.0.1', 'order_number' => '6', diff --git a/tests/src/Kernel/FeeStorageTest.php b/tests/src/Kernel/FeeStorageTest.php index f262316..5e49445 100755 --- a/tests/src/Kernel/FeeStorageTest.php +++ b/tests/src/Kernel/FeeStorageTest.php @@ -99,6 +99,16 @@ public function testLoadAvailable() { 'name' => 'Fee 1', 'order_types' => [$this->orderType], 'stores' => [$this->store->id()], + 'fee' => [ + 'target_plugin_id' => 'order_fixed_amount', + 'target_plugin_configuration' => [ + 'amount' => [ + 'number' => '25.00', + 'currency_code' => 'USD', + ], + ], + ], + 'start_date' => '2019-11-15T10:14:00', 'status' => TRUE, ]); $this->assertEquals(SAVED_NEW, $fee1->save()); @@ -109,6 +119,13 @@ public function testLoadAvailable() { 'name' => 'Fee 2', 'order_types' => [$this->orderType], 'stores' => [$this->store->id()], + 'fee' => [ + 'target_plugin_id' => 'order_percentage', + 'target_plugin_configuration' => [ + 'percentage' => '0.20', + ], + ], + 'start_date' => '2019-01-01T00:00:00', 'status' => FALSE, ]); $this->assertEquals(SAVED_NEW, $fee2->save()); @@ -117,8 +134,14 @@ public function testLoadAvailable() { 'name' => 'Fee 3', 'order_types' => [$this->orderType], 'stores' => [$this->store->id()], + 'fee' => [ + 'target_plugin_id' => 'order_percentage', + 'target_plugin_configuration' => [ + 'percentage' => '0.30', + ], + ], + 'start_date' => '2014-01-01T00:00:00', 'status' => TRUE, - 'start_date' => '2014-01-01T20:00:00Z', ]); $this->assertEquals(SAVED_NEW, $fee3->save()); // Start in 1 week, end in 1 year. Enabled. @@ -127,8 +150,15 @@ public function testLoadAvailable() { 'order_types' => [$this->orderType], 'stores' => [$this->store->id()], 'status' => TRUE, - 'start_date' => gmdate('Y-m-d', time() + 604800), - 'end_date' => gmdate('Y-m-d', time() + 31536000), + 'fee' => [ + 'target_plugin_id' => 'order_percentage', + 'target_plugin_configuration' => [ + 'percentage' => '0.40', + ], + ], + 'start_date' => '2019-01-01T00:00:00', + 'end_date' => '2019-11-15T10:14:00', + 'status' => TRUE, ]); $this->assertEquals(SAVED_NEW, $fee4->save());