Inventory Adjustments: quantity counts, availability, and allocation
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
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion