Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion modules/order/src/Entity/LineItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public function setTitle($title) {
* {@inheritdoc}
*/
public function getQuantity() {
return $this->get('quantity')->value;
return (string) $this->get('quantity')->value;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bojanz so should we change the field type to be string, or store it as decimal and keep this cast?

Copy link
Contributor

Choose a reason for hiding this comment

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

It would be more proper to change a field type, but that will probably require providing a custom widget. We can go with the cast and open a followup issue.

}

/**
Expand Down
3 changes: 3 additions & 0 deletions modules/promotion/commerce_promotion.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ services:
plugin.manager.commerce_promotion_offer:
class: Drupal\commerce_promotion\PromotionOfferManager
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@entity_type.manager']
plugin.manager.commerce_promotion_condition:
class: Drupal\commerce_promotion\PromotionConditionManager
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@entity_type.manager']
25 changes: 25 additions & 0 deletions modules/promotion/src/Annotation/CommercePromotionCondition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Drupal\commerce_promotion\Annotation;

use Drupal\Core\Condition\Annotation\Condition;

/**
* Defines the promotion condition plugin annotation object.
*
* Plugin namespace: Plugin\Commerce\PromotionCondition.
*
* @Annotation
*/
class CommercePromotionCondition extends Condition {

/**
* The target entity type this action applies to.
*
* For example, this should be 'commerce_order' or 'commerce_line_item'.
*
* @var string
*/
public $target_entity_type;

}
32 changes: 32 additions & 0 deletions modules/promotion/src/Entity/Promotion.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,36 @@ public function setEnabled($enabled) {
return $this;
}

/**
* {@inheritdoc}
*/
public function applies(EntityInterface $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);

// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would expect the conditions to be evaluated in another method, like applies(), and I would expect apply() to always work. I don't think the API consumers would blindly try to apply promotions without checking if they apply first?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense.. so you could choose to explicitly apply a promotion regardless. applies and apply are two methods, validation should be done by end implementer.

Choose a reason for hiding this comment

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

If a promotion applies that doesn't match the rules, that is going to confuse the hell out of any admins though. If a API user wants to apply a promotion outside of conditions, wouldn't it be better for them to make a new promotion? Also any re-validation of the order based on a change would cause it to be dumped? either way it would be confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If a promotion applies that doesn't match the rules, that is going to confuse the hell out of any admins though. I

That's a level they'd never see.

If a API user wants to apply a promotion outside of conditions, wouldn't it be better for them to make a new promotion

Possibly.

Also any re-validation of the order based on a change would cause it to be dumped

That's all TBD, but true. I'm sure next validation round the conditions would fail and it'd be removed. Not our problem

Choose a reason for hiding this comment

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

Whats the point of splitting it up though then? aren't we just allowing an unnecessarily dangerous thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True. What if we wanted to check if a promotion "just applies" We'd want to have applies then. Should apply also then wrap the applies method?

Either way, no one should be loading a promotion directly and running these. There will be a service you pass an entity to, and that entity is processed.

Choose a reason for hiding this comment

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

Hmm yah, there should be an applies stand alone, lots of use cases for that. I think apply should include it, but you're right, it is only for preventing someone from doing something stupid.

/** @var \Drupal\commerce_promotion\Plugin\Commerce\PromotionCondition\PromotionConditionInterface $condition */
$condition = $item->getTargetInstance([$entity_type_id => $context]);
if (!$condition->evaluate()) {
return FALSE;
}
}

return TRUE;
}

/**
* {@inheritdoc}
*/
public function apply(EntityInterface $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->execute();
Expand Down Expand Up @@ -320,6 +343,15 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
'weight' => 3,
]);

$fields['conditions'] = BaseFieldDefinition::create('commerce_plugin_item:commerce_promotion_condition')
->setLabel(t('Conditions'))
->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
->setRequired(FALSE)
->setDisplayOptions('form', [
'type' => 'commerce_plugin_select',
'weight' => 3,
]);

