feat(assignments): partial return schema, concurrency guard, domain error

This commit is contained in:
2026-06-25 21:02:46 +02:00
parent 9b023ee558
commit e4bd76a353
6 changed files with 365 additions and 88 deletions
+63 -59
View File
@@ -1,11 +1,14 @@
import { type Prisma } from "@/generated/prisma/client"
import type { Prisma } from "@/generated/prisma/client"
import prisma from "@/lib/prisma"
import type {
CreateAssignmentData,
UpdateAssignmentData,
} from "@/schemas/assignment.schema"
import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service"
import {
AssignmentService,
ConcurrentReturnError,
} from "@/services/assignment.service"
import { ItemService } from "@/services/item.service"
import { MovementService } from "@/services/movement.service"
import type {
@@ -160,11 +163,7 @@ export async function reassignAssignment(
if (oldAssignment.itemId) {
const oldItem = await ItemService.findById(oldAssignment.itemId, tx)
if (oldItem && oldItem.trackingType === "QUANTITY") {
await ItemService.updateStock(
oldAssignment.itemId,
returnedQuantity,
tx,
)
await ItemService.updateStock(oldAssignment.itemId, returnedQuantity, tx)
}
}
await MovementService.create(
@@ -338,10 +337,7 @@ export async function updateAssignmentUseCase(
return updateAssignmentError({ id: ["Assignment not found"] })
}
if (
assignment.status === "RETURNED" ||
assignment.status === "CANCELLED"
) {
if (assignment.status === "RETURNED" || assignment.status === "CANCELLED") {
return updateAssignmentError({ id: ["Assignment is closed"] })
}
@@ -427,60 +423,68 @@ export async function returnAssignmentUseCase(
return returnAssignmentError({ id: ["Assignment ID is required"] })
}
return prisma.$transaction(async (tx) => {
const assignment = await AssignmentService.findById(id, tx)
try {
return await prisma.$transaction(async (tx) => {
const assignment = await AssignmentService.findById(id, tx)
if (!assignment) {
return returnAssignmentError({ id: ["Assignment not found"] })
}
if (!assignment) {
return returnAssignmentError({ id: ["Assignment not found"] })
}
if (assignment.returnDate) {
return returnAssignmentError({ id: ["Assignment already returned"] })
}
if (assignment.returnDate) {
return returnAssignmentError({ id: ["Assignment already returned"] })
}
const returnResult = await AssignmentService.returnStockAssignment(
id,
actorId,
returns,
tx,
)
if (!returnResult) {
return returnAssignmentError({ error: ["Invalid assignment data"] })
}
for (const returnedLine of returnResult.returnedLines) {
await ItemService.updateStock(
returnedLine.itemId,
returnedLine.quantity,
const returnResult = await AssignmentService.returnStockAssignment(
id,
actorId,
returns,
tx,
)
await MovementService.create(
{
type: "RETURN",
quantity: returnedLine.quantity,
itemId: returnedLine.itemId,
assetId: assignment.assetId || undefined,
assignmentId: id,
userId: actorId,
},
tx,
)
if (!returnResult) {
return returnAssignmentError({ error: ["Invalid assignment data"] })
}
for (const returnedLine of returnResult.returnedLines) {
await ItemService.updateStock(
returnedLine.itemId,
returnedLine.quantity,
tx,
)
await MovementService.create(
{
type: "RETURN",
quantity: returnedLine.quantity,
itemId: returnedLine.itemId,
assetId: assignment.assetId || undefined,
assignmentId: id,
userId: actorId,
},
tx,
)
}
if (assignment.assetId) {
await AssetService.update(
assignment.assetId,
{
status: "AVAILABLE",
},
tx,
)
}
return {
success: true,
}
})
} catch (error) {
if (error instanceof ConcurrentReturnError) {
return returnAssignmentError({ error: ["errorConcurrent"] })
}
if (assignment.assetId) {
await AssetService.update(
assignment.assetId,
{
status: "AVAILABLE",
},
tx,
)
}
return {
success: true,
}
})
throw error
}
}