-
Notifications
You must be signed in to change notification settings - Fork 256
2770757 implement promotion conditions #475
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's a level they'd never see.
Possibly.
That's all TBD, but true. I'm sure next validation round the conditions would fail and it'd be removed. Not our problem There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
@@ -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.')) | ||
|
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.