Set up a simple inventory control with Drupal Commerce 2

A stock of coffee box

If we do not have (yet) a contributed module (commerce_stock should be available soon), Drupal Commerce 2 already has an API to set up an inventory control, with the Availability Manager service.

This is a collector service that will collect any services declared on the commerce.availability_checker tag. Its operating principle is for the moment quite simple. If a service that implements the AvailabilityCheckerInterface Interface returns FALSE, the product will no longer be considered available.

Let's look at how to set up a very simple stock control, based on a field (for example field_stock) that has been added on a product.

From our MY_MODULE module, declare our service using the file MY_MODULE.services.yml.

services:

  my_module.availability_product_variation:
    class: Drupal\my_module\AvailabilityProductVariation
    arguments: ['@entity_type.manager', '@cerema_commerce.core']
    tags:
      - { name: commerce.availability_checker, priority: 100 }

Then declare our Class AvailabilityProductVariation.

<?php

namespace Drupal\my_module;

use Drupal\commerce\Context;
use Drupal\commerce\PurchasableEntityInterface;
use Drupal\commerce_product\Entity\ProductVariationInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\commerce\AvailabilityCheckerInterface;

/**
 * Provides an availability checker that removes variations that are no longer available.
 */
class AvailabilityProductVariation implements AvailabilityCheckerInterface {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;


  /**
   * Constructs a new AvailabilityOrderProcessor object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The availability manager.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public function applies(PurchasableEntityInterface $entity) {
    if ($entity->hasField('field_stock')) {
      $stock = $entity->field_stock->value;
      if (is_null($stock)) {
        return FALSE;
      }
      return TRUE;
    }
    return FALSE;
  }

  public function check(PurchasableEntityInterface $entity, $quantity, Context $context) {
    $stock = (integer) $entity->{CeremaCommerceInterface::STOCK_FIELD_NAME}->value;
    $quantity = (integer) $quantity;
    if ($quantity > $stock) {
      return FALSE;
    }
  }
  
}

This service must implement 2 methods:

  • applies() which will determine whether or not the entity must be verified
  • check() who will actually check the availability of the product.

With this simple service, we then have control over the products added to the Cart according to a dedicated Stock field.

All that remains is to adjust the stock for each product order by subscribing to events propagated by an order, including the event when an order is placed, commerce_order.place.post_transition and the event when an order is cancelled, commerce_order.cancel.post_transition, for example.

We can now also use the AvailabilityManager service directly on the add to cart form to react to the availability of a product on the add to cart button. For example, we can alter this form with these few lines that will disable the add to cart button and display a warning message below.

/** @var \Drupal\commerce\AvailabilityManagerInterface $availabiltyManager */
$availabiltyManager = \Drupal::service('commerce.availability_manager');

$entityTypeManager = \Drupal::entityTypeManager();
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $entityTypeManager->getStorage('commerce_order')->create(['type' => 'default']);
$store = $entityTypeManager->getStorage('commerce_store')->loadDefault();
$context = new \Drupal\commerce\Context($order->getCustomer(), $store);

if (!$availabiltyManager->check($purchased_entity, '1', $context)) {
  $form['actions']['submit']['#attributes']['disabled'] = TRUE;
  $form['actions']['submit']['#suffix'] = '<div class="description upper small out-stock">' . t('Out of stock') . '</div>';
}

It's also difficult to introduce simple stock management with Drupal Commerce 2 without mentioning a stock management module available on GitHub that looks promising and operational right now. But the generic track to be favored over the long term remains undoubtedly the contributed module commerce_stock as soon as it will be finalized.

Inventory management can vary infinitely on an e-commerce project. Should we block orders once the stock is finished, continue to record pending orders, while warning the customer? What to do under a certain limit reached? Send an alert to the manager? Show a message to visitors? If your business needs for inventory management remain standard, they will most likely be covered by the commerce stock module. And even if it were not the case, the API available will address the most atypical cases with the help of a Drupal 8 developper.

Finally, to conclude, do not hesitate to follow this issue Finalize the availability manager service which should introduce substantial modifications to the possibilities offered natively by this API.

 

Ajouter un commentaire