arch-8.x-1.x-dev/modules/stock/arch_stock.module

modules/stock/arch_stock.module
<?php

/**
 * @file
 * Stock module.
 */

use Drupal\arch_cart\Cart\CartInterface;
use Drupal\arch_order\Entity\OrderInterface;
use Drupal\arch_order\Plugin\Field\FieldType\OrderLineItemInterface;
use Drupal\arch_product\Entity\Product;
use Drupal\arch_product\Entity\ProductInterface;
use Drupal\arch_product\Entity\ProductTypeInterface;
use Drupal\arch_stock\Plugin\Field\FieldType\StockFieldItemList;
use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\Template\Attribute;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;

/**
 * Implements hook_entity_load().
 */
function arch_stock_entity_load(array $entities, $entity_type_id) {
  /** @var \Drupal\arch_stock\Manager\WarehouseManager $warehouse_manager */
  $warehouse_manager = Drupal::service('warehouse.manager');
  $warehouses = $warehouse_manager->getFormOptions();

  $fields = drupal_static(__FUNCTION__);

  /** @var \Drupal\Core\Entity\EntityInterface $entity */
  foreach ($entities as $entity) {
    if ($entity->getEntityTypeId() != 'product') {
      continue;
    }
    if (!$entity instanceof FieldableEntityInterface) {
      continue;
    }
    if (!isset($fields[$entity->getEntityTypeId()][$entity->bundle()])) {
      $fields[$entity->getEntityTypeId()][$entity->bundle()] = $entity->getFields();
    }
    foreach ($fields[$entity->getEntityTypeId()][$entity->bundle()] as $field) {
      if (!$field instanceof StockFieldItemList) {
        continue;
      }

      _arch_stock_prepare_stock_field_value($field, $warehouses);
    }
  }
}

/**
 * Set stock field values to make sure every warehouse has value.
 *
 * @param \Drupal\arch_stock\Plugin\Field\FieldType\StockFieldItemList $field
 *   Stock field.
 * @param array $warehouses
 *   Warehouse list.
 */
function _arch_stock_prepare_stock_field_value(StockFieldItemList $field, array $warehouses) {
  $values = array_filter($field->getValue(), function ($item) use ($warehouses) {
    return isset($warehouses[$item['warehouse']]);
  });
  $existing = array_column($values, 'warehouse');
  foreach ($warehouses as $id => $label) {
    if (in_array($id, $existing)) {
      continue;
    }

    $values[] = [
      'warehouse' => $id,
      'quantity' => 0,
      'cart_quantity' => 0,
    ];
  }

  $field->setValue($values);
}

/**
 * Implements hook_theme().
 */
function arch_stock_theme() {
  return [
    'warehouse' => [
      'render element' => 'elements',
    ],

    'stock_form_table' => [
      'render element' => 'element',
    ],

    'stock_info_field' => [
      'render element' => 'element',
    ],
  ];
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function arch_stock_theme_suggestions_warehouse(array $variables) {
  $suggestions = [];

  /** @var \Drupal\arch_stock\Entity\WarehouseInterface $warehouse */
  $warehouse = $variables['elements']['#warehouse'];

  $suggestions[] = 'warehouse__' . $warehouse->bundle();
  $suggestions[] = 'warehouse__' . $warehouse->id();
  $suggestions[] = 'warehouse__' . $warehouse->bundle() . '__' . $warehouse->id();

  return $suggestions;
}

/**
 * Prepares variables for warehouse templates.
 *
 * Default template: warehouse.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the warehouse. Properties used:
 *     - #warehouse: A \Drupal\arch_stock\Entity\WarehouseInterface object.
 *     - #view_mode: The current view mode for this warehouse, e.g.
 *       'full' or 'teaser'.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_warehouse(array &$variables) {
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  $variables['warehouse'] = $variables['elements']['#warehouse'];
  /** @var \Drupal\arch_stock\Entity\WarehouseInterface $warehouse */

  // Helpful $content variable for templates.
  $variables['content'] = [];
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Preprocess arch stock form table.
 *
 * @param array $variables
 *   Variables array.
 */
function template_preprocess_stock_form_table(array &$variables) {
  $element = $variables['element'];
  $variables['multiple'] = $element['#cardinality_multiple'];

  $variables['label'] = [
    '#theme' => 'form_element_label',
    '#title' => $element['#title'],
    '#title_display' => 'above',
  ];

  $table_id = Html::getUniqueId($element['#field_name'] . '_values');
  $order_class = $element['#field_name'] . '-delta-order';
  $header = [];
  if ($variables['multiple']) {
    $header['drag'] = [
      'data' => NULL,
    ];
    $header['warehouse'] = [
      'data' => t('Warehouse', [], ['context' => 'arch_stock']),
      'class' => ['header-value', 'header-value--type', 'col-narrow'],
    ];
  }

  $header['quantity'] = [
    'data' => t('Quantity', [], ['context' => 'arch_stock']),
    'class' => ['header-value', 'header-value--vat', 'col-narrow'],
  ];

  if ($variables['multiple']) {
    $header['sort'] = t('Order', [], ['context' => 'Sort order']);
  }

  $rows = [];

  // Sort items according to '_weight' (needed when the form comes back after
  // preview or failed validation).
  $items = [];
  $variables['button'] = [];

  $fields = [
    'warehouse' => [
      '#title_display' => 'invisible',
    ],
    'quantity' => [
      '#title_display' => 'invisible',
    ],
  ];
  foreach (Element::children($element) as $key) {
    if ($key === 'add_more') {
      $variables['button'] = &$element[$key];
    }
    else {
      $items[] = &$element[$key];
    }
  }
  usort($items, '_field_multiple_value_form_sort_helper');

  // Add the items as table rows.
  foreach ($items as $item) {
    $item['_weight']['#attributes']['class'] = [$order_class];

    // Remove weight form element from item render array so it can be rendered
    // in a separate table column.
    $delta_element = $item['_weight'];
    unset($item['_weight']);

    foreach ($fields as $field => $settings) {
      if (isset($item[$field])) {
        $item[$field] = array_merge($item[$field], $settings);
      }
    }

    $cells = [];
    if ($variables['multiple']) {
      $cells['drag'] = [
        'data' => '',
        'class' => ['field-multiple-drag'],
      ];
      $cells['warehouse'] = [
        'data' => $item['warehouse'],
        'class' => ['value', 'value--warehouse', 'col-narrow'],
      ];
    }

    $cells['quantity'] = [
      'data' => $item['quantity'],
      'class' => ['value', 'value--quantity', 'col-narrow'],
    ];
    if ($variables['multiple']) {
      $cells['sort'] = ['data' => $delta_element, 'class' => ['delta-order']];
    }

    $rows[] = [
      'data' => $cells,
      'class' => ['draggable'],
    ];
  }

  $variables['table'] = [
    '#type' => 'table',
    '#header' => $header,
    '#rows' => $rows,
    '#attributes' => [
      'id' => $table_id,
      'class' => [
        'field-multiple-table',
        'stock-edit-table',
      ],
    ],
  ];
  if ($variables['multiple']) {
    $variables['table']['#tabledrag'] = [
      [
        'action' => 'order',
        'relationship' => 'sibling',
        'group' => $order_class,
      ],
    ];
  }

  if (!empty($element['#description'])) {
    $description_id = $element['#attributes']['aria-describedby'];
    $description_attributes['id'] = $description_id;
    $variables['description']['attributes'] = new Attribute($description_attributes);
    $variables['description']['content'] = $element['#description'];

    // Add the description's id to the table aria attributes.
    $variables['table']['#attributes']['aria-describedby'] = $element['#attributes']['aria-describedby'];
  }

  /** @var \Drupal\arch_stock\StockCartInfoInterface $stock_cart_info */
  $stock_cart_info = \Drupal::service('stock_cart.info');
  $in_cart = $stock_cart_info->quantityInCarts($element['#product_id']);
  $desc = new PluralTranslatableMarkup(
    $in_cart,
    'Currently @count item in customer cart',
    'Currently @count items in customer cart',
    ['@count' => $in_cart],
    ['context' => 'arch_stock']
  );
  $variables += [
    'description' => [],
  ];
  $variables['description'] += ['attributes' => []];
  $variables['description']['attributes']['class'][] = 'description';
  $variables['description']['content'][] = ['#markup' => $desc];
}

/**
 * Preprocess arch stock info field.
 *
 * @param array $variables
 *   Variables array.
 */
function template_preprocess_stock_info_field(array &$variables) {
  $element = $variables['element'];

  /** @var \Drupal\arch_product\Entity\ProductInterface $product */
  $product = $element['#product'];

  $variables['status'] = $element['#status'];
  $variables['attributes'] = [];
  $variables['attributes']['class'][] = 'stock-info';
  $variables['attributes']['class'][] = 'stock-info--' . $product->id();
  $variables['attributes']['class'][] = Html::cleanCssIdentifier('stock-info--' . $variables['status']);

  $variables['label'] = $element['#status_label'];
}

/**
 * Implements hook_form_alter().
 */
function arch_stock_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if (!in_array($form_id, ['product_type_add_form', 'product_type_edit_form'])) {
    return;
  }
  /** @var \Drupal\arch_product\Form\ProductTypeForm $form_object */
  $form_object = $form_state->getFormObject();
  /** @var \Drupal\arch_product\Entity\ProductType $product_type */
  $product_type = $form_object->getEntity();

  $form['stock'] = [
    '#type' => 'details',
    '#title' => t('Stock settings', [], ['context' => 'arch_stock']),
    '#group' => 'product_type_features',
    '#weight' => -100,
  ];

  $form['stock']['stock_enable'] = [
    '#type' => 'checkbox',
    '#title' => t('Enable inventory management', [], ['context' => 'arch_stock']),
    '#default_value' => $product_type->getThirdPartySetting('arch_stock', 'stock_enable'),
  ];

  $form['stock']['out_of_stock'] = [
    '#type' => 'textfield',
    '#default_value' => $product_type->getThirdPartySetting('arch_stock', 'out_of_stock'),
    '#title' => t('Out of stock text', [], ['context' => 'arch_stock']),
    '#description' => t('Leave this empty to display default "@default_text" message.', [
      '@default_text' => t('Out of stock', [], ['context' => 'arch_stock']),
    ], ['context' => 'arch_stock']),
  ];

  /** @var \Drupal\arch_stock\StockInfoInterface $stock_info */
  $stock_info = \Drupal::service('arch_stock.info');
  if ($stock_info->typeHasStockData($product_type)) {
    $form['stock']['stock_enable']['#disabled'] = TRUE;
    $form['stock']['stock_enable']['#description'] = t('There are products with stock info in this type.', [], ['context' => 'arch_stock']);
  }
  $form['#entity_builders'][] = 'arch_stock_form_product_type_form_builder';
}