$fields['coupons'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Coupons'))
->setDescription(t('Coupons which allow promotion to be redeemed.'))
Expand Down
13 changes: 12 additions & 1 deletion modules/promotion/src/Entity/PromotionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,18 @@ public function isEnabled();
public function setEnabled($enabled);

/**
* Applies the promotion to an entity.
* Checks whether the promotion entity can be applied.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*
* @return bool
* TRUE if promotion can be applied, or false if conditions failed.
*/
public function applies(EntityInterface $entity);

/**
* Apply the promotion to an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace Drupal\commerce_promotion\Plugin\Commerce\PromotionCondition;

use Drupal\commerce_price\Price;
use Drupal\Core\Form\FormStateInterface;

/**
* Provides an 'Order: Total amount comparison' condition.
*
* @CommercePromotionCondition(
* id = "commerce_promotion_order_total_price",
* label = @Translation("Total amount"),
* target_entity_type = "commerce_order",
* )
*/
class OrderTotalPrice extends PromotionConditionBase {

/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'amount' => NULL,
// @todo expose the operator in form.
'operator' => '>',
] + parent::defaultConfiguration();
}

/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form += parent::buildConfigurationForm($form, $form_state);

$default_price = NULL;
if (!empty($this->configuration['amount']['amount'])) {
$default_price = new Price($this->configuration['amount']['amount'], $this->configuration['amount']['currency_code']);
}

$form['amount'] = [
'#type' => 'commerce_price',
'#title' => t('Amount'),
'#default_value' => $default_price,
'#required' => TRUE,
];

return $form;
}

/**
* {@inheritdoc}
*/
public function evaluate() {
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $this->getTargetEntity();
/** @var \Drupal\commerce_price\Price $total_price */
$total_price = $order->getTotalPrice();
/** @var \Drupal\commerce_price\Price $comparison_price */
$comparison_price = $this->configuration['amount'];

switch ($this->configuration['operator']) {
case '==':
return $total_price->equals($comparison_price);

case '>=':
return $total_price->greaterThanOrEqual($comparison_price);

case '>':
return $total_price->greaterThan($comparison_price);

case '<=':
return $total_price->lessThanOrEqual($comparison_price);

case '<':
return $total_price->lessThan($comparison_price);

default:
throw new \InvalidArgumentException("Invalid operator {$this->configuration['operator']}");
}
}

/**
* {@inheritdoc}
*/
public function summary() {
return $this->t('Compares the order total amount.');
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Drupal\commerce_promotion\Plugin\Commerce\PromotionCondition;

use Drupal\Core\Condition\ConditionPluginBase;

/**
* Base class for Promotion Condition plugins.
*/
abstract class PromotionConditionBase extends ConditionPluginBase implements PromotionConditionInterface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needs docblock.


/**
* {@inheritdoc}
*/
public function getTargetEntityType() {
return $this->pluginDefinition['target_entity_type'];
}

/**
* {@inheritdoc}
*/
public function getTargetEntity() {
return $this->getContextValue($this->getTargetEntityType());
}

/**
* {@inheritdoc}
*/
public function execute() {
$result = $this->evaluate();
return $this->isNegated() ? !$result : $result;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Drupal\commerce_promotion\Plugin\Commerce\PromotionCondition;

use Drupal\Core\Condition\ConditionInterface;

/**
* Defines an interface for Condition plugins.
*/
interface PromotionConditionInterface extends ConditionInterface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needs docblock


/**
* Gets the entity type the condition is for.
*
* @return string
* The entity type it applies to.
*/
public function getTargetEntityType();

/**
* Get the target entity for the offer.
*
* @return \Drupal\Core\Entity\EntityInterface
* The target entity.
*/
public function getTargetEntity();

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ProductPercentageOff extends PercentageOffBase {
public function execute() {
/** @var \Drupal\commerce_order\Entity\LineItemInterface $line_item */
$line_item = $this->getTargetEntity();
$price_amount = $line_item->getTotalPrice()->multiply($this->getAmount());
$price_amount = $line_item->getUnitPrice()->multiply($this->getAmount());
$this->applyAdjustment($line_item, $price_amount);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
/**
* {@inheritdoc}
*/
public function appliesTo() {
public function getTargetEntityType() {
return $this->pluginDefinition['target_entity_type'];
}

/**
* {@inheritdoc}
*/
public function getTargetEntity() {
return $this->getContextValue($this->appliesTo());
return $this->getContextValue($this->getTargetEntityType());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ interface PromotionOfferInterface extends ExecutableInterface, PluginFormInterfa
const LINE_ITEM = 'commerce_line_item';

/**
* Returns what entity the promotion offer applies to.
* Gets the entity type the offer is for.
*
* @return string
* The entity type it applies to.
*/
public function appliesTo();
public function getTargetEntityType();

/**
* Get the target entity for the offer.
Expand Down
Loading