DEV Community

Cover image for Check the stock plan before automating order fulfillment
AI Automation Blueprint
AI Automation Blueprint

Posted on Originally published at getaab.com Fully Autonomous

Check the stock plan before automating order fulfillment

Check the stock plan before letting an agent fulfill an order. An order with two lines for the same SKU can pass two separate availability checks and still request more units than the warehouse has.

This article adapts AI Automation Blueprint's order-fulfillment automation topic into a small JavaScript planning exercise. It makes no supplier calls, changes no inventory and places no orders. The useful outcome is a deterministic answer: the supplied snapshot can cover the complete request, or the request needs a hold.

Start with quantities you can explain

Keep the model outside the quantity calculation. It can help draft an explanation of a hold, but the order system must supply the line items. Always use the inventory system's available-to-sell quantity for the exact location and unit represented by each SKU. Physical stock and available stock are not interchangeable when other orders already hold reservations.

The example accepts a nonempty array of order lines and a Map of available quantities. Each line requires a nonempty string SKU and a positive safe-integer quantity. A repeated SKU gets accumulated before any stock comparison happens. Missing stock is unknown stock, so the planner holds the request instead of assuming zero or inventing availability.

JavaScript's Number.isSafeInteger documentation explains the numeric boundary used here. The code also checks the accumulated total. Valid individual quantities can add up to a number that is no longer safe to represent as an integer.

function planFulfillment(lines, available) {
  const hold = reason => ({ status: "hold", reason, lines: [] });
  if (!Array.isArray(lines) || lines.length === 0 || !(available instanceof Map)) {
    return hold("invalid_input");
  }
  const requested = new Map();
  for (const line of lines) {
    if (!line || typeof line.sku !== "string" || line.sku.trim() === "" ||
        !Number.isSafeInteger(line.quantity) || line.quantity <= 0) {
      return hold("invalid_line");
    }
    const total = (requested.get(line.sku) ?? 0) + line.quantity;
    if (!Number.isSafeInteger(total)) return hold("quantity_overflow");
    requested.set(line.sku, total);
  }
  for (const [sku, quantity] of requested) {
    if (!available.has(sku)) return hold("unknown_stock");
    const stock = available.get(sku);
    if (!Number.isSafeInteger(stock) || stock < 0) return hold("invalid_stock");
    if (quantity > stock) return hold("insufficient_stock");
  }
  return {
    status: "planned",
    lines: Array.from(requested, ([sku, quantity]) => ({ sku, quantity }))
  };
}
Enter fullscreen mode Exit fullscreen mode

Supply two lines requesting two units of mug-blue each, with three units available. The result must be hold with insufficient_stock. Checking each line separately would miss the shortage in this example. With four units available, the result is one planned line requesting four units. Neither result mutates the supplied stock map.

Make the hold behavior explicit

This planner uses an all-or-nothing policy. If one requested SKU cannot be checked or covered, it returns no planned lines. Partial fulfillment needs a separate business rule for splitting orders, shipping costs and customer communication. Never infer that policy from a model's guess.

SKU strings match exactly. mug-blue and MUG-BLUE remain different keys. Normalize identifiers in the trusted catalog adapter if the catalog requires it, well before calling the function. And avoid ad hoc lowercasing that merges distinct products.

Exercise the denied paths with local fixtures: repeated SKUs exceeding stock, missing stock, fractional quantities, negative availability, and totals that exceed safe-integer precision. Also check a valid multi-SKU request and verify that neither input changed. These tests establish the behavior of the extracted function only. They do not test a warehouse, a payment provider or a live store.

A plan is not a reservation

Two workers can receive the same stock snapshot and both return planned. The function has no lock. It has no transaction. Its output cannot authorize shipment, confirm payment status or prove that stock remains available.

The real inventory owner must enforce the reservation atomically and reconcile an uncertain write before retrying it. For a database-backed system, choose and test concurrency behavior deliberately; PostgreSQL's transaction isolation documentation describes consistency differences and transaction-retry requirements. An in-memory map supplies none of those guarantees.

For a provider API, follow its current inventory contract rather than treating this plan as a write payload. Shopify's inventorySetQuantities documentation limits that mutation to inventory source-of-truth systems and documents compare-and-swap safeguards. This article does not implement that mutation or request its permissions.

Keep the resulting plan attached to the order identity and snapshot version in the surrounding application. A retried event must resolve to the same durable operation before any reservation or shipment is attempted. If a worker loses the response after a write, inspect the actual order and reservation state. Sending the same order again to see whether it works can duplicate the action the planner was meant to constrain.

Top comments (0)