Skip to content

Commit

Permalink
Issue #2763705 by mglaman: Implement a usage API
Browse files Browse the repository at this point in the history
  • Loading branch information
mglaman committed Mar 25, 2017
1 parent f488a12 commit 5f48c6d
Show file tree
Hide file tree
Showing 22 changed files with 615 additions and 68 deletions.
3 changes: 2 additions & 1 deletion modules/product/src/ProductVariationStorageInterface.php
Expand Up @@ -3,11 +3,12 @@
namespace Drupal\commerce_product;

use Drupal\commerce_product\Entity\ProductInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;

/**
* Defines the interface for product variation storage.
*/
interface ProductVariationStorageInterface {
interface ProductVariationStorageInterface extends ContentEntityStorageInterface {

/**
* Loads the variation from context.
Expand Down
Expand Up @@ -2,3 +2,7 @@ promotion:
label: Promotion
has_ui: true
weight: 0
promotion_coupon:
label: Coupon
has_ui: true
weight: 0
65 changes: 65 additions & 0 deletions modules/promotion/commerce_promotion.install
@@ -0,0 +1,65 @@
<?php

/**
* @file
* Install, update and uninstall functions for the commerce_promotion module.
*/

/**
* Implements hook_schema().
*/
function commerce_promotion_schema() {
$schema['commerce_promotion_usage'] = [
'description' => 'Stores module data as key/value pairs per user.',
'fields' => [
'promotion_id' => [
'description' => 'Primary key: {commerce_promotion}.promotion_id for promotion.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
],
'coupon_id' => [
'description' => 'Primary key: {commerce_promotion_coupon}.id for coupon.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
'default' => 0,
],
'order_id' => [
'description' => 'Primary key: {commerce_order}.order_id for order.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
],
'uid' => [
'description' => 'Primary key: {users}.uid for user.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
'default' => 0,
],
],
'primary key' => ['promotion_id', 'coupon_id', 'order_id'],
'indexes' => [
'promotion_id' => ['promotion_id'],
'coupon_id' => ['coupon_id'],
],
'foreign keys' => [
'promotion_id' => ['commerce_promotion' => 'promotion_id'],
'coupon_id' => ['commerce_promotion_coupon' => 'id'],
'order_id' => ['commerce_order' => 'order_id'],
'uid' => ['users' => 'uid'],
],
];

return $schema;
}

/**
* Install the `commerce_promotion_usage` table schema.
*/
function commerce_promotion_update_8001() {
drupal_install_schema('commerce_promotion');
}
14 changes: 14 additions & 0 deletions modules/promotion/commerce_promotion.post_update.php
Expand Up @@ -56,3 +56,17 @@ function commerce_promotion_post_update_3() {
}
$coupon_storage->delete($delete_coupons);
}

/**
* Remove promotion current_usage field.
*/
function commerce_promotion_post_update_4() {
$entity_definition_update = \Drupal::entityDefinitionUpdateManager();
$storage_definition = BaseFieldDefinition::create('integer')
->setName('current_usage')
->setTargetEntityTypeId('commerce_promotion')
->setLabel(t('Current usage'))
->setDescription(t('The number of times the promotion was used.'))
->setDefaultValue(0);
$entity_definition_update->uninstallFieldStorageDefinition($storage_definition);
}
12 changes: 12 additions & 0 deletions modules/promotion/commerce_promotion.services.yml
Expand Up @@ -17,3 +17,15 @@ services:
arguments: ['@entity_type.manager']
tags:
- { name: commerce_order.order_processor, priority: 50 }

commerce_promotion.usage:
class: Drupal\commerce_promotion\PromotionUsage
arguments: ['@database']
tags:
- { name: backend_overridable }

commerce_promotion.order_subscriber:
class: Drupal\commerce_promotion\EventSubscriber\OrderEventSubscriber
arguments: ['@entity_type.manager', '@commerce_promotion.usage']
tags:
- { name: event_subscriber }
4 changes: 2 additions & 2 deletions modules/promotion/src/CouponOrderProcessor.php
Expand Up @@ -74,13 +74,13 @@ public function process(OrderInterface $order) {
$target_entity_type = $plugin->getTargetEntityType();
if ($target_entity_type == PromotionOfferInterface::ORDER) {
if ($promotion->applies($order)) {
$promotion->apply($order);
$promotion->apply($order, $coupon);
}
}
elseif ($target_entity_type == PromotionOfferInterface::ORDER_ITEM) {
foreach ($order->getItems() as $order_item) {
if ($promotion->applies($order_item)) {
$promotion->apply($order_item);
$promotion->apply($order_item, $coupon);
}
}
}
Expand Down
44 changes: 13 additions & 31 deletions modules/promotion/src/Entity/Promotion.php
Expand Up @@ -2,9 +2,9 @@

namespace Drupal\commerce_promotion\Entity;

use Drupal\commerce_order\EntityAdjustableInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
Expand Down Expand Up @@ -240,21 +240,6 @@ protected function getCouponIndex(CouponInterface $coupon) {
return array_search($coupon->id(), $this->getCouponIds());
}

/**
* {@inheritdoc}
*/
public function getCurrentUsage() {
return $this->get('current_usage')->value;
}

/**
* {@inheritdoc}
*/
public function setCurrentUsage($current_usage) {
$this->set('current_usage', $current_usage);
return $this;
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -318,7 +303,7 @@ public function setEnabled($enabled) {
/**
* {@inheritdoc}
*/
public function applies(EntityInterface $entity) {
public function applies(EntityAdjustableInterface $entity, CouponInterface $coupon = NULL) {
$entity_type_id = $entity->getEntityTypeId();

/** @var \Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer\PromotionOfferInterface $offer */
Expand All @@ -327,15 +312,16 @@ public function applies(EntityInterface $entity) {
return FALSE;
}

// @todo should whatever invokes this method be providing the context?
$context = new Context(new ContextDefinition('entity:' . $entity_type_id), $entity);

// Execute each plugin, this is an AND operation.
// @todo support OR operations.
/** @var \Drupal\commerce\Plugin\Field\FieldType\PluginItem $item */
foreach ($this->get('conditions') as $item) {
/** @var \Drupal\commerce_promotion\Plugin\Commerce\PromotionCondition\PromotionConditionInterface $condition */
$condition = $item->getTargetInstance([$entity_type_id => $context]);
$condition = $item->getTargetInstance([
$entity_type_id => new Context(new ContextDefinition('entity:' . $entity_type_id), $entity),
'commerce_promotion' => new Context(new ContextDefinition('entity:commerce_promotion'), $this),
'commerce_promotion_coupon' => new Context(new ContextDefinition('entity:commerce_promotion_coupon'), $coupon),
]);
if (!$condition->evaluate()) {
return FALSE;
}
Expand All @@ -347,13 +333,14 @@ public function applies(EntityInterface $entity) {
/**
* {@inheritdoc}
*/
public function apply(EntityInterface $entity) {
public function apply(EntityAdjustableInterface $entity, CouponInterface $coupon = NULL) {
$entity_type_id = $entity->getEntityTypeId();
// @todo should whatever invokes this method be providing the context?
$context = new Context(new ContextDefinition('entity:' . $entity_type_id), $entity);

/** @var \Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer\PromotionOfferInterface $offer */
$offer = $this->get('offer')->first()->getTargetInstance([$entity_type_id => $context]);
$offer = $this->get('offer')->first()->getTargetInstance([
$entity_type_id => new Context(new ContextDefinition('entity:' . $entity_type_id), $entity),
'commerce_promotion' => new Context(new ContextDefinition('entity:commerce_promotion'), $this),
'commerce_promotion_coupon' => new Context(new ContextDefinition('entity:commerce_promotion_coupon'), $coupon),
]);
$offer->execute();
}

Expand Down Expand Up @@ -489,11 +476,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
],
]);

$fields['current_usage'] = BaseFieldDefinition::create('integer')
->setLabel(t('Current usage'))
->setDescription(t('The number of times the promotion was used.'))
->setDefaultValue(0);

$fields['usage_limit'] = BaseFieldDefinition::create('integer')
->setLabel(t('Usage limit'))
->setDescription(t('The maximum number of times the promotion can be used. 0 for unlimited.'))
Expand Down
34 changes: 9 additions & 25 deletions modules/promotion/src/Entity/PromotionInterface.php
Expand Up @@ -2,10 +2,10 @@

namespace Drupal\commerce_promotion\Entity;

use Drupal\commerce_order\EntityAdjustableInterface;
use Drupal\commerce_store\Entity\EntityStoresInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;

/**
* Defines the interface for promotions.
Expand Down Expand Up @@ -149,26 +149,6 @@ public function removeCoupon(CouponInterface $coupon);
*/
public function hasCoupon(CouponInterface $coupon);

/**
* Gets the promotion current usage.
*
* Represents the number of times the promotion was used.
*
* @return int
* The promotion current usage.
*/
public function getCurrentUsage();

/**
* Sets the promotion current usage.
*
* @param int $current_usage
* The promotion current usage.
*
* @return $this
*/
public function setCurrentUsage($current_usage);

/**
* Gets the promotion usage limit.
*
Expand Down Expand Up @@ -247,20 +227,24 @@ public function setEnabled($enabled);
/**
* Checks whether the promotion entity can be applied.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* @param \Drupal\commerce_order\EntityAdjustableInterface $entity
* The entity.
* @param \Drupal\commerce_promotion\Entity\CouponInterface $coupon
* The promotion's coupon, if provided.
*
* @return bool
* TRUE if promotion can be applied, or false if conditions failed.
*/
public function applies(EntityInterface $entity);
public function applies(EntityAdjustableInterface $entity, CouponInterface $coupon = NULL);

/**
* Apply the promotion to an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* @param \Drupal\commerce_order\EntityAdjustableInterface $entity
* The entity.
* @param \Drupal\commerce_promotion\Entity\CouponInterface $coupon
* The promotion's coupon, if provided.
*/
public function apply(EntityInterface $entity);
public function apply(EntityAdjustableInterface $entity, CouponInterface $coupon = NULL);

}
84 changes: 84 additions & 0 deletions modules/promotion/src/EventSubscriber/OrderEventSubscriber.php
@@ -0,0 +1,84 @@
<?php

namespace Drupal\commerce_promotion\EventSubscriber;

use Drupal\commerce_promotion\PromotionUsageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\state_machine\Event\WorkflowTransitionEvent;

class OrderEventSubscriber implements EventSubscriberInterface {

/**
* The promotion storage.
*
* @var \Drupal\commerce_promotion\PromotionStorageInterface
*/
protected $promotionStorage;

/**
* The coupon storage.
*
* @var \Drupal\commerce_promotion\CouponStorageInterface
*/
protected $couponStorage;

/**
* The promotion usage.
*
* @var \Drupal\commerce_promotion\PromotionUsageInterface
*/
protected $usage;

/**
* Constructs a new OrderEventSubscriber object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\commerce_promotion\PromotionUsageInterface $usage
* The promotion usage.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, PromotionUsageInterface $usage) {
$this->promotionStorage = $entity_type_manager->getStorage('commerce_promotion');
$this->couponStorage = $entity_type_manager->getStorage(('commerce_promotion_coupon'));
$this->usage = $usage;
}

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = [
'commerce_order.place.pre_transition' => 'addUsage',
];
return $events;
}

/**
* Adds promotion usage when cart placed.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The workflow transition event.
*
* @todo Investigate moving to kernel terminate somehow, for performance.
*/
public function addUsage(WorkflowTransitionEvent $event) {
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $event->getEntity();
/** @var \Drupal\commerce_order\Adjustment[] $adjustments */
$adjustments = $order->collectAdjustments();

foreach ($adjustments as $adjustment) {
if ($adjustment->getType() == 'promotion') {
$promotion = $this->promotionStorage->load($adjustment->getSourceId());
$this->usage->addUsage($order, $promotion);
}
if ($adjustment->getType() == 'promotion_coupon') {
/** @var \Drupal\commerce_promotion\Entity\CouponInterface $coupon */
$coupon = $this->couponStorage->load($adjustment->getSourceId());
$this->usage->addUsage($order, $coupon->getPromotion(), $coupon);
}
}
}

}

0 comments on commit 5f48c6d

Please sign in to comment.