contacts_events-8.x-1.x-dev/src/Entity/OrderItemBookkeepingHooks.php
src/Entity/OrderItemBookkeepingHooks.php
<?php
namespace Drupal\contacts_events\Entity;
use Drupal\bookkeeping\Plugin\Field\FieldType\BookkeepingEntryItem;
use Drupal\commerce_order\Entity\OrderItemInterface;
use Drupal\contacts_events\Event\OrderItemTransactionEvent;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Commerce Order Item Bookkeeping hooks.
*/
class OrderItemBookkeepingHooks {
/**
* The bookkeeping transaction entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* The commerce order type entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $orderItemTypeStorage;
/**
* The bookkeeping commerce settings.
*
* @var \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
*/
protected $config;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcher
*/
protected $eventDispatcher;
/**
* Constructs a commercepaymenthooks object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Config\ConfigFactory $config_factory
* The config factory.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, ConfigFactory $config_factory, EventDispatcherInterface $event_dispatcher) {
$this->storage = $entity_type_manager->getStorage('bookkeeping_transaction');
$this->orderItemTypeStorage = $entity_type_manager->getStorage('commerce_order_item_type');
$this->config = $config_factory->get('bookkeeping.commerce');
$this->eventDispatcher = $event_dispatcher;
}
/**
* Post save (insert/update) for order items.
*
* @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
* The order.
* @param \Drupal\commerce_order\Entity\OrderItemInterface|null $original
* The original, if any.
*/
public function postSave(OrderItemInterface $order_item, OrderItemInterface $original = NULL): void {
// We only want to process stateful order items.
if (!$order_item->hasField('state')) {
return;
}
// Ensure the item belongs to an order.
$order = $order_item->getOrder();
if (!$order) {
return;
}
// Only deal with bookings.
if ($order->bundle() != 'contacts_booking') {
return;
}
// Check for a disabled store.
$store_id = $order->getStoreId();
if ($this->config->get("stores.{$store_id}.disabled")) {
return;
}
// Check whether it is/was payable.
$is_payable = $this->isPayable($order_item);
$was_payable = $original && $this->isPayable($original);
// If it was not and is not payable, there's nothing to do.
if (!$is_payable && !$was_payable) {
return;
}
// Get the amount to post. Start with the total price.
$post_amount = $order_item->getTotalPrice();
// If it was and is payable, we need to check for a difference.
if ($is_payable && $was_payable) {
$original_amount = $original->getTotalPrice();
$generator = 'commerce_order_item:changed';
if ($original_amount) {
$post_amount = $post_amount->subtract($original_amount);
}
}
// Otherwise if it is no longer payable, we need the negative of the
// original amount.
elseif ($was_payable) {
$post_amount = $original->getTotalPrice();
$generator = 'commerce_order_item:unpayable';
if ($post_amount) {
$post_amount = $post_amount->multiply('-1');
}
}
// Otherwise we post the total price.
else {
$generator = 'commerce_order_item:payable';
}
// See where we should track income for this store.
$accounts_receivable = $this->config->get("stores.{$store_id}.accounts_receivable_account");
$income_account = $this->config->get("stores.{$store_id}.income_account");
// If the order is a booking, see if there is an event specific income
// account override.
if ($order->bundle() == 'contacts_booking') {
/** @var \Drupal\contacts_events\Entity\EventInterface $event */
$event = $order->get('event')->entity;
if ($event->hasField('bookkeeping_income_account')) {
$event_income_account = $event->get('bookkeeping_income_account')->target_id;
if ($event_income_account) {
$income_account = $event_income_account;
}
}
}
// Dispatch our event.
$event = new OrderItemTransactionEvent(
$generator,
$post_amount,
$income_account,
$accounts_receivable,
$order,
$order_item,
$original
);
$this->eventDispatcher->dispatch(OrderItemTransactionEvent::EVENT, $event);
// If a subscriber asked us not to post, stop now.
if ($event->isPrevented()) {
return;
}
// Get our potentially modified post amount.
$post_amount = $event->getValue();
// If the amount is zero, there's nothing more to do.
if ($post_amount->isZero()) {
return;
}
// Create our transaction.
/** @var \Drupal\bookkeeping\Entity\TransactionInterface $transaction */
$transaction = $this->storage->create([
'generator' => $generator,
]);
// Add the entries.
$transaction
->addEntry($event->getFrom(), $post_amount, BookkeepingEntryItem::TYPE_CREDIT)
->addEntry($event->getTo(), $post_amount, BookkeepingEntryItem::TYPE_DEBIT);
// Add the related entities.
foreach ($event->getRelated() as $related) {
$transaction->addRelated($related);
}
// Save the transaction.
$transaction->save();
}
/**
* Check whether an order is payable (therefore should be tracked as income).
*
* @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
* The order to check.
*
* @return bool
* Whether it is payable.
*
* @todo Consider making this something publically accessible for other uses.
*/
protected function isPayable(OrderItemInterface $order_item) {
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state */
$state = $order_item->get('state')->first();
if ($state === NULL) {
return FALSE;
}
/** @var \Drupal\commerce_order\Entity\OrderItemTypeInterface $order_item_type */
$order_item_type = $this->orderItemTypeStorage->load($order_item->bundle());
$payable_states = $order_item_type->getThirdPartySetting('contacts_events', 'payable_states');
// Default to everything but pending.
if (empty($payable_states)) {
$payable_states = array_keys($state->getWorkflow()->getStates());
$payable_states = array_diff($payable_states, ['pending']);
}
return in_array($state->value, $payable_states);
}
}
