contacts_events-8.x-1.x-dev/src/Form/TransferForm.php
src/Form/TransferForm.php
<?php namespace Drupal\contacts_events\Form; use CommerceGuys\Intl\Formatter\CurrencyFormatterInterface; use Drupal\commerce_order\Entity\OrderItemInterface; use Drupal\commerce_payment\Entity\PaymentGateway; use Drupal\commerce_price\Price; use Drupal\Component\Datetime\TimeInterface; use Drupal\contacts_events\Entity\EventClass; use Drupal\contacts_events\Entity\TicketInterface; use Drupal\Core\Entity\ContentEntityForm; use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Entity form variant for transferring a ticket. */ class TransferForm extends ContentEntityForm { /** * The ticket to be transferred. * * @var \Drupal\contacts_events\Entity\TicketInterface */ protected $entity; /** * The booking to transfer a ticket to. * * @var \Drupal\commerce_order\Entity\OrderInterface */ protected $targetBooking; /** * The storage for Events Classes. * * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $classStorage; /** * The storage for Commerce Bookings. * * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $orderStorage; /** * The storage for Commerce Payments. * * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $paymentStorage; /** * The currency formatter service. * * @var \CommerceGuys\Intl\Formatter\CurrencyFormatterInterface */ protected $currencyFormatter; /** * TransferForm constructor. * * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository * The entity repository service. * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface|null $entity_type_bundle_info * The entity type bundle service. * @param \Drupal\Component\Datetime\TimeInterface|null $time * The time service. * @param \Drupal\Core\Entity\EntityTypeManagerInterface|null $entity_type_manager * The entity type manager service. * @param \CommerceGuys\Intl\Formatter\CurrencyFormatterInterface|null $currency_formatter * The currency formatter service. */ public function __construct(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL, EntityTypeManagerInterface $entity_type_manager = NULL, CurrencyFormatterInterface $currency_formatter = NULL) { parent::__construct($entity_repository, $entity_type_bundle_info, $time); $entity_type_manager = $entity_type_manager ?: \Drupal::service('entity_type.manager'); $this->classStorage = $entity_type_manager->getStorage('contacts_events_class'); $this->orderStorage = $entity_type_manager->getStorage('commerce_order'); $this->paymentStorage = $entity_type_manager->getStorage('commerce_payment'); $this->currencyFormatter = $currency_formatter ?: \Drupal::service('commerce_price.currency_formatter'); } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('entity.repository'), $container->get('entity_type.bundle.info'), $container->get('datetime.time'), $container->get('entity_type.manager'), $container->get('commerce_price.currency_formatter') ); } /** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { // Show the booking step if we don't have a target booking. if (!$this->targetBooking) { $form = $this->bookingForm($form, $form_state); } // Otherwise show the confirmation step. else { $form = $this->confirmationForm($form, $form_state); } return $form; } /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); // Only validate the booking ID if step one is being submitted. if (!$this->targetBooking) { $event_code = $this->entity->getEvent()->get('code')->value; $booking_id = $form_state->getValue('booking'); // Remove the 0-padding to get the Order ID. $booking_id = ltrim($booking_id, '0'); $booking = $this->orderStorage->load($booking_id); // Validate that the order exists. if (!$booking) { $form_state->setError($form['booking'], $this->t('Booking %booking does not exist.', ['%booking' => $event_code . '-' . $booking_id])); } // And is a booking. elseif ($booking->bundle() != 'contacts_booking') { $form_state->setError($form['booking'], $this->t('Order %booking is not a Booking.', ['%booking' => $booking_id])); } // And that the booking is for the same event. elseif ($booking->get('event')->target_id != $this->entity->getOrderItem()->getOrder()->get('event')->target_id) { $form_state->setError($form['booking'], $this->t('Booking %booking is for a different event to this booking.', ['%booking' => $booking->get('event')->entity->get('code')->value . '-' . $booking->id()])); } // And that the booking is not the source booking. elseif ($booking->id() == $this->entity->getOrderItem()->getOrderId()) { $form_state->setError($form['booking'], $this->t('You cannot transfer tickets to the same booking.')); } // Check whether we can calculate the amount paid on an Order Item. if ($form_state->getValue('transfer_payment') && is_null($this->getPaidForItem($this->entity->getOrderItem()))) { $form_state->setError($form['transfer_payment'], $this->t('Payments cannot be automatically transferred for this Ticket.')); } // If there are no errors set the target booking and rebuild the form. if (empty($form_state->getErrors())) { $this->targetBooking = $booking; $form_state->setRebuild(TRUE); } } } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { // Get the amount paid before any changes are made. $paid = $this->getPaidForItem($this->entity->getOrderItem()); // Move order item to target booking. $order_item = $this->entity->getOrderItem(); $this->targetBooking->addItem($order_item); // Remove order item from original booking. $original_booking = $order_item->getOrder(); $original_booking->removeItem($order_item); // Update the Order ID on the Order Item. $order_item->set('order_id', $this->targetBooking->id()); $order_item->save(); // Append a note to booking notes on both orders. $replacements = [ '%time' => $this->time->getRequestTime(), '%ticket_holder' => $this->entity->getName(), '%original_booking' => $original_booking->label(), '%target_booking' => $this->targetBooking->label(), '%user' => $this->currentUser()->getDisplayName(), '%uid' => $this->currentUser()->id(), ]; $transfer_message = $this->t("<br/>%time: Ticket for %ticket_holder transferred from %original_booking to %target_booking by %user [%uid].", $replacements); $target_notes = $this->targetBooking->get('back_end_notes')->value; $target_notes .= $transfer_message; $this->targetBooking->set('back_end_notes', $target_notes); $this->targetBooking->save(); $original_notes = $original_booking->get('back_end_notes')->value; $original_notes .= $transfer_message; $original_booking->set('back_end_notes', $original_notes); $original_booking->save(); // Show user a confirmation message. $replacements = [ '%ticket_holder' => $this->entity->getName(), '%old_booking' => $original_booking->label(), '%new_booking' => $this->targetBooking->label(), ]; $this->messenger()->addMessage($this->t('Ticket for %ticket_holder transferred from %old_booking to %new_booking', $replacements)); // If transfer payments is selected, create payments to transfer the amount // paid on the order item. if ($form_state->getValue('transfer_payment')) { // Only transfer payments if we can calculate the amount paid for the // Order Item and the transfer payment gateway exists. $transfer_payment_gateway = PaymentGateway::load('booking_transfer'); if ($paid && $paid->isPositive() && $transfer_payment_gateway) { // Add a Booking Transfer payment to target booking for paid value. /** @var \Drupal\commerce_payment\Entity\PaymentInterface $target_booking_payment */ $target_booking_payment = $this->paymentStorage->create([ 'state' => 'completed', 'amount' => $paid, 'payment_gateway' => 'booking_transfer', 'order_id' => $this->targetBooking->id(), 'completed' => $this->time->getRequestTime(), 'order_item_tracking' => [ $order_item->id() => $paid->toArray() + ['target_id' => $order_item->id()], ], ]); $target_booking_payment->save(); // Add Booking Transfer payment to original booking to negate the // amount that has been paid on that order. $paid = $paid->multiply('-1'); /** @var \Drupal\commerce_payment\Entity\PaymentInterface $source_booking_payment */ $source_booking_payment = $this->paymentStorage->create([ 'state' => 'completed', 'amount' => $paid, 'payment_gateway' => 'booking_transfer', 'order_id' => $original_booking, 'completed' => $this->time->getRequestTime(), 'order_item_tracking' => [ $order_item->id() => $paid->toArray() + ['target_id' => $order_item->id()], ], ]); $source_booking_payment->save(); // Show user a message about the transferred payment after un-negating. $paid = $paid->multiply('-1'); $replacements = [ '%payment_value' => $this->currencyFormatter->format($paid->getNumber(), $paid->getCurrencyCode()), '%old_booking' => $original_booking->label(), '%new_booking' => $this->targetBooking->label(), ]; $this->messenger()->addMessage($this->t('Payment of for %payment_value transferred from %old_booking to %new_booking', $replacements)); } } // Redirect to the target booking to see the newly transferred ticket. $form_state->setRedirect('entity.commerce_order.booking_admin_tickets', ['commerce_order' => $this->targetBooking->id()]); } /** * {@inheritdoc} */ protected function actions(array $form, FormStateInterface $form_state) { $actions['continue'] = [ '#type' => 'submit', '#value' => $this->targetBooking ? $this->t('Transfer booking') : $this->t('Continue'), '#submit' => ['::submitForm'], ]; return $actions; } /** * Create the booking step form for transferring a ticket. * * @param array $form * The current form array. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current form state. * * @return array * A Form API form for booking step of the ticket transfer. */ protected function bookingForm(array $form, FormStateInterface $form_state) { $form['ticket'] = $this->ticketDetails(); $form['booking'] = [ '#type' => 'fieldset', '#title' => $this->t('Booking to transfer ticket to:'), 'booking' => [ '#type' => 'textfield', '#title' => $this->t('Booking to transfer to'), '#field_prefix' => $this->entity->getEvent()->get('code')->value . '-', ], ]; return $form; } /** * Create the confirmation step form for transferring a ticket. * * @param array $form * The current form array. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current form state. * * @return array * A Form API form for the confirmation step of the ticket transfer. */ protected function confirmationForm(array $form, FormStateInterface $form_state) { $form['ticket'] = $this->ticketDetails(); $form['booking'] = $this->bookingDetails(); $paid = $this->getPaidForItem($this->entity->getOrderItem()); $transfer_payment_gateway = PaymentGateway::load('booking_transfer'); // Only show the option to transfer payments if we can calculate the amount // paid on the Order Item and if the transfer payment gateway exists. if ($paid && !$paid->isZero() && $transfer_payment_gateway) { $form['transfer_payment'] = [ '#type' => 'checkbox', '#title' => $this->t('Transfer payment with ticket'), '#description' => $this->t('If you do not transfer existing payments then you will need to manually fix any discrepancies between order totals and payments.'), ]; } return $form; } /** * Create a render array of ticket details. * * @return array * A renderable array of ticket details. */ protected function ticketDetails() { $details = [ '#type' => 'fieldset', '#title' => $this->t('Ticket to be transferred:'), ]; $details['name'] = $this->renderWithLabel($this->t('Name'), $this->entity->getName()); $details['class'] = $this->renderWithLabel($this->t('Ticket class'), $this->getClassLabel($this->entity)); if ($price = $this->entity->getPrice()) { $details['price'] = $this->renderWithLabel($this->t('Price'), $this->currencyFormatter->format($price->getNumber(), $price->getCurrencyCode())); } if ($paid = $this->getPaidForItem($this->entity->getOrderItem())) { $details['paid'] = $this->renderWithLabel($this->t('Paid'), $this->currencyFormatter->format($paid->getNumber(), $paid->getCurrencyCode())); } if ($balance = $this->getBalanceForItem($this->entity->getOrderItem())) { $details['balance'] = $this->renderWithLabel($this->t('Balance'), $this->currencyFormatter->format($balance->getNumber(), $balance->getCurrencyCode())); } return $details; } /** * Create a render array of booking details. * * @return array * A renderable array of booking details. */ protected function bookingDetails() { $details = [ '#type' => 'fieldset', '#title' => $this->t('Booking to transfer ticket to:'), ]; $details['reference'] = $this->renderWithLabel($this->t('Booking reference'), $this->targetBooking->label()); $customer_link = $this->targetBooking->getCustomer()->toLink(NULL, 'canonical', ['attributes' => ['target' => '_blank']])->toString(); $details['manager'] = $this->renderWithLabel($this->t('Booking manager'), $customer_link); return $details; } /** * Get the event class label for a ticket. * * @param \Drupal\contacts_events\Entity\TicketInterface $ticket * The ticket to retrieve for. * * @return string * The class label. */ protected function getClassLabel(TicketInterface $ticket) { if (!isset($this->classLabels)) { $this->classLabels = array_map(function (EventClass $class) { return $class->label(); }, $this->classStorage->loadMultiple()); } $class = $ticket->getMappedPrice()['class']; return $this->classLabels[$class] ?? '-'; } /** * Render a value with a label. * * @param \Drupal\Core\StringTranslation\TranslatableMarkup $label * The translatable label. * @param string $value * The value to be displayed. * * @return array * A renderable array of label and value. */ protected function renderWithLabel(TranslatableMarkup $label, $value) { return [ '#markup' => '<div>' . $label . ': ' . $value . '</div>', ]; } /** * Get a Price for the amount paid on an Order Item. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item * The Order Item to get the paid amount for. * * @return \Drupal\commerce_price\Price|null * A Price with the paid value. NULL if not supported. */ protected function getPaidForItem(OrderItemInterface $order_item) { if ($order_item->hasField('paid') && !$order_item->get('paid')->isEmpty()) { $paid_value = $order_item->get('paid')->first()->getValue(); return new Price($paid_value['number'], $paid_value['currency_code']); } return NULL; } /** * Get a Price for the balance of an Order Item. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item * The Order Item to get the balance for. * * @return \Drupal\commerce_price\Price|null * A Price with the balance value. NULL if not supported. */ protected function getBalanceForItem(OrderItemInterface $order_item) { if ($order_item->hasField('balance') && !$order_item->get('balance')->isEmpty()) { $balance_value = $order_item->get('balance')->first()->getValue(); return new Price($balance_value['number'], $balance_value['currency_code']); } return NULL; } }