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 24, 2017
1 parent d82e7e9 commit b4009d1
Show file tree
Hide file tree
Showing 12 changed files with 556 additions and 12 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
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');
}
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 }
11 changes: 6 additions & 5 deletions modules/promotion/src/Entity/Promotion.php
Expand Up @@ -2,6 +2,7 @@

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;
Expand Down Expand Up @@ -347,13 +348,13 @@ public function applies(EntityInterface $entity) {
/**
* {@inheritdoc}
*/
public function apply(EntityInterface $entity) {
public function apply(EntityAdjustableInterface $entity) {
$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),
$this->entityTypeId => new Context(new ContextDefinition('entity:' . $this->entityTypeId), $this),
]);
$offer->execute();
}

Expand Down
10 changes: 5 additions & 5 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 @@ -247,20 +247,20 @@ 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.
*
* @return bool
* TRUE if promotion can be applied, or false if conditions failed.
*/
public function applies(EntityInterface $entity);
public function applies(EntityAdjustableInterface $entity);

/**
* Apply the promotion to an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* @param \Drupal\commerce_order\EntityAdjustableInterface $entity
* The entity.
*/
public function apply(EntityInterface $entity);
public function apply(EntityAdjustableInterface $entity);

}
79 changes: 79 additions & 0 deletions modules/promotion/src/EventSubscriber/OrderEventSubscriber.php
@@ -0,0 +1,79 @@
<?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 promotion usage.
*
* @var \Drupal\commerce_promotion\PromotionUsageInterface
*/
protected $usage;

/**
* Constructs a new OrderEventSubscriber object.
*
* @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->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();

// @todo The offer plugin sets the adjustment. So we have no way of knowing
// the source promotion or coupon for an adjustment being applied. Also, with
// the current \Drupal\commerce_promotion\Entity\PromotionInterface::apply
// method there is know way to know it was applied by a coupon.
foreach ($adjustments as $adjustment) {
if ($adjustment->getType() == 'promotion') {
$promotion = $this->promotionStorage->load($adjustment->getSourceId());
$this->usage->addPromotionUsage($promotion, $order);
}
}

/** @var \Drupal\commerce_promotion\Entity\CouponInterface[] $coupons */
$coupons = $order->get('coupons')->referencedEntities();
foreach ($coupons as $coupon) {
$this->usage->addCouponUsage($coupon, $order);
}
}

}
Expand Up @@ -74,12 +74,15 @@ public function getTargetEntity() {
* {@inheritdoc}
*/
public function applyAdjustment(EntityAdjustableInterface $entity, Price $amount) {
/** @var \Drupal\commerce_promotion\Entity\PromotionInterface $promotion */
$promotion = $this->getContextValue('commerce_promotion');

$entity->addAdjustment(new Adjustment([
'type' => 'promotion',
// @todo Change to label from UI when added in #2770731.
'label' => t('Discount'),
'amount' => $amount->multiply('-1'),
'source_id' => $this->getPluginId(),
'source_id' => $promotion->id(),
]));
}

Expand Down
19 changes: 19 additions & 0 deletions modules/promotion/src/PromotionConditionManager.php
Expand Up @@ -62,6 +62,22 @@ public function execute(ExecutableInterface $condition) {
return $condition->isNegated() ? !$result : $result;
}

/**
* {@inheritdoc}
*/
public function createInstance($plugin_id, array $configuration = []) {
$plugin = $this->getFactory()->createInstance($plugin_id, $configuration);

// If we receive any context values via config set it into the plugin.
if (!empty($configuration['context'])) {
foreach ($configuration['context'] as $name => $context) {
$plugin->setContextValue($name, $context);
}
}

return $plugin;
}

/**
* {@inheritdoc}
*/
Expand All @@ -84,6 +100,9 @@ public function processDefinition(&$definition, $plugin_id) {
$definition['category'] = $this->entityTypeManager->getDefinition($target)->getLabel();
}

// Add a context definition for the promotion being executed.
$definition['context']['commerce_promotion'] = new ContextDefinition('entity:commerce_promotion', $this->t('Promotion'));

// Generate the context definition if it is missing.
if (empty($definition['context'][$target])) {
$definition['context'][$target] = new ContextDefinition('entity:' . $target, $definition['category']);
Expand Down
3 changes: 3 additions & 0 deletions modules/promotion/src/PromotionOfferManager.php
Expand Up @@ -84,6 +84,9 @@ public function processDefinition(&$definition, $plugin_id) {
$definition['category'] = $this->entityTypeManager->getDefinition($target)->getLabel();
}

// Add a context definition for the promotion being executed.
$definition['context']['commerce_promotion'] = new ContextDefinition('entity:commerce_promotion', $this->t('Promotion'));

// Generate the context definition if it is missing.
if (empty($definition['context'][$target])) {
$definition['context'][$target] = new ContextDefinition('entity:' . $target, $definition['category']);
Expand Down
81 changes: 81 additions & 0 deletions modules/promotion/src/PromotionUsage.php
@@ -0,0 +1,81 @@
<?php

namespace Drupal\commerce_promotion;

use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_promotion\Entity\CouponInterface;
use Drupal\commerce_promotion\Entity\PromotionInterface;
use Drupal\Core\Database\Connection;

class PromotionUsage implements PromotionUsageInterface {

/**
* The database connection to use.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;

/**
* Constructs a PromotionUsage object.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection to use.
*/
public function __construct(Connection $connection) {
$this->connection = $connection;
}

/**
* {@inheritdoc}
*/
public function getPromotionUsage(PromotionInterface $promotion) {
$count = $this->connection->select('commerce_promotion_usage', 'cpu')
->condition('promotion_id', $promotion->id())
->countQuery()
->execute()
->fetchField();
return $count;
}

/**
* {@inheritdoc}
*/
public function addPromotionUsage(PromotionInterface $promotion, OrderInterface $order) {
$this->connection->insert('commerce_promotion_usage')
->fields([
'promotion_id' => $promotion->id(),
'order_id' => $order->id(),
'uid' => $order->getCustomerId(),
])
->execute();
}

/**
* {@inheritdoc}
*/
public function getCouponUsage(CouponInterface $coupon) {
$count = $this->connection->select('commerce_promotion_usage', 'cpu')
->condition('coupon_id', $coupon->id())
->countQuery()
->execute()
->fetchField();
return $count;
}

/**
* {@inheritdoc}
*/
public function addCouponUsage(CouponInterface $coupon, OrderInterface $order) {
$this->connection->upsert('commerce_promotion_usage')
->key(['promotion_id' => $coupon->getPromotionId(), 'order_id' => $order->id(), 'uid' => $order->getCustomerId()])
->fields([
'promotion_id' => $coupon->getPromotionId(),
'coupon_id' => $coupon->id(),
'order_id' => $order->id(),
'uid' => $order->getCustomerId(),
])
->execute();
}

}

0 comments on commit b4009d1

Please sign in to comment.