Skip to main content
Stock movements are an immutable audit log of every quantity change in your inventory. Every time stock is received or dispatched, a movement record is created. This gives you a complete, chronological history of how your inventory has changed over time.
The movements list is ordered newest first, so the most recent activity is always visible at the top.

Movement types

Each movement has one of two types:

IN

Stock received into inventory — for example, a delivery from a supplier or a returned item. Increments the product’s inventory quantity.

OUT

Stock dispatched or consumed — for example, a sale, a write-off, or internal use. Decrements the product’s inventory quantity.

How movements update inventory

When you record a movement, the backend creates the movement record and then immediately updates the linked inventory quantity in a single request. The update uses atomic increment/decrement operations:
router.post("/", async (req: Request, res: Response) => {
  const { productId, type, quantity, reason } = req.body

  const movement = await prisma.stockMovement.create({
    data: {
      productId,
      type,
      quantity,
      reason,
      tenantId: req.tenantId!,
    },
  })

  // Update inventory
  if (type === "IN") {
    await prisma.inventory.update({
      where: { productId },
      data: { quantity: { increment: quantity } },
    })
  } else if (type === "OUT") {
    await prisma.inventory.update({
      where: { productId },
      data: { quantity: { decrement: quantity } },
    })
  }

  res.json(movement)
})
This means you never need to manually update an inventory record when stock changes — recording a movement handles it automatically.

Stock movement data model

The StockMovement model in the database schema:
model StockMovement {
  id        String   @id @default(uuid())
  productId String
  product   Product  @relation(fields: [productId], references: [id], onDelete: Cascade)
  type      String   @enum(["IN", "OUT"])
  quantity  Int
  reason    String?
  tenantId  String
  tenant    Tenant   @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

enum MovementType {
  IN
  OUT
}
The reason field is optional but strongly recommended for traceability.

Viewing the movement log

Navigate to Stock Movements in the sidebar. The page displays a table of all movements sorted by createdAt descending, with columns for Product, Type, Quantity, Date, and Reason. Movements are fetched with the product relation included:
const movements = await prisma.stockMovement.findMany({
  where: { tenantId: req.tenantId },
  include: { product: true },
  orderBy: { createdAt: "desc" },
})

Recording a new movement

1

Open the stock movements page

Click Stock Movements in the sidebar.
2

Select a product

Choose the product whose stock is changing from the Product dropdown. Only products in your catalog are shown.
3

Choose the movement type

Select IN if stock is being added, or OUT if stock is being removed.
4

Enter the quantity

Enter the number of units being moved. This must be a positive integer.
5

Add a reason (optional)

Describe why the stock is changing. This is stored in the reason field and appears in the movement log.
6

Submit the movement

Click Save. The movement is recorded and the linked inventory quantity is updated immediately.
Always fill in the reason field. Examples like "received from supplier", "sold to customer", or "damaged in transit" make it easy to understand your inventory history months later.

API reference

MethodEndpointDescription
GET/api/stock-movementsList all movements, newest first
POST/api/stock-movementsRecord a new movement and update inventory
All endpoints require a valid JWT in the Authorization: Bearer <token> header.

Build docs developers (and LLMs) love