feat(assignments): close and reopen assignment on person swap

This commit is contained in:
2026-06-25 17:20:24 +02:00
parent b4b63e107a
commit 18e274ef37
5 changed files with 472 additions and 78 deletions
+103 -39
View File
@@ -1,3 +1,4 @@
import { type Prisma } from "@/generated/prisma/client"
import prisma from "@/lib/prisma"
import type {
CreateAssignmentData,
@@ -8,6 +9,7 @@ import { AssignmentService } from "@/services/assignment.service"
import { ItemService } from "@/services/item.service"
import { MovementService } from "@/services/movement.service"
import type {
Assignment,
AssignmentStockLineInput,
AssignmentStockReturnInput,
} from "@/types"
@@ -123,6 +125,98 @@ function resolveAssignmentLine(
}
}
export type ReassignAssignmentArgs = {
oldAssignment: Assignment
newPersonId: string
newItemId?: string
newAssetId?: string
newQuantity: number
newNotes?: string
actorId: string
tx: Prisma.TransactionClient
}
export async function reassignAssignment(
args: ReassignAssignmentArgs,
): Promise<{ newAssignment: Assignment }> {
const {
oldAssignment,
newPersonId,
newItemId,
newAssetId,
newQuantity,
newNotes,
actorId,
tx,
} = args
const returnedQuantity = oldAssignment.quantity ?? 1
// 1. Close old assignment (handles both QUANTITY and SERIALIZED via markReturnedIfActive)
await AssignmentService.markReturnedIfActive(oldAssignment.id, actorId, tx)
// 2. Mutate item.stock to restore the returned quantity, then write RETURN movement
// Skip for SERIALIZED items (stock is constrained to 0 and is not affected by assignments).
if (oldAssignment.itemId) {
const oldItem = await ItemService.findById(oldAssignment.itemId, tx)
if (oldItem && oldItem.trackingType === "QUANTITY") {
await ItemService.updateStock(
oldAssignment.itemId,
returnedQuantity,
tx,
)
}
}
await MovementService.create(
{
type: "RETURN",
quantity: returnedQuantity,
itemId: oldAssignment.itemId || undefined,
assetId: oldAssignment.assetId || undefined,
personId: oldAssignment.personId || undefined,
assignmentId: oldAssignment.id,
userId: actorId,
},
tx,
)
// 3. Create new assignment for the new person
const newAssignment = await AssignmentService.create(
{
personId: newPersonId,
itemId: newItemId,
assetId: newAssetId,
quantity: newQuantity,
notes: newNotes,
assignmentDate: new Date(),
createdBy: actorId,
},
tx,
)
// 4. Mutate item.stock to deduct the new assigned quantity, then write ASSIGNMENT movement
if (newItemId) {
const newItem = await ItemService.findById(newItemId, tx)
if (newItem && newItem.trackingType === "QUANTITY") {
await ItemService.updateStock(newItemId, -newQuantity, tx)
}
}
await MovementService.create(
{
type: "ASSIGNMENT",
quantity: newQuantity,
itemId: newItemId,
assetId: newAssetId,
personId: newPersonId,
assignmentId: newAssignment.id,
userId: actorId,
},
tx,
)
return { newAssignment }
}
export async function createAssignmentUseCase(
input: CreateAssignmentUseCaseInput,
): Promise<CreateAssignmentUseCaseResult> {
@@ -276,46 +370,16 @@ export async function updateAssignmentUseCase(
}
if (assignment.personId !== personId) {
await MovementService.create(
{
type: "RETURN",
quantity: assignment.quantity || 1,
itemId: assignment.itemId || undefined,
assetId: assignment.assetId || undefined,
personId: assignment.personId || undefined,
assignmentId: id,
userId: actorId,
},
await reassignAssignment({
oldAssignment: assignment,
newPersonId: personId,
newItemId: itemId,
newAssetId: assetId,
newQuantity: quantity,
newNotes: notes,
actorId,
tx,
)
await MovementService.create(
{
type: "ASSIGNMENT",
quantity,
itemId,
assetId: assetId || undefined,
personId,
assignmentId: id,
userId: actorId,
},
tx,
)
await AssignmentService.update(
id,
{
createdBy: actorId,
personId: personId,
itemId,
assetId,
quantity,
notes,
assignmentDate,
returnDate: null,
},
tx,
)
})
} else {
if (item && assignment.quantity !== quantity) {
const stockDelta = quantity - (assignment.quantity ?? 0)