refactor(movements): normalize snapshot convention to mutate-then-write

This commit is contained in:
2026-06-25 03:11:15 +02:00
parent 575cd2d9a0
commit 95c52579d1
3 changed files with 52 additions and 40 deletions
+5 -5
View File
@@ -61,15 +61,15 @@ function toLegacyMovementType(type: InventoryMovementType) {
return type return type
} }
function getStockSnapshot(currentStock: number, stockDelta: number) { function getStockSnapshotFromAfter(afterStock: number, stockDelta: number) {
if (stockDelta < 0) { if (stockDelta < 0) {
return { return {
previousStock: currentStock + Math.abs(stockDelta), previousStock: afterStock + Math.abs(stockDelta),
newStock: currentStock, newStock: afterStock,
} }
} }
const previousStock = Math.max(currentStock - stockDelta, 0) const previousStock = Math.max(afterStock - stockDelta, 0)
return { return {
previousStock, previousStock,
@@ -219,7 +219,7 @@ export const MovementService = {
create: { create: {
itemId, itemId,
stockDelta, stockDelta,
...getStockSnapshot(item.stock, stockDelta), ...getStockSnapshotFromAfter(item.stock, stockDelta),
}, },
} }
: undefined, : undefined,
+35 -35
View File
@@ -173,6 +173,10 @@ export async function createAssetUseCase(
) )
: null : null
if (status === "AVAILABLE") {
await ItemService.updateStock(itemId, 1, tx)
}
await MovementService.create( await MovementService.create(
{ {
itemId, itemId,
@@ -186,10 +190,6 @@ export async function createAssetUseCase(
tx, tx,
) )
if (status === "AVAILABLE") {
await ItemService.updateStock(itemId, 1, tx)
}
return { return {
success: true, success: true,
assetId: newAsset.id, assetId: newAsset.id,
@@ -290,6 +290,37 @@ export async function updateAssetUseCase(
tx, tx,
) )
const shouldIncrementNextItemStock =
transition.willBeAvailable &&
(!transition.wasAvailable || transition.itemChanged)
const shouldDecrementPreviousItemStock =
transition.wasAvailable &&
(!transition.willBeAvailable || transition.itemChanged)
if (shouldIncrementNextItemStock) {
await ItemService.updateStock(transition.nextItemId, 1, tx)
}
if (shouldDecrementPreviousItemStock) {
if (!transition.previousItemId) {
throw new AssetTransitionError({
itemId: ["Previous item not found for available asset"],
})
}
const stockWasDecremented = await ItemService.decrementStockIfAvailable(
transition.previousItemId,
1,
tx,
)
if (!stockWasDecremented) {
throw new AssetTransitionError({
stock: ["Item does not have enough stock"],
})
}
}
let closedActiveAssignment = false let closedActiveAssignment = false
if (transition.activeAssignment && !transition.willBeAssigned) { if (transition.activeAssignment && !transition.willBeAssigned) {
@@ -325,13 +356,6 @@ export async function updateAssetUseCase(
closedActiveAssignment = true closedActiveAssignment = true
} }
const shouldIncrementNextItemStock =
transition.willBeAvailable &&
(!transition.wasAvailable || transition.itemChanged)
const shouldDecrementPreviousItemStock =
transition.wasAvailable &&
(!transition.willBeAvailable || transition.itemChanged)
if ( if (
transition.statusChanged && transition.statusChanged &&
!transition.hasPerson && !transition.hasPerson &&
@@ -422,30 +446,6 @@ export async function updateAssetUseCase(
) )
} }
if (shouldIncrementNextItemStock) {
await ItemService.updateStock(transition.nextItemId, 1, tx)
}
if (shouldDecrementPreviousItemStock) {
if (!transition.previousItemId) {
throw new AssetTransitionError({
itemId: ["Previous item not found for available asset"],
})
}
const stockWasDecremented = await ItemService.decrementStockIfAvailable(
transition.previousItemId,
1,
tx,
)
if (!stockWasDecremented) {
throw new AssetTransitionError({
stock: ["Item does not have enough stock"],
})
}
}
if (transition.willBeAssigned && transition.nextPersonId) { if (transition.willBeAssigned && transition.nextPersonId) {
const activeAssignment = transition.activeAssignment const activeAssignment = transition.activeAssignment
@@ -80,6 +80,8 @@ describe("asset use-cases", () => {
expect(movements[0].stockLines[0]).toMatchObject({ expect(movements[0].stockLines[0]).toMatchObject({
itemId: item.id, itemId: item.id,
stockDelta: 1, stockDelta: 1,
previousStock: 0,
newStock: 1,
}) })
expect(movements[0].assetLines[0]).toMatchObject({ expect(movements[0].assetLines[0]).toMatchObject({
assetId: result.assetId, assetId: result.assetId,
@@ -343,6 +345,12 @@ describe("asset use-cases", () => {
"ASSIGNMENT", "ASSIGNMENT",
"RETURN", "RETURN",
]) ])
expect(movements[0].stockLines[0]).toMatchObject({
itemId: item.id,
stockDelta: 1,
previousStock: 0,
newStock: 1,
})
expect(movements[1]).toMatchObject({ expect(movements[1]).toMatchObject({
assignmentId: activeAssignment.id, assignmentId: activeAssignment.id,
performedById: actor.id, performedById: actor.id,
@@ -350,6 +358,8 @@ describe("asset use-cases", () => {
expect(movements[1].stockLines[0]).toMatchObject({ expect(movements[1].stockLines[0]).toMatchObject({
itemId: item.id, itemId: item.id,
stockDelta: -1, stockDelta: -1,
previousStock: 1,
newStock: 0,
}) })
expect(movements[1].assetLines[0]).toMatchObject({ expect(movements[1].assetLines[0]).toMatchObject({
assetId: created.assetId, assetId: created.assetId,
@@ -361,6 +371,8 @@ describe("asset use-cases", () => {
expect(movements[2].stockLines[0]).toMatchObject({ expect(movements[2].stockLines[0]).toMatchObject({
itemId: item.id, itemId: item.id,
stockDelta: 1, stockDelta: 1,
previousStock: 0,
newStock: 1,
}) })
expect(movements[2].assetLines[0]).toMatchObject({ expect(movements[2].assetLines[0]).toMatchObject({
assetId: created.assetId, assetId: created.assetId,