contacts_events-8.x-1.x-dev/src/OrderItemStateTrait.php
src/OrderItemStateTrait.php
<?php
namespace Drupal\contacts_events;
use Drupal\commerce_order\Entity\OrderItemInterface;
use Drupal\commerce_partial_payments\OrderItemTrackingInterface;
use Drupal\state_machine\Plugin\Workflow\WorkflowState;
/**
* Trait for common order item state checks and processes.
*/
trait OrderItemStateTrait {
/**
* The order item tracking service, if available.
*
* @var \Drupal\commerce_partial_payments\OrderItemTrackingInterface|null
*/
protected $orderItemTracking;
/**
* Set the order item tracking service.
*
* @param \Drupal\commerce_partial_payments\OrderItemTrackingInterface $tracking
* The tracking service.
*
* @return $this
*/
public function setOrderItemTracking(OrderItemTrackingInterface $tracking) {
$this->orderItemTracking = $tracking;
return $this;
}
/**
* Check the status of an order item, applying a transition as required.
*
* @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
* The order item.
*
* @return bool
* Whether the state was changed.
*/
protected function checkOrderItemStatus(OrderItemInterface $order_item): bool {
if (!$order_item->hasField('state')) {
return FALSE;
}
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state */
$state = $order_item->get('state')->first();
// Never automatically confirm or un-cancel an order item.
if (in_array($state->value, ['pending', 'cancelled'])) {
return FALSE;
}
// We can't do anything if the item doesn't have an order.
$order = $order_item->getOrder();
if (!$order) {
return FALSE;
}
// Avoid running expensive database queries if there are no possible
// transitions by checking the transitions first.
$transitions = $state->getWorkflow()->getAllowedTransitions($state->getOriginalId() ?? $state->getId(), $order_item);
$paid_transition = $unpaid_transition = FALSE;
foreach ($transitions as $transition) {
// Always skip any transition to pending or cancelled.
// phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
if (in_array($transition->getToState()->getId(), ['pending', 'cancelled'])) {
continue;
}
// See if this is a transition to paid in full.
if ($transition->getToState()->getId() == 'paid_in_full') {
$paid_transition = $transition->getId();
}
// Otherwise, see if it's a transition away from paid in full.
else {
$from_states = array_map(function (WorkflowState $state) {
return $state->getId();
}, $transition->getFromStates());
if (in_array('paid_in_full', $from_states)) {
$unpaid_transition = $transition->getId();
}
}
// If we have both transitions, break.
if ($paid_transition && $unpaid_transition) {
break;
}
}
// If we don't have either transition, return now.
if (!$paid_transition && !$unpaid_transition) {
return FALSE;
}
// Now we need to check whether the item is paid in full.
$transition = $this->isOrderItemPaidInFull($order_item) ? $paid_transition : $unpaid_transition;
// If there is no transition, there's nothing to do.
if (!$transition) {
return FALSE;
}
// Make sure that the sate item is back in its original state before
// applying the transition.
if ($state->getOriginalId()) {
$state->value = $state->getOriginalId();
}
// Otherwise apply it and indicate we've made a change.
$state->applyTransitionById($transition);
return TRUE;
}
/**
* Check if an order item is paid in full.
*
* @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
* The order item.
*
* @return bool
* Whether the order item is paid in full.
*/
protected function isOrderItemPaidInFull(OrderItemInterface $order_item): bool {
// Start simply by seeing if the order itself is paid in full.
$order_balance = $order_item->getOrder()->getBalance();
if (!$order_balance || $order_balance->isZero() || $order_balance->isNegative()) {
return TRUE;
}
// If we have order item tracking, we can check the specific item.
elseif ($this->orderItemTracking) {
$total = $order_item->getAdjustedTotalPrice();
// Zero or negative value will always be paid in full.
if (!$total || $total->isZero() || $total->isNegative()) {
return TRUE;
}
// Otherwise, look up the tracked amount.
else {
$tracking = $this->orderItemTracking->getTrackedAmountsForItem($order_item->id());
return $tracking && $tracking->greaterThanOrEqual($total);
}
}
// Otherwise, we must assume it's not paid in full.
return FALSE;
}
}
