arch-8.x-1.x-dev/modules/stock/src/StockKeeper.php

modules/stock/src/StockKeeper.php
<?php

namespace Drupal\arch_stock;

use Drupal\arch_order\Entity\OrderInterface;
use Drupal\arch_product\Entity\ProductAvailability;
use Drupal\arch_product\Entity\ProductInterface;
use Drupal\arch_stock\Entity\WarehouseInterface;
use Drupal\arch_stock\Manager\WarehouseManagerInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Stock keeper service.
 *
 * @package Drupal\arch_stock
 */
class StockKeeper implements StockKeeperInterface {

  /**
   * Product type storage.
   *
   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
   */
  protected $productTypeStorage;

  /**
   * Warehouse manager.
   *
   * @var \Drupal\arch_stock\Manager\WarehouseManagerInterface
   */
  protected $warehouseManager;

  /**
   * Module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * Cache tags invalidator service.
   *
   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
   */
  protected $cacheTagsInvalidator;

  /**
   * Lock backend.
   *
   * @var \Drupal\Core\Lock\LockBackendInterface
   */
  protected $lock;

  /**
   * StockKeeper constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   Entity type manager.
   * @param \Drupal\arch_stock\Manager\WarehouseManagerInterface $warehouse_manager
   *   Warehouse manager.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   Module handler.
   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
   *   Cache tag invalidator service.
   * @param \Drupal\Core\Lock\LockBackendInterface $lock
   *   Lock backend.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    WarehouseManagerInterface $warehouse_manager,
    ModuleHandlerInterface $module_handler,
    CacheTagsInvalidatorInterface $cache_tags_invalidator,
    LockBackendInterface $lock,
  ) {
    $this->productTypeStorage = $entity_type_manager->getStorage('product_type');
    $this->warehouseManager = $warehouse_manager;
    $this->moduleHandler = $module_handler;
    $this->cacheTagsInvalidator = $cache_tags_invalidator;
    $this->lock = $lock;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('warehouse.manager'),
      $container->get('module_handler'),
      $container->get('cache_tags.invalidator'),
      $container->get('lock')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function isProductManagingStock(ProductInterface $product) {
    /** @var \Drupal\arch_product\Entity\ProductTypeInterface $type */
    $type = $this->productTypeStorage->load($product->bundle());
    return (bool) $type->getThirdPartySetting('arch_stock', 'stock_enable');
  }

  /**
   * {@inheritdoc}
   */
  public function reduceStock(
    ProductInterface $product,
    $sold_amount,
    OrderInterface $order,
    AccountInterface $account,
  ) {
    $lock_name = 'arch_stock_reduce:product:' . $product->id();
    while ($this->lock->wait($lock_name, 0.2)) {
      $this->lock->acquire($lock_name);
      $this->doReduce($product, $sold_amount, $order, $account);
      $this->lock->release($lock_name);
      break;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function isNegativeStockAllowed(
    ProductInterface $product,
    AccountInterface $account,
  ) {
    if ($product->getAvailability() === ProductAvailability::STATUS_NOT_AVAILABLE) {
      return FALSE;
    }
    $selected_warehouses = $this->selectWarehouses($account);
    return $this->allowNegativeStockForWarehouses($selected_warehouses, $account);
  }

  /**
   * {@inheritdoc}
   */
  public function getTotalProductStock(
    ProductInterface $product,
    AccountInterface $account,
  ) {
    $selected_warehouses = $this->selectWarehouses($account);
    $stock_values = $this->getCurrentStock($product);
    $total = 0;
    foreach ($stock_values as &$stock) {
      if (!in_array($stock['warehouse'], $selected_warehouses)) {
        continue;
      }
      $total += $stock['quantity'];
    }

    $this->moduleHandler->alter('product_stock', $total, $product, $account);

    return $total;
  }

  /**
   * {@inheritdoc}
   */
  public function hasProductEnoughStock(
    ProductInterface $product,
    AccountInterface $account,
    $amount = 1,
  ) {
    if ($product->getAvailability() === ProductAvailability::STATUS_NOT_AVAILABLE) {
      return FALSE;
    }

    $selected_warehouses = $this->selectWarehouses($account);
    if ($this->allowNegativeStockForWarehouses($selected_warehouses, $account)) {
      return TRUE;
    }

    return $this->getTotalProductStock($product, $account) >= $amount;
  }

  /**
   * Do reduce.
   *
   * @param \Drupal\arch_product\Entity\ProductInterface $product
   *   Product entity.
   * @param float $sold_amount
   *   Sold amount of product.
   * @param \Drupal\arch_order\Entity\OrderInterface $order
   *   Order entity.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Customer account.
   *
   * @return bool
   *   Result.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function doReduce(
    ProductInterface $product,
    $sold_amount,
    OrderInterface $order,
    AccountInterface $account,
  ) {
    $selected_warehouses = $this->selectWarehouses($account);
    $amount = $sold_amount;

    $stock_values = $this->getCurrentStock($product);
    $original_stock_values = $stock_values;
    foreach ($stock_values as &$stock) {
      if (!in_array($stock['warehouse'], $selected_warehouses)) {
        continue;
      }

      $available = $stock['quantity'];
      if ($available <= $amount) {
        $stock['quantity'] = 0;
        $amount -= $available;
      }
      else {
        $stock['quantity'] -= $amount;
        $amount = 0;
      }
    }

    if ($amount > 0) {
      if ($this->allowNegativeStockForWarehouses($selected_warehouses, $account)) {
        /** @var \Drupal\arch_stock\Entity\WarehouseInterface[] $warehouses */
        $warehouses = $this->warehouseManager->getAvailableWarehouses($account);
        foreach ($stock_values as &$stock) {
          if (!in_array($stock['warehouse'], $selected_warehouses)) {
            continue;
          }

          if (
            !empty($stock['warehouse'])
            && !empty($warehouses[$stock['warehouse']])
            && $warehouses[$stock['warehouse']]->allowNegative()
          ) {
            $stock['quantity'] -= $amount;
            $amount = 0;

            if (
              $stock['quantity'] <= 0
              && ($availability = $warehouses[$stock['warehouse']]->getOverBookedAvailability())
            ) {
              $product->setAvailability($availability);
            }
          }
        }
      }

      // @todo Handle over selling.
    }

    /** @var \Drupal\arch_product\Entity\ProductInterface $product */
    $product->set('stock', $stock_values);
    $product->setNewRevision(FALSE);
    $product->save();
    $this->moduleHandler->invokeAll('stock_reduced', [
      $sold_amount,
      $product,
      $order,
      $account,
      $original_stock_values,
    ]);

    // Invalidate cache.
    $this->cacheTagsInvalidator->invalidateTags($product->getCacheTagsToInvalidate());
    return TRUE;
  }

  /**
   * Get current stock of product.
   *
   * @param \Drupal\arch_product\Entity\ProductInterface $product
   *   Product entity.
   *
   * @return array
   *   Stock values.
   */
  protected function getCurrentStock(ProductInterface $product) {
    if (!$product->hasField('stock')) {
      return [];
    }
    return array_map(function ($item) {
      $item['quantity'] = (float) $item['quantity'];
      return $item;
    }, $product->get('stock')->getValue());
  }

  /**
   * {@inheritdoc}
   */
  public function selectWarehouses(AccountInterface $account) {
    $warehouses = $this->warehouseManager->getAvailableWarehouses($account);
    $list = array_map(function ($warehouse) {
      /** @var \Drupal\arch_stock\Entity\WarehouseInterface $warehouse */
      return $warehouse->id();
    }, $warehouses);

    $context = [
      'account' => $account,
      'warehouses' => $warehouses,
    ];
    $this->moduleHandler->alter('stock_keeper_selected_warehouses', $list, $context);
    return $list;
  }

  /**
   * Check if any of the warehouses with given IDs is allow negative stock.
   *
   * @param string[] $warehouse_ids
   *   Warehouse IDs.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Account.
   *
   * @return bool
   *   Return TRUE if any of the warehouses with given ID is allow overbooking.
   */
  protected function allowNegativeStockForWarehouses(array $warehouse_ids, AccountInterface $account) {
    if (empty($warehouse_ids)) {
      return FALSE;
    }

    $warehouses = $this->warehouseManager->getWarehouses();
    foreach ($warehouses as $warehouse) {
      if (!in_array($warehouse->id(), $warehouse_ids)) {
        continue;
      }

      if ($this->allowNegativeStockForWarehouse($warehouse, $account)) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Check if negative stock is allowed for account at warehouse.
   *
   * @param \Drupal\arch_stock\Entity\WarehouseInterface $warehouse
   *   Warehouse entity.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Account.
   *
   * @return bool
   *   Returns TRUE if given user can access negative stock in warehouse.
   */
  protected function allowNegativeStockForWarehouse(WarehouseInterface $warehouse, AccountInterface $account) {
    $allow_negative = AccessResult::allowedIf($warehouse->allowNegative());
    $this->moduleHandler->alter('allow_negative_stock_for_warehouse', $allow_negative, $warehouse, $account);
    return $allow_negative->isAllowed();
  }

}

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

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