/**
 * Entity builder for the product type form with inventory options.
 *
 * @see arch_stock_form_product_type_edit_form_alter()
 */
function arch_stock_form_product_type_form_builder($entity_type, ProductTypeInterface $type, &$form, FormStateInterface $form_state) {
  if ($form_state->getValue('stock_enable')) {
    $type->setThirdPartySetting('arch_stock', 'stock_enable', TRUE);
  }
  else {
    $type->unsetThirdPartySetting('arch_stock', 'stock_enable');
  }
  if ($out_of_stock = $form_state->getValue('out_of_stock')) {
    $type->setThirdPartySetting('arch_stock', 'out_of_stock', $out_of_stock);
  }
  else {
    $type->unsetThirdPartySetting('arch_stock', 'out_of_stock');
  }
}

/**
 * Implements hook_entity_update().
 */
function arch_stock_entity_update(EntityInterface $entity) {
  if (\Drupal::isConfigSyncing()) {
    // Do not change data while config import in progress.
    return;
  }

  if ($entity->getEntityTypeId() == 'product_type') {
    /** @var \Drupal\arch_product\Entity\ProductTypeInterface $entity */
    if ($entity->getThirdPartySetting('arch_stock', 'stock_enable')) {
      _arch_stock_add_stock_field($entity);
    }
    else {
      _arch_stock_remove_stock_field($entity);
    }
  }
  elseif ($entity->getEntityTypeId() == 'order') {
    /** @var \Drupal\arch_order\Entity\OrderInterface $entity */
    _arch_stock_handle_stock_change_on_order_update($entity);
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function arch_stock_order_presave(EntityInterface $entity) {
  if ($entity->isNew()) {
    /** @var \Drupal\arch_order\Entity\OrderInterface $entity */
    $entity->setDataKey('stock__save_stock_changes', TRUE);
  }
}

/**
 * Implements hook_entity_insert().
 */
function arch_stock_entity_insert(EntityInterface $entity) {
  if (\Drupal::isConfigSyncing()) {
    // Do not change data while config import in progress.
    return;
  }

  if ($entity->getEntityTypeId() == 'product_type') {
    /** @var \Drupal\arch_product\Entity\ProductTypeInterface $entity */
    if ($entity->getThirdPartySetting('arch_stock', 'stock_enable')) {
      _arch_stock_add_stock_field($entity);
    }
  }
}

/**
 * Check if product has enough stock for given customer.
 *
 * @param \Drupal\arch_product\Entity\ProductInterface $product
 *   Product.
 * @param \Drupal\Core\Session\AccountInterface $account
 *   Customer.
 * @param int|float $amount
 *   Required amount.
 *
 * @return bool
 *   Return TRUE if product has minimum $amount of stock.
 */
function _arch_stock_product_available_for_sell(ProductInterface $product, AccountInterface $account, $amount = 1) {
  /** @var \Drupal\arch_stock\StockKeeperInterface $stock_keeper */
  $stock_keeper = \Drupal::service('arch_stock.stock_keeper');
  return $stock_keeper->hasProductEnoughStock($product, $account);
}

/**
 * Implements hook_product_available_for_sell().
 */
function arch_stock_product_available_for_sell(ProductInterface $product, AccountInterface $account) {
  if (!$product) {
    return AccessResult::neutral();
  }
  /** @var \Drupal\arch_stock\StockKeeperInterface $stock_keeper */
  $stock_keeper = \Drupal::service('arch_stock.stock_keeper');
  if (!$stock_keeper->isProductManagingStock($product)) {
    return AccessResult::neutral();
  }
  $result = _arch_stock_product_available_for_sell($product, $account);
  return $result ? AccessResult::neutral() : AccessResult::forbidden();
}

/**
 * Implements hook_preprocess_add_to_api_cart().
 */
function arch_stock_preprocess_add_to_api_cart(&$variables) {
  /** @var \Drupal\arch_product\Entity\ProductInterface $product */
  $product = &$variables['product'];
  if (!$product) {
    return;
  }

  /** @var \Drupal\arch_stock\StockKeeperInterface $stock_keeper */
  $stock_keeper = \Drupal::service('arch_stock.stock_keeper');
  if (!$stock_keeper->isProductManagingStock($product)) {
    return;
  }

  /** @var \Drupal\Core\Session\AccountInterface $account */
  $account = $variables['user'];

  if (!_arch_stock_product_available_for_sell($product, $account, 1)) {
    $variables['not_available_text'] = t('Out of stock', [], ['context' => 'arch_stock_add_to_cart']);
    $variables['available_for_sell'] = FALSE;
  }
}

/**
 * Update stock of products in order.
 *
 * @param \Drupal\arch_order\Entity\OrderInterface $order
 *   Updated order.
 */
function _arch_stock_handle_stock_change_on_order_update(OrderInterface $order) {
  $old_status = $order->original->get('status')->getValue()[0]['value'];
  $new_status = $order->get('status')->getValue()[0]['value'];
  // If order status has not changed wo do nothing.
  if ($old_status == $new_status) {
    return;
  }

  // If the order not completed yet, wo do nothing.
  if ($new_status != 'completed') {
    return;
  }

  // If save_stock_changes flag is empty stock was already reduced.
  if (!$order->getDataKey('stock__save_stock_changes')) {
    return;
  }

  /** @var \Drupal\Core\Session\AccountInterface $user */
  $user = $order->getOwner();
  /** @var \Drupal\arch_stock\StockKeeperInterface $stock_keeper */
  $stock_keeper = \Drupal::service('arch_stock.stock_keeper');
  foreach ($order->getProducts() as $line_item) {
    /** @var Drupal\arch_order\Plugin\Field\FieldType\OrderLineItemFieldItem $line_item */
    if (!$line_item->isProduct()) {
      continue;
    }
    /** @var \Drupal\arch_product\Entity\ProductInterface $product */
    $product = $line_item->getProduct();
    if (!$product) {
      continue;
    }
    if ($stock_keeper->isProductManagingStock($product)) {
      $amount = $line_item->getQuantity();
      $stock_keeper->reduceStock($product, $amount, $order, $user);
    }
  }
  $order->setDataKey('stock__save_stock_changes', NULL);
}

/**
 * Add stock field to product type.
 *
 * @param \Drupal\arch_product\Entity\ProductTypeInterface $product_type
 *   Product type.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _arch_stock_add_stock_field(ProductTypeInterface $product_type) {
  $definition = [
    'name' => 'stock',
    'form_display' => [
      'type' => 'stock_default',
    ],
    'display' => [
      'default' => [
        'label' => 'hidden',
        'type' => 'stock_default',
      ],
      'teaser' => [
        'label' => 'hidden',
        'type' => 'stock_default',
      ],
    ],
  ];
  // Add or remove the description field, as needed.
  $field_storage = FieldStorageConfig::loadByName('product', $definition['name']);
  if (!$field_storage) {
    $field_storage = FieldStorageConfig::create([
      'status' => TRUE,
      'dependencies' => [
        'module' => [
          'arch_stock',
          'arch_product',
        ],
      ],
      'id' => 'product.stock',
      'field_name' => 'stock',
      'entity_type' => 'product',
      'type' => 'stock',
      'module' => 'arch_stock',
      'locked' => TRUE,
      'cardinality' => -1,
      'translatable' => FALSE,
    ]);
    $field_storage->save();
  }
  $field = FieldConfig::loadByName('product', $product_type->id(), $definition['name']);
  if (empty($field)) {
    $field = FieldConfig::create([
      'label' => 'Stock',
      'field_storage' => $field_storage,
      'bundle' => $product_type->id(),
    ]);
    $field->save();

    $entity_form_display = \Drupal::entityTypeManager()->getStorage('entity_form_display')
      ->load('product.' . $product_type->id() . '.default');
    if (empty($entity_form_display)) {
      $entity_form_display = EntityFormDisplay::create([
        'targetEntityType' => 'product',
        'bundle' => $product_type->id(),
        'mode' => 'default',
        'status' => TRUE,
      ]);
    }

    // Assign widget settings for the 'default' form mode.
    $entity_form_display
      ->setComponent($definition['name'], $definition['form_display'])
      ->save();

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
    $view_modes = \Drupal::service('entity_display.repository')->getViewModes('product');
    // Assign display settings for the 'default' and 'teaser' view modes.
    foreach ($definition['display'] as $view_mode => $config) {
      if (isset($view_modes[$view_mode]) || 'default' == $view_mode) {
        $entity_display = \Drupal::entityTypeManager()->getStorage('entity_view_display')
          ->load('product.' . $product_type->id() . '.' . $view_mode);
        if (!$entity_display) {
          $entity_display = EntityViewDisplay::create([
            'targetEntityType' => 'product',
            'bundle' => $product_type->id(),
            'mode' => $view_mode,
            'status' => TRUE,
          ]);
        }
        $entity_display
          ->setComponent($definition['name'], $definition['display'][$view_mode])
          ->save();
      }
    }
  }
}

/**
 * Remove stock field from product type.
 *
 * @param \Drupal\arch_product\Entity\ProductTypeInterface $product_type
 *   Product type.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _arch_stock_remove_stock_field(ProductTypeInterface $product_type) {
  $field = FieldConfig::loadByName('product', $product_type->id(), 'stock');
  if ($field) {
    $field->delete();
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function arch_stock_form_arch_cart_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if (empty($form['#cart'])) {
    return;
  }
  /** @var \Drupal\arch_cart\Cart\CartInterface $cart */
  $cart = $form['#cart'];
  /** @var \Drupal\arch_stock\StockKeeperInterface $stock_keeper */
  $stock_keeper = \Drupal::service('arch_stock.stock_keeper');

  /** @var \Drupal\Core\Session\AccountInterface $account */
  $account = \Drupal::currentUser();
  foreach ($cart->getProducts() as $item) {
    $item_id = $item['type'] . ':' . $item['id'];
    /** @var \Drupal\arch_product\Entity\ProductInterface $product */
    if (empty($form['products'][$item_id]['#product'])) {
      continue;
    }
    $product = $form['products'][$item_id]['#product'];
    if (
      $stock_keeper->isProductManagingStock($product)
      && !$stock_keeper->isNegativeStockAllowed($product, $account)
    ) {
      $form['products'][$item_id]['quantity']['#max'] = $stock_keeper->getTotalProductStock($product, $account);
    }
  }
}

/**
 * Implements hook_api_cart_data_alter().
 */
function arch_stock_api_cart_data_alter(array &$data, CartInterface $cart) {
  /** @var \Drupal\arch_stock\StockKeeperInterface $stock_keeper */
  $stock_keeper = \Drupal::service('arch_stock.stock_keeper');

  /** @var \Drupal\arch_product\Entity\Storage\ProductStorageInterface $product_storage */
  $product_storage = \Drupal::service('entity_type.manager')->getStorage('product');

  /** @var \Drupal\Core\Session\AccountInterface $account */
  $account = \Drupal::currentUser();
  foreach ($cart->getProducts() as $key => $item) {
    /** @var \Drupal\arch_product\Entity\ProductInterface $product */
    $product = $product_storage->load($item['id']);
    if (!$product) {
      continue;
    }
    if (
      $stock_keeper->isProductManagingStock($product)
      && !$stock_keeper->isNegativeStockAllowed($product, $account)
    ) {
      $data['cart']['items'][$key]['_line_item']['max_quantity'] = $stock_keeper->getTotalProductStock($product, $account);
    }
  }
}

/**
 * Implements hook_arch_order_create_from_cart_data_alter().
 */
function arch_stock_arch_order_create_from_cart_data_alter(array &$data, CartInterface $cart) {
  /** @var \Drupal\arch_stock\StockKeeperInterface $stock_keeper */
  $stock_keeper = \Drupal::service('arch_stock.stock_keeper');

  /** @var \Drupal\arch_product\Entity\Storage\ProductStorageInterface $product_storage */
  $product_storage = \Drupal::service('entity_type.manager')->getStorage('product');

  /** @var \Drupal\Core\Session\AccountInterface $account */
  $account = \Drupal::currentUser();
  foreach ($data['line_items'] as $index => $line_item) {
    if ($line_item['type'] != OrderLineItemInterface::ORDER_LINE_ITEM_TYPE_PRODUCT) {
      continue;
    }

    $remove = FALSE;

    /** @var \Drupal\arch_product\Entity\ProductInterface $product */
    $product = $product_storage->load($line_item['product_id']);
    if (
      !$product
      || empty($line_item['quantity'])
    ) {
      $remove = TRUE;
    }
    elseif (
      $stock_keeper->isProductManagingStock($product)
      && !$stock_keeper->hasProductEnoughStock($product, $account)
    ) {
      $remove = TRUE;
    }

    if ($remove) {
      unset($data['line_items'][$index]);
    }
  }
}

/**
 * Implements hook_arch_cart_change().
 */
function arch_stock_arch_cart_change($type, &$item, &$old_item, array &$items, CartInterface $cart) {
  if (in_array($type, [CartInterface::ITEM_NEW, CartInterface::ITEM_UPDATE])) {
    /** @var \Drupal\arch_stock\StockKeeperInterface $stock_keeper */
    $stock_keeper = \Drupal::service('arch_stock.stock_keeper');
    $current_user = \Drupal::currentUser();
    foreach ($items as &$item) {
      if (empty($item['type']) || $item['type'] !== 'product') {
        continue;
      }
      /** @var \Drupal\arch_product\Entity\ProductInterface $product */
      $product = Product::load($item['id']);
      if (!$product) {
        continue;
      }
      if (
        !$stock_keeper->isProductManagingStock($product)
        || $stock_keeper->isNegativeStockAllowed($product, $current_user)
      ) {
        continue;
      }
      $total = $stock_keeper->getTotalProductStock($product, $current_user);
      if ($item['quantity'] > $total) {
        $item['quantity'] = $total;
        $cart->addMessage(t('We have only %amount of this product', ['%amount' => $total], ['context' => 'arch_stock']));
      }
    }
  }

  // @todo Implement following stock changes.
  // @todo Implement Stock lock when customer add product to cart.
  // /** @var \Drupal\arch_stock\StockCartInfoInterface $cart_stock_info */
  // @codingStandardsIgnoreStart
  // $cart_stock_info = \Drupal::service('stock_cart.info');
  // if ($type === CartInterface::ITEM_NEW) {
  //   $cart_stock_info->addItem($item['id'], $item['quantity']);
  // }
  // elseif ($type === CartInterface::ITEM_UPDATE) {
  //   $cart_stock_info->updateItem($item['id'], $item['quantity']);
  // }
  // elseif ($type === CartInterface::ITEM_REMOVE) {
  //   $cart_stock_info->removeItem($old_item['id'], $old_item['quantity']);
  // }
  // @codingStandardsIgnoreEnd
}

/**
 * Implements hook_cron().
 */
function arch_stock_cron() {
  // @todo Implement Stock lock release.
  // /** @var \Drupal\arch_stock\StockCartInfoInterface $cart_stock_info */
  // $cart_stock_info = \Drupal::service('stock_cart.info');
  // $cart_stock_info->garbageCollection();
}

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

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