inventory v1

Stock per SKU. A delivery adds to it, an order holds what it takes for 30 minutes, shipping posts the hold, cancelling voids it, and a return puts the goods back on the shelves

examples/inventory/inventory.book, as chobo doc writes it. Each account keeps a balance: what came in, less what went out. Every transfer keeps the bounds of the accounts: one that would break a bound is refused with the name the book gives that bound, and nothing of it moves. A transfer either moves at once (do), or first holds what it moves (hold); a hold is then posted (post, all of it or part), voided (void), or expires.

Accounts

AccountOne for eachUnitBoundsWhat it is
stockskupcsat least 0; a transfer that would go below is refused with out_of_stockwhat is on the shelves
suppliersone accountpcsoutside the book: no bounds, and it may go below 0
customersone accountpcsoutside the book: no bounds, and it may go below 0

How things move

How things move between the accounts of inventory receive reserve take_back stock(sku)pcsat least 0 (out_of_stock) supplierspcs · outside customerspcs · outside

A box is an account, and an arrow a move of a transfer. A rounded, dashed box is an account outside the book, which has no bounds. A dashed arrow belongs to a transfer that holds first, and moves when the hold is posted.

Transfers

receive

  • Moves qty from suppliers to stock(sku).
  • Key: once per delivery and sku. The same call again does nothing, and answers done_before; one that differs only in qty is refused with key_conflict.
  • Moves at once (do).
OperationMay be refused withWhen
receive.dokey_conflicta call with the same delivery and sku and other arguments came before
How each refusal comes about

receive.do: key_conflict

 1  receive.do(delivery: delivery-1, sku: sku-2, qty: 1)  done
 2  receive.do(delivery: delivery-1, sku: sku-2, qty: 2)  refused: key_conflict

reserve

posted when the order ships, voided when it is cancelled

  • Moves qty from stock(sku) to customers.
  • Key: once per order and sku. The same call again does nothing, and answers done_before; one that differs only in qty is refused with key_conflict; holding again with the same key after the hold has ended answers done_before, and holds nothing.
  • Holds first: the caller posts or voids the hold. It expires 30 minutes after it was made, and what it holds goes back.
reserve.hold held post posted void is refused: already_posted post for other amounts: key_conflict void voided post is refused: already_voided after 30 minutes expired post and void are refused: expired
The holdpostvoid
heldposts it: the amounts given, or all of it, and the rest goes back; more than it holds is refused with over_hold; once 30 minutes have passed since it was held, refused with expiredvoids it: what it holds goes back; once 30 minutes have passed since it was held, refused with expired
posteddone_before with the same amounts; refused with key_conflict with othersrefused with already_posted
voidedrefused with already_voideddone_before
expiredrefused with expiredrefused with expired
no hold with the keyrefused with no_such_holdrefused with no_such_hold
OperationMay be refused withWhen
reserve.holdout_of_stockstock(sku) would go below 0
reserve.holdkey_conflicta call with the same order and sku and other arguments came before
reserve.holdalready_refuseda call with the same order and sku was refused by a bound before; a key a bound refused stays refused, even once there is enough
reserve.postkey_conflictthe hold was posted before, for other amounts
reserve.postalready_voidedthe hold is voided already
reserve.postexpiredthe hold has expired
reserve.postover_holdmore than the hold holds
reserve.postno_such_holdthere is no hold with that order and sku
reserve.voidalready_postedthe hold is posted already
reserve.voidexpiredthe hold has expired
reserve.voidno_such_holdthere is no hold with that order and sku
How each refusal comes about

reserve.hold: out_of_stock

 1  reserve.hold(order: order-1, sku: sku-2, qty: 1)  refused: out_of_stock (move 1 takes 1 from stock(sku-2): posted 0, held out 0)

reserve.hold: key_conflict

 1  receive.do(delivery: delivery-1, sku: sku-2, qty: 1)  done
 2  reserve.hold(order: order-3, sku: sku-2, qty: 1)      done
 3  reserve.hold(order: order-3, sku: sku-2, qty: 2)      refused: key_conflict

reserve.hold: already_refused

 1  reserve.hold(order: order-1, sku: sku-2, qty: 1)  refused: out_of_stock (move 1 takes 1 from stock(sku-2): posted 0, held out 0)
 2  reserve.hold(order: order-1, sku: sku-2, qty: 1)  refused: already_refused

reserve.post: key_conflict

 1  receive.do(delivery: delivery-1, sku: sku-2, qty: 2)  done
 2  reserve.hold(order: order-3, sku: sku-2, qty: 2)      done
 3  reserve.post(order: order-3, sku: sku-2)              done
 4  reserve.post(order: order-3, sku: sku-2, qty: 1)      refused: key_conflict

reserve.post: already_voided

 1  receive.do(delivery: delivery-1, sku: sku-2, qty: 2)  done
 2  reserve.hold(order: order-3, sku: sku-2, qty: 2)      done
 3  reserve.void(order: order-3, sku: sku-2)              done
 4  reserve.post(order: order-3, sku: sku-2)              refused: already_voided

reserve.post: expired

 1  receive.do(delivery: delivery-1, sku: sku-2, qty: 2)  done
 2  reserve.hold(order: order-3, sku: sku-2, qty: 2)      done
 3  pass 30 minutes                                       reserve(order-3, sku-2) expired
 4  reserve.post(order: order-3, sku: sku-2)              refused: expired

reserve.post: over_hold

 1  receive.do(delivery: delivery-1, sku: sku-2, qty: 2)  done
 2  reserve.hold(order: order-3, sku: sku-2, qty: 2)      done
 3  reserve.post(order: order-3, sku: sku-2, qty: 3)      refused: over_hold

reserve.post: no_such_hold

 1  reserve.post(order: order-1, sku: sku-2)  refused: no_such_hold

reserve.void: already_posted

 1  receive.do(delivery: delivery-1, sku: sku-2, qty: 2)  done
 2  reserve.hold(order: order-3, sku: sku-2, qty: 2)      done
 3  reserve.post(order: order-3, sku: sku-2)              done
 4  reserve.void(order: order-3, sku: sku-2)              refused: already_posted

reserve.void: expired

 1  receive.do(delivery: delivery-1, sku: sku-2, qty: 2)  done
 2  reserve.hold(order: order-3, sku: sku-2, qty: 2)      done
 3  pass 30 minutes                                       reserve(order-3, sku-2) expired
 4  reserve.void(order: order-3, sku: sku-2)              refused: expired

reserve.void: no_such_hold

 1  reserve.void(order: order-1, sku: sku-2)  refused: no_such_hold

take_back

what a customer sent back, on the shelves again

  • Moves qty from customers to stock(sku).
  • Key: once per return_id and sku. The same call again does nothing, and answers done_before; one that differs only in qty is refused with key_conflict.
  • Moves at once (do).
OperationMay be refused withWhen
take_back.dokey_conflicta call with the same return_id and sku and other arguments came before
How each refusal comes about

take_back.do: key_conflict

 1  take_back.do(return_id: return_id-1, sku: sku-2, qty: 1)  done
 2  take_back.do(return_id: return_id-1, sku: sku-2, qty: 2)  refused: key_conflict

Scenarios

21 scenarios, which chobo scenarios makes from the book. Pick one and step through it: after each step come the balances and the holds it left, and on the chart the arrows of the transfers it called light up (red when refused).

    AccountPostedHeld outHeld in
    HoldState
    How things move between the accounts of inventory receive reserve take_back stock(sku)pcsat least 0 (out_of_stock) supplierspcs · outside customerspcs · outside