refactor(assignments): move workflows into use cases

This commit is contained in:
2026-06-04 22:09:54 +02:00
parent 5034ec0646
commit e88fb2e6d4
14 changed files with 630 additions and 376 deletions
+341
View File
@@ -0,0 +1,341 @@
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 { ItemService } from "@/services/item.service"
import { MovementService } from "@/services/movement.service"
type FieldErrors = Record<string, string[]>
type CreateAssignmentUseCaseInput = CreateAssignmentData & {
actorId: string
}
type ReturnAssignmentUseCaseInput = {
id: string
actorId: string
}
type UpdateAssignmentUseCaseInput = UpdateAssignmentData & {
actorId: string
}
type CreateAssignmentUseCaseResult =
| {
success: true
assignmentId: string
}
| {
success: false
errors: FieldErrors
}
type ReturnAssignmentUseCaseResult =
| {
success: true
}
| {
success: false
errors: FieldErrors
}
type UpdateAssignmentUseCaseResult =
| {
success: true
}
| {
success: false
errors: FieldErrors
}
function createAssignmentError(
errors: FieldErrors,
): CreateAssignmentUseCaseResult {
return {
success: false,
errors,
}
}
function returnAssignmentError(
errors: FieldErrors,
): ReturnAssignmentUseCaseResult {
return {
success: false,
errors,
}
}
function updateAssignmentError(
errors: FieldErrors,
): UpdateAssignmentUseCaseResult {
return {
success: false,
errors,
}
}
export async function createAssignmentUseCase(
input: CreateAssignmentUseCaseInput,
): Promise<CreateAssignmentUseCaseResult> {
const { actorId, itemId, assetId, quantity, recipientId } = input
if (!itemId || !recipientId || quantity <= 0) {
return createAssignmentError({ error: ["Invalid assignment data"] })
}
return prisma.$transaction(async (tx) => {
const item = await ItemService.findById(itemId, tx)
if (!item) {
return createAssignmentError({ itemId: ["Item not found"] })
}
if (item.stock < quantity) {
return createAssignmentError({
quantity: ["Item does not have enough stock"],
})
}
if (assetId) {
const asset = await AssetService.findById(assetId, tx)
if (!asset) {
return createAssignmentError({ assetId: ["Asset not found"] })
}
if (asset.itemId !== item.id) {
return createAssignmentError({
assetId: ["Asset does not belong to item"],
})
}
}
const stockWasDecremented = await ItemService.decrementStockIfAvailable(
itemId,
quantity,
tx,
)
if (!stockWasDecremented) {
return createAssignmentError({
quantity: ["Item does not have enough stock"],
})
}
if (assetId) {
await AssetService.update(
assetId,
{
status: "ASSIGNED",
},
tx,
)
}
const createdAssignment = await AssignmentService.create(
{
itemId,
assetId: assetId || undefined,
quantity,
recipientId,
notes: input.notes,
assignmentDate: input.assignmentDate,
createdBy: actorId,
},
tx,
)
await MovementService.create(
{
itemId,
assetId: assetId || undefined,
quantity,
type: "ASSIGNMENT",
recipientId,
assignmentId: createdAssignment.id,
userId: actorId,
},
tx,
)
return {
success: true,
assignmentId: createdAssignment.id,
}
})
}
export async function updateAssignmentUseCase(
input: UpdateAssignmentUseCaseInput,
): Promise<UpdateAssignmentUseCaseResult> {
const {
actorId,
id,
recipientId,
itemId,
assetId,
quantity,
notes,
assignmentDate,
} = input
if (!id) {
return updateAssignmentError({ id: ["Assignment ID is required"] })
}
return prisma.$transaction(async (tx) => {
const assignment = await AssignmentService.findById(id, tx)
if (!assignment) {
return updateAssignmentError({ id: ["Assignment not found"] })
}
if (itemId) {
const item = await ItemService.findById(itemId, tx)
if (!item) {
return updateAssignmentError({ itemId: ["Item not found"] })
}
}
if (assetId) {
const asset = await AssetService.findById(assetId, tx)
if (!asset) {
return updateAssignmentError({ assetId: ["Asset not found"] })
}
if (asset.itemId !== itemId) {
return updateAssignmentError({
assetId: ["Asset does not belong to item"],
})
}
}
if (assignment.recipientId !== recipientId) {
await MovementService.create(
{
type: "RETURN",
quantity: assignment.quantity || 1,
itemId: assignment.itemId || undefined,
assetId: assignment.assetId || undefined,
recipientId: assignment.recipientId || undefined,
assignmentId: id,
userId: actorId,
},
tx,
)
await MovementService.create(
{
type: "ASSIGNMENT",
quantity,
itemId,
assetId: assetId || undefined,
recipientId,
assignmentId: id,
userId: actorId,
},
tx,
)
await AssignmentService.update(
id,
{
createdBy: actorId,
recipientId,
itemId,
assetId,
quantity,
notes,
assignmentDate,
returnDate: null,
},
tx,
)
} else {
await AssignmentService.update(
id,
{
recipientId,
itemId,
assetId,
quantity,
notes,
assignmentDate,
returnDate: null,
},
tx,
)
}
return {
success: true,
}
})
}
export async function returnAssignmentUseCase(
input: ReturnAssignmentUseCaseInput,
): Promise<ReturnAssignmentUseCaseResult> {
const { id, actorId } = input
if (!id) {
return returnAssignmentError({ id: ["Assignment ID is required"] })
}
return prisma.$transaction(async (tx) => {
const assignment = await AssignmentService.findById(id, tx)
if (!assignment) {
return returnAssignmentError({ id: ["Assignment not found"] })
}
if (assignment.returnDate) {
return returnAssignmentError({ id: ["Assignment already returned"] })
}
const assignmentWasReturned = await AssignmentService.markReturnedIfActive(
id,
tx,
)
if (!assignmentWasReturned) {
return returnAssignmentError({ id: ["Assignment already returned"] })
}
if (assignment.itemId && assignment.quantity) {
await ItemService.updateStock(assignment.itemId, assignment.quantity, tx)
}
if (assignment.assetId) {
await AssetService.update(
assignment.assetId,
{
status: "AVAILABLE",
},
tx,
)
}
await MovementService.create(
{
type: "RETURN",
quantity: assignment.quantity || 1,
itemId: assignment.itemId || undefined,
assetId: assignment.assetId || undefined,
assignmentId: id,
userId: actorId,
},
tx,
)
return {
success: true,
}
})
}