Inventory Adjustments: quantity counts, availability, and allocation

Last updated on
20 August 2018

Inventory Adjustments were built to make full use of the entity architecture in Drupal. This means they are field-able, hook-able, able to be used in Views, etc. They are bundled by an Adjustment Type plugin-type, which allows modifications and validation before the adjustment is made – IE, if an Inventory Adjustment is made of a 'Decrease' Adjustment Type, the adjustment type makes sure that the quantity is negative before being saved to the database. Commerce Inventory then offers a few different Quantity Managers, which determine On-Hand, Available, and Minimum quantity counts. To extend that further, there is an Allocation manager which helps determine which inventory items are used and how many are used from each location.

Adjustment Type plugin

Most adjustment types (New, Return, Sell, Move-To, Move-From) are empty classes which simply extend the base Increase and Decrease adjustment types. But since the setup is easily-extendable, other, more-complex adjustments can be made. An example of this is the Manual adjustment-type:

/**
 * Provides an adjustment-type for manually adjusting to specific amount.
 *
 * @CommerceInventoryAdjustmentType(
 *   id = "manual",
 *   label = @Translation("Manual"),
 *   label_verb = @Translation("Manually adjust")
 * )
 */
class Manual extends InventoryAdjustmentTypeBase implements InventoryAdjustmentTypeInterface {

  /**
   * {@inheritdoc}
   */
  public function adjustQuantity($quantity, $current_quantity = NULL) {
    // Make sure new quantity positive.
    if ($quantity < 0) {
      $quantity = $quantity * -1;
    }
    // Clean current quantity.
    $current_quantity = (is_int($current_quantity) || is_float($current_quantity)) ? $current_quantity : 0;

    // Find quantity difference for adjustment.
    return $quantity - $current_quantity;
  }

}

Quantity Manager services

Quantity Managers implement the QuantityManagerInterface, which have methods to help handle retrieving and invalidating quantity on an Inventory Item. By default, Commerce Inventory sets up three quantity manager services: On-Hand, Available, and Minimum. 

On-Hand

The Quantity On-Hand Manager service calculates and caches an Inventory Item's true on-hand quantity based on previous inventory adjustments. It's unalterable and unaltered until another inventory adjustment is made. 

Available

The Quantity Available Manager service takes the Inventory Item's on-hand quantity from the Quantity On-Hand Manager service and allows adjustment via an AdjustQuantityAvailableEvent event. The adjustment is then merged with other adjustments and then cached. This allows quantity to be withheld on an item without making a full inventory adjustment. An example of this is in the Commerce Inventory: Order module, when the available quantity is adjusted based on Commerce Orders that have been created but not processed yet. 

Minimum

The Quantity Minimum Manager service defaults to 0, but provides a service resolver to allow other services resolve a different minimum. This allows instances where an Item can be preordered before an inventory is actually received, or when a store wants to stop selling an item online at a certain positive amount to leave the option for an in-store purchase.

Allocation Manager service

The allocation manager allows for a uniform inventory item quantity allocation based on purchasable entity and passed-in contexts. This is beneficial when needing to figure out how much of a purchasable entity is withheld or adjusted in multiple locations, yet one of the locations only has a portion of the amount requested. The manager implements an allocator (service resolver) and passes in the Purchasable Entity, the quantity requested, and an array of contextual information that is relevant to the allocation being requested. A basic example allocator is the StoreInventoryAllocator in the Commerce Inventory: Store module (this is a fallback allocator if this module is enabled but Commerce Inventory: Order is not installed. See the OrderItemInventoryAllocator in the Commerce Inventory: Order module for a more in-depth example).

/**
   * {@inheritdoc}
   */
  public function allocate(PurchasableEntityInterface $purchasable_entity, $quantity, array $context = []) {
    // Find Item IDs for this purchased entity and store, ordered by preferred
    // locations.
    $inventory_item_ids = $this->inventoryStoreManager->getStoreItemIds($purchasable_entity, $context['commerce_store']);

    $inventory_data = array_map(function ($value) {
      return ['inventory_item_id' => $value];
    }, $inventory_item_ids);

    $placement_array = $this->getInventoryAllocation($inventory_data, $quantity);
    $placement = new InventoryAllocation($placement_array);

    // Return placements if there are any.
    if (!empty($placement->toArray())) {
      return $placement;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function applies(PurchasableEntityInterface $purchasable_entity, array $context = []) {
    return (array_key_exists('commerce_store', $context) && $context['commerce_store'] instanceof Store);
  }

Help improve this page

Page status: No known problems

You can: