Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #2763705 by mglaman: Implement a usage API #695

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
64 changes: 64 additions & 0 deletions modules/promotion/commerce_promotion.install
@@ -0,0 +1,64 @@
<?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.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untrue.

'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.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of these are primary keys, right? We might as well drop the phrase.

'type' => 'int',
'unsigned' => TRUE,
'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 @@ -84,3 +84,17 @@ function commerce_promotion_post_update_4() {
$promotion->save();
}
}

/**
* Remove promotion current_usage field.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Remove the promotion current_usage field."
People will see the lack of an article and think a Slavic person wrote this :P

*/
function commerce_promotion_post_update_5() {
$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
42 changes: 12 additions & 30 deletions modules/promotion/src/Entity/Promotion.php
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 @@ -351,7 +336,7 @@ public function setWeight($weight) {
/**
* {@inheritdoc}
*/
public function applies(EntityAdjustableInterface $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 @@ -376,15 +361,16 @@ public function applies(EntityAdjustableInterface $entity) {
break;
}

// @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 @@ -396,13 +382,14 @@ public function applies(EntityAdjustableInterface $entity) {
/**
* {@inheritdoc}
*/
public function apply(EntityAdjustableInterface $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 @@ -538,11 +525,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
28 changes: 6 additions & 22 deletions modules/promotion/src/Entity/PromotionInterface.php
Expand Up @@ -152,26 +152,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 @@ -288,18 +268,22 @@ public function setWeight($weight);
*
* @param \Drupal\commerce_order\EntityAdjustableInterface $entity
* The adjustable 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(EntityAdjustableInterface $entity);
public function applies(EntityAdjustableInterface $entity, CouponInterface $coupon = NULL);

/**
* Apply the promotion to an entity.
*
* @param \Drupal\commerce_order\EntityAdjustableInterface $entity
* The adjustable entity.
* @param \Drupal\commerce_promotion\Entity\CouponInterface $coupon
* The promotion's coupon, if provided.
*/
public function apply(EntityAdjustableInterface $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);
}
}
}

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

$type = ($coupon) ? 'promotion_coupon' : 'promotion';
$source_id = ($coupon) ? $coupon->id() : $promotion->id();

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

Expand Down