contacts_events-8.x-1.x-dev/modules/village_allocation/src/AutomaticAllocation.php

modules/village_allocation/src/AutomaticAllocation.php
<?php

namespace Drupal\village_allocation;

use Drupal\contacts_events\Entity\Event;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
 * Performs automatic allocation.
 *
 * @package Drupal\contacts_events_villages
 */
class AutomaticAllocation {

  use DebugTrait;
  // Need DependencySerializationTrait or Drupal complains about serializing
  // the database connection when invoked through the form batch api.
  use DependencySerializationTrait;


  /**
   * VA queries.
   *
   * @var \Drupal\village_allocation\VillageAllocationQueries
   */
  protected $queries;

  /**
   * Logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $log;

  /**
   * Database.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $db;

  /**
   * AutomaticAllocation constructor.
   *
   * @param \Drupal\Core\Database\Connection $db
   *   Database.
   * @param \Drupal\village_allocation\VillageAllocationQueries $queries
   *   VA queries.
   */
  public function __construct(Connection $db, VillageAllocationQueries $queries) {
    $this->db = $db;
    $this->queries = $queries;
  }

  /**
   * Batch API automatic village allocation process callback.
   *
   * Get an ordered list of churches with unassigned bookings and process them
   * one at a time with the following steps:
   *
   * - Check if it already assigned and if so, find the village.
   * - Otherwise, check if there is a church with a similar postcode (+/-)
   *   and if so find the village.
   *
   * If there is a village found we then we attempt to allocate the entire
   * church moving out one village in each direction each time.
   * Otherwise we start at* the top of the village list and work our way down.
   *
   * When determining whether a village has space, we take the total pitch value
   * of the entire church and check whether it will fit in the village without
   * exceeding the fill percentage.
   *
   * @param \Drupal\contacts_events\Entity\Event $event
   *   The event being allocated.
   * @param array $context
   *   Drupal batch context.
   */
  public function process(Event $event, array &$context) {
    // Set up the sandbox if it's not configured.
    if (empty($context['sandbox'])) {
      // Preload group id, pitches, num. orders.
      $groups = $this->queries->getGroupsForAutomaticAllocation($event->id());

      $context['sandbox']['progress'] = 0;
      $context['sandbox']['max'] = count($groups);
      $context['sandbox']['groups'] = $groups;

      $context['results']['assigned'] = 0;
      $context['results']['skipped'] = 0;
      $context['results']['error'] = 0;
      $context['results']['messages'] = [];

      // Get hold of all the villages in order.
      $context['sandbox']['villages'] = $this->queries->getVillagesInOrder($event->id());

      $this->debug(new TranslatableMarkup('Beginning allocation: @groups groups @villages villages', [
        '@groups' => count($groups),
        '@villages' => count($context['sandbox']['villages']),
      ]));

    }

    // Get hold of the current item.
    // At this point item is an array of objects with properties
    // group_id, group_name, orders (count of orders), pitches, postal_code.
    $groups = array_slice($context['sandbox']['groups'], $context['sandbox']['progress'], 1, TRUE);
    $item = reset($groups);

    if ($item && $item->pitches > 0) {
      $this->debug("Processing group: {$item->group_id} Pitches: {$item->pitches} Bookings: {$item->orders} Postcode: {$item->postal_code}");

      // See if any other bookings in this group are already assigned to a
      // village. If so, we want to put the remainder of this group with them.
      $village_ids = $this->queries->getVillageIdsAllocatedToGroup($item->group_id);

      if (count($village_ids)) {
        $this->debug("Group: {$item->group_id}. Some bookings already allocated to villages: " . implode(', ', $village_ids));
      }

      $assigned = $this->findMatchWithSpace($item->pitches, $village_ids, $context['sandbox']['villages']);

      if ($assigned) {
        $this->debug("Group: {$item->group_id}. Assigning to village $assigned");
      };

      // If not, have a look for a similar postcode.
      if (!$assigned) {
        $village_ids = $this->queries->getVillagesContainingBookingsWithSimilarPostcode($event->id(), $item->postal_code);
        $assigned = $this->findMatchWithSpace($item->pitches, $village_ids, $context['sandbox']['villages']);

        if (count($village_ids)) {
          $this->debug("Group: {$item->group_id}. Villages containing bookings with similar postcode: " . print_r($village_ids, TRUE));
          if ($assigned) {
            $this->debug("Group: {$item->group_id}. Assigning to village $assigned");
          };
        }
      }

      // Just run down the village list.
      if (!$assigned) {
        $village_ids = array_keys($context['sandbox']['villages']);
        $assigned = $this->findMatchWithSpace($item->pitches, $village_ids, $context['sandbox']['villages']);
        $this->debug("Group: {$item->group_id} assigning to first available village: $assigned");
      }

      if ($assigned) {
        // Get hold of all the unassigned bookings in this group.
        $orders = $this->queries->getUnassignedBookingsInGroup($event->id(), $item->group_id);
        if (count($orders) == $item->orders) {
          $refs = [];
          /** @var \Drupal\commerce_order\Entity\Order $order */
          foreach ($orders as $order_id => $order) {
            try {
              $order->set('village', $assigned);
              $order->save();
              $context['results']['assigned']++;
              $context['sandbox']['villages'][$assigned]->pitches_allocated += $item->pitches;
            }
            catch (\Throwable $ex) {
              $this->debug($ex->getMessage());
              $this->debug($ex->getTraceAsString());
              $refs[] = $order->getOrderNumber();
            }
          }

          if (!empty($refs)) {
            $context['message'] = new TranslatableMarkup('Failed to allocate booking: @booking_refs.', [
              '@booking_refs' => implode(', ', $refs),
            ]);
          }
          else {
            $context['message'] = new TranslatableMarkup('@label assigned to @village.', [
              '@label' => $item->group_name,
              '@village' => $context['sandbox']['villages'][$assigned]->label(),
            ]);
          }

        }
        else {
          $context['results']['error'] += $item->orders;
          $context['message'] = new TranslatableMarkup('Error with @label - there is inconsistent data.', ['@label' => $item->group_name]);
        }
      }
      else {
        $context['results']['skipped'] += $item->orders;
        $context['message'] = new TranslatableMarkup('@label skipped - insufficient space.', ['@label' => $item->group_name]);
      }
    }
    elseif ($item) {
      $context['results']['skipped'] += $item->orders;
      $context['message'] = new TranslatableMarkup('@label skipped - zero pitches.', ['@label' => $item->group_name]);
    }

    // Record progress through the batch.
    $context['sandbox']['progress']++;
    $context['finished'] = $context['sandbox']['max'] > 0 ? $context['sandbox']['progress'] / $context['sandbox']['max'] : 1;
    if (!empty($context['message'])) {
      $context['results']['messages'] = array_merge($context['results']['messages'], [$context['message']]);
    }
  }

  /**
   * Iterate over the villages to find a match with space.
   *
   * @param float $pitches
   *   The space required for this item.
   * @param array $village_ids
   *   An array of ids of possible village matches.
   * @param array $villages
   *   The villages to look within.
   *
   * @return int|bool
   *   Either a matched village with space, or FALSE if none found.
   */
  protected function findMatchWithSpace($pitches, array $village_ids, array $villages) {
    // Find any valid matches.
    $village_ids = array_intersect(array_keys($villages), $village_ids);

    // Find a village with space.
    foreach ($village_ids as $id) {
      /** @var \Drupal\contacts_events_villages\Entity\Village $village */
      $village = $villages[$id];

      // pitches_allocated is a surrogate field that will have been added
      // by VillageAllocationQueries::getVillagesInOrder.
      if (($village->get('pitches')->value * $village->get('fill_value')->value / 100) >= ($village->pitches_allocated + $pitches)) {
        // Space found, so return.
        $this->debug('Checking village ' . $village->getName() . ': Has space');
        return $id;
      }
      else {
        $this->debug('Checking village ' . $village->getName() . ': No space');
      }
    }

    // Nothing found.
    return FALSE;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc