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
+118
View File
@@ -0,0 +1,118 @@
"use server"
import { revalidatePath } from "next/cache"
import { getAuthenticatedUserId } from "@/services/auth.service"
import {
createAssignmentUseCase,
returnAssignmentUseCase,
updateAssignmentUseCase,
} from "@/use-cases/assignment.use-cases"
import {
assignmentSchema,
type CreateAssignmentFormType,
type ReturnAssignmentFormType,
type UpdateAssignmentFormType,
updateAssignmentSchema,
} from "@/schemas/assignment.schema"
export async function createAssignment(formData: CreateAssignmentFormType) {
const createdBy = await getAuthenticatedUserId()
const validatedFields = assignmentSchema.safeParse({
...formData,
createdBy,
})
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await createAssignmentUseCase({
...validatedFields.data,
actorId: createdBy,
})
if (!result.success) {
return result
}
revalidatePath("/assignments")
return {
success: true,
message: "Assignment created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error creating assignment"] },
}
}
}
export async function updateAssignment(formData: UpdateAssignmentFormType) {
const validatedFields = updateAssignmentSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const createdBy = await getAuthenticatedUserId()
const result = await updateAssignmentUseCase({
...validatedFields.data,
actorId: createdBy,
})
if (!result.success) {
return result
}
revalidatePath("/assignments")
return {
success: true,
message: "Assignment updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error updating assignment"] },
}
}
}
export async function returnAssignment(formData: ReturnAssignmentFormType) {
const { id } = formData
const userId = await getAuthenticatedUserId()
const result = await returnAssignmentUseCase({
id,
actorId: userId,
})
if (!result.success) {
return {
...result,
message: "Error returning assignment",
}
}
revalidatePath("/assignments")
return {
success: true as const,
message: "Assignment returned successfully",
}
}
@@ -1,18 +1,18 @@
import type { UpdateAssignmentFormType } from "@/lib/schemas/assignment.schemas"
import type { Item } from "@/lib/types"
import type { UpdateAssignmentFormType } from "@/schemas/assignment.schema"
import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service"
import { ItemService } from "@/services/item.service"
import { RecipientService } from "@/services/recipient.service"
import type { Item } from "@/types"
import AssignmentForm from "../../_components/edit.assignment.form"
export default async function EditAssignmentPage({
params,
}: {
params: Promise<{ assignamentId: string }>
params: Promise<{ assignmentId: string }>
}) {
const { assignamentId } = await params
const assignment = await AssignmentService.findById(assignamentId)
const { assignmentId } = await params
const assignment = await AssignmentService.findById(assignmentId)
const recipients = await RecipientService.findAll()
const items = await ItemService.findAllWithStock()
const assets = await AssetService.findAll()
@@ -4,14 +4,13 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateAssignment } from "@/actions/assignment.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { updateAssignment } from "@/lib/actions/assignament.actions"
import {
type UpdateAssignmentFormType,
updateAssignmentSchema,
} from "@/lib/schemas/assignment.schemas"
import type { Asset, Item, Recipient } from "@/lib/types"
} from "@/schemas/assignment.schema"
import type { Asset, Item, Recipient } from "@/types"
interface Props {
recipients: Recipient[]
@@ -5,14 +5,13 @@ import { useRouter } from "next/navigation"
import { useMemo } from "react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createAssignment } from "@/actions/assignment.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { createAssignment } from "@/lib/actions/assignament.actions"
import {
type CreateAssignmentFormType,
createAssignmentSchema,
} from "@/lib/schemas/assignment.schemas"
import type { Asset, Item, Recipient } from "@/lib/types"
} from "@/schemas/assignment.schema"
import type { Asset, Item, Recipient } from "@/types"
interface Props {
recipients: Recipient[]
@@ -4,10 +4,9 @@ import { ArrowLeft } from "lucide-react"
import { useRouter } from "next/navigation"
import { useTransition } from "react"
import { toast } from "sonner"
import { returnAssignment } from "@/actions/assignment.actions"
import { Button } from "@/components/ui/button"
import { returnAssignment } from "@/lib/actions/assignament.actions"
import type { ReturnAssignmentFormType } from "@/lib/schemas/assignment.schemas"
import type { ReturnAssignmentFormType } from "@/schemas/assignment.schema"
export default function ReturnButton({
assignmentId,
+7 -1
View File
@@ -46,6 +46,9 @@ export default async function AssignmentsPage(props: {
<th scope="col" className="p-4">
Serial Number
</th>
<th scope="col" className="p-4">
Quantity
</th>
<th scope="col" className="p-4">
Actions
</th>
@@ -74,6 +77,9 @@ export default async function AssignmentsPage(props: {
<td className="p-4">
{assignment?.asset?.serialNumber || "N/A"}
</td>
<td className="p-4">
{assignment?.quantity}
</td>
<td className="p-4">
<div className="flex gap-2">
<Link
@@ -92,7 +98,7 @@ export default async function AssignmentsPage(props: {
</tbody>
<tfoot className="border-t">
<tr>
<td colSpan={4} className="p-4 text-center text-sm">
<td colSpan={5} className="p-4 text-center text-sm">
<PaginationButtons totalPages={totalPages} />
</td>
</tr>
-298
View File
@@ -1,298 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import prisma from "@/lib/prisma"
import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service"
import { getAuthenticatedUserId } from "@/services/auth.service"
import { ItemService } from "@/services/item.service"
import { MovementService } from "@/services/movement.service"
import {
assignmentSchema,
type CreateAssignmentFormType,
type ReturnAssignmentFormType,
type UpdateAssignmentFormType,
updateAssignmentSchema,
} from "../schemas/assignment.schemas"
export async function getAssignments() {
return await AssignmentService.findAllWithRecipient()
}
export async function getAssignmentById(id: string) {
return await AssignmentService.findById(id)
}
export async function createAssignment(formData: CreateAssignmentFormType) {
const createdBy = await getAuthenticatedUserId()
const validatedFields = assignmentSchema.safeParse({
...formData,
createdBy,
})
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { itemId, assetId, quantity, recipientId } = validatedFields.data
if (!itemId || !recipientId || quantity <= 0) return
try {
const item = await ItemService.findById(itemId)
if (!item) {
return {
success: false,
errors: { itemId: ["Item not found"] },
}
}
if (item.stock < quantity) {
return {
success: false,
errors: { quantity: ["Item does not have enough stock"] },
}
}
if (assetId) {
const asset = await AssetService.findById(assetId)
if (!asset) {
return {
success: false,
errors: { assetId: ["Asset not found"] },
}
}
if (asset.itemId !== item.id) {
return {
success: false,
errors: { assetId: ["Asset does not belong to item"] },
}
}
}
await ItemService.updateStock(itemId, -quantity)
if (assetId && recipientId) {
await AssetService.update(assetId, {
status: "ASSIGNED",
})
}
const createdAssignment = await AssignmentService.create({
itemId,
assetId: assetId || undefined,
quantity,
recipientId,
})
await MovementService.create({
itemId,
assetId: assetId || undefined,
quantity,
type: "ASSIGNMENT",
recipientId,
assignmentId: createdAssignment.id,
})
revalidatePath("/assignments")
return {
success: true,
message: "Assignment created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error creating assignment"] },
}
}
}
export async function updateAssignment(formData: UpdateAssignmentFormType) {
const validatedFields = updateAssignmentSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const { id, recipientId, itemId, assetId, quantity } = validatedFields.data
const assignment = await prisma.assignment.findUnique({
where: { id },
})
if (!assignment) {
return {
success: false,
errors: { id: ["Assignment not found"] },
}
}
if (itemId) {
const item = await prisma.item.findUnique({
where: { id: itemId },
})
if (!item) {
return {
success: false,
errors: { itemId: ["Item not found"] },
}
}
// if (item.stock < quantity) {
// return {
// success: false,
// errors: { quantity: ["Item does not have enough stock"] },
// }
// }
}
if (assetId) {
const asset = await prisma.asset.findUnique({
where: { id: assetId },
})
if (!asset) {
return {
success: false,
errors: { assetId: ["Asset not found"] },
}
}
if (asset.itemId !== itemId) {
await prisma.asset.update({
where: { id: assetId },
data: { itemId },
})
return {
success: false,
errors: { assetId: ["Asset does not belong to item"] },
}
}
}
const createdBy = await getAuthenticatedUserId()
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,
})
await MovementService.create({
type: "ASSIGNMENT",
quantity,
itemId,
assetId: assetId || undefined,
recipientId,
assignmentId: id,
})
await prisma.assignment.update({
data: {
...validatedFields.data,
createdBy,
recipientId,
itemId,
assetId,
quantity,
returnDate: null,
},
where: { id },
})
} else {
await prisma.assignment.update({
where: { id },
data: {
...validatedFields.data,
itemId,
assetId,
quantity,
returnDate: new Date(),
},
})
}
revalidatePath("/assignments")
return {
success: true,
message: "Assignment updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error creating assignment"] },
}
}
}
export async function returnAssignment(formData: ReturnAssignmentFormType) {
const { id } = formData
const assignment = await AssignmentService.findById(id)
if (!assignment) {
return {
success: false,
errors: { id: ["Assignment not found"] },
}
}
if (assignment.returnDate) {
return {
success: false,
errors: { id: ["Assignment already returned"] },
}
}
if (assignment.itemId && assignment.quantity) {
await ItemService.update(assignment.itemId, {
stock: { increment: assignment.quantity || 1 },
})
}
if (assignment.assetId) {
await AssetService.update(assignment.assetId, {
status: "AVAILABLE",
})
}
await AssignmentService.delete(id)
await MovementService.create({
type: "RETURN",
quantity: assignment.quantity || 1,
itemId: assignment.itemId || undefined,
assetId: assignment.assetId || undefined,
assignmentId: id,
})
revalidatePath("/assignments")
return {
success: true,
message: "Assignment returned successfully",
}
}
@@ -2,21 +2,20 @@ import { z } from "zod"
export const assignmentSchema = z.object({
id: z.string().optional(),
quantity: z.coerce
.number()
.int()
.nonnegative()
.min(1, {
error: "Quantity is required"
}),
quantity: z.coerce.number().int().nonnegative().min(1, {
error: "Quantity is required",
}),
notes: z.string().optional(),
itemId: z.string().min(1, {
error: "Item is required"
}).optional(),
itemId: z
.string()
.min(1, {
error: "Item is required",
})
.optional(),
assetId: z.string().optional(),
recipientId: z.string().min(1, {
error: "Recipient is required"
}),
error: "Recipient is required",
}),
assignmentDate: z.date().optional(),
returnDate: z.date().optional(),
})
@@ -43,10 +42,11 @@ export const updateAssignmentSchema = assignmentSchema
})
export type UpdateAssignmentFormType = z.input<typeof updateAssignmentSchema>
export type UpdateAssignmentData = z.output<typeof updateAssignmentSchema>
export const returnAssignmentSchema = z.object({
id: z.string().min(1, {
error: "Assignment ID is required"
}),
error: "Assignment ID is required",
}),
})
export type ReturnAssignmentFormType = z.infer<typeof returnAssignmentSchema>
+21
View File
@@ -0,0 +1,21 @@
import { z } from "zod"
export const movementSchema = z.object({
type: z.enum(["IN", "OUT", "ASSIGNMENT", "RETURN", "ADJUSTMENT"]),
quantity: z.coerce.number().int().nonnegative().min(1, {
error: "Quantity is required",
}),
details: z.string().optional(),
notes: z.string().optional(),
itemId: z.string().optional(),
assetId: z.string().optional(),
userId: z.string(),
assignmentId: z.string().optional(),
recipientId: z.string().optional(),
})
export const createMovementSchema = movementSchema.omit({
userId: true,
})
export type CreateMovementFormType = z.infer<typeof createMovementSchema>
+19 -8
View File
@@ -4,7 +4,7 @@ import prisma from "@/lib/prisma"
import type {
AssetWithAssignment,
AssetWithItemAndCategory,
} from "@/lib/types/asset"
} from "@/types/asset"
export const AssetService = {
findAll: async (opts?: {
@@ -93,8 +93,11 @@ export const AssetService = {
})
},
findById: async (id: string): Promise<AssetWithAssignment | null> => {
return prisma.asset.findUnique({
findById: async (
id: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<AssetWithAssignment | null> => {
return db.asset.findUnique({
where: { id },
include: { item: true, assignment: true },
})
@@ -106,19 +109,27 @@ export const AssetService = {
findBySerialNumber: async (
serialNumber: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<AssetWithAssignment | null> => {
return prisma.asset.findUnique({
return db.asset.findUnique({
where: { serialNumber },
include: { item: true, assignment: true },
})
},
create: async (data: Prisma.AssetCreateInput): Promise<Asset> => {
return prisma.asset.create({ data })
create: async (
data: Prisma.AssetCreateInput,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Asset> => {
return db.asset.create({ data })
},
update: async (id: string, data: Prisma.AssetUpdateInput): Promise<Asset> => {
return prisma.asset.update({ where: { id }, data })
update: async (
id: string,
data: Prisma.AssetUpdateInput,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Asset> => {
return db.asset.update({ where: { id }, data })
},
delete: async (id: string): Promise<Asset> => {
+33 -13
View File
@@ -1,10 +1,8 @@
import type { Prisma } from "@/generated/prisma/client"
import { paginate } from "@/lib/paginate"
import prisma from "@/lib/prisma"
import type { CreateAssignmentData } from "@/lib/schemas/assignment.schemas"
import type { Assignment, AssignmentWithRecipientItemAsset } from "@/lib/types"
import { getAuthenticatedUserId } from "./auth.service"
import type { CreateAssignmentData } from "@/schemas/assignment.schema"
import type { Assignment, AssignmentWithRecipientItemAsset } from "@/types"
export const AssignmentService = {
findAllWithRecipient: async (): Promise<
@@ -72,8 +70,9 @@ export const AssignmentService = {
},
findById: async (
id: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<AssignmentWithRecipientItemAsset | null> => {
return prisma.assignment.findUnique({
return db.assignment.findUnique({
where: { id },
include: {
recipient: true,
@@ -94,12 +93,12 @@ export const AssignmentService = {
},
})
},
create: async (data: CreateAssignmentData): Promise<Assignment> => {
return prisma.assignment.create({
data: {
...data,
createdBy: await getAuthenticatedUserId(),
},
create: async (
data: CreateAssignmentData & { createdBy: string },
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Assignment> => {
return db.assignment.create({
data,
})
},
delete: async (id: string): Promise<Assignment> => {
@@ -114,11 +113,32 @@ export const AssignmentService = {
},
})
},
markReturnedIfActive: async (
id: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<boolean> => {
const result = await db.assignment.updateMany({
where: {
id,
returnDate: null,
},
data: {
returnDate: new Date(),
recipientId: null,
quantity: null,
assetId: null,
itemId: null,
},
})
return result.count === 1
},
update: async (
id: string,
data: Prisma.AssignmentUpdateInput,
data: Prisma.AssignmentUpdateInput | Prisma.AssignmentUncheckedUpdateInput,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Assignment> => {
return prisma.assignment.update({
return db.assignment.update({
where: { id },
data,
})
+56 -16
View File
@@ -6,7 +6,7 @@ import type {
ItemSummary,
ItemWithAssetAndMovementCount,
ItemWithAssetCount,
} from "@/lib/types"
} from "@/types"
export const ItemService = {
findAllItemsCount: async (): Promise<number> => {
@@ -86,8 +86,11 @@ export const ItemService = {
}) as Promise<Item[]>
},
findByIdWithCategory: async (id: string): Promise<ItemSummary | null> => {
return prisma.item.findUnique({
findByIdWithCategory: async (
id: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<ItemSummary | null> => {
return db.item.findUnique({
where: { id },
include: {
category: { select: { id: true, name: true } },
@@ -97,8 +100,9 @@ export const ItemService = {
findByIdWithAssetCount: async (
id: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<ItemWithAssetCount | null> => {
return prisma.item.findUnique({
return db.item.findUnique({
where: { id },
include: {
category: { select: { id: true, name: true } },
@@ -119,15 +123,21 @@ export const ItemService = {
})
},
findByName: async (name: string): Promise<Item | null> => {
return prisma.item.findFirst({
findByName: async (
name: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Item | null> => {
return db.item.findFirst({
where: { name },
include: { category: true, assets: true, movements: true },
}) as Promise<Item | null>
},
findById: async (id: string): Promise<Item | null> => {
return prisma.item.findUnique({
findById: async (
id: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Item | null> => {
return db.item.findUnique({
where: { id },
include: { category: true, assets: true, movements: true },
}) as Promise<Item | null>
@@ -148,23 +158,53 @@ export const ItemService = {
}) as Promise<Item[]>
},
create: async (data: Prisma.ItemCreateInput): Promise<Item> => {
return prisma.item.create({ data })
create: async (
data: Prisma.ItemCreateInput,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Item> => {
return db.item.create({ data })
},
update: async (id: string, data: Prisma.ItemUpdateInput): Promise<Item> => {
return prisma.item.update({ where: { id }, data })
update: async (
id: string,
data: Prisma.ItemUpdateInput,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Item> => {
return db.item.update({ where: { id }, data })
},
updateStock: async (id: string, quantity: number): Promise<Item> => {
return prisma.item.update({
updateStock: async (
id: string,
quantity: number,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Item> => {
return db.item.update({
where: { id },
data: { stock: { increment: quantity } },
})
},
delete: async (id: string): Promise<Item> => {
return prisma.item.update({
decrementStockIfAvailable: async (
id: string,
quantity: number,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<boolean> => {
const result = await db.item.updateMany({
where: {
id,
stock: { gte: quantity },
},
data: { stock: { decrement: quantity } },
})
return result.count === 1
},
delete: async (
id: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Item> => {
return db.item.update({
where: { id },
data: { deletedAt: new Date() },
})
+8 -10
View File
@@ -1,10 +1,8 @@
import type { MovementType, Prisma } from "@/generated/prisma/client"
import { paginate } from "@/lib/paginate"
import prisma from "@/lib/prisma"
import type { CreateMovementFormType } from "@/lib/schemas/movement.schemas"
import type { Movement } from "@/lib/types"
import { getAuthenticatedUserId } from "./auth.service"
import type { CreateMovementFormType } from "@/schemas/movement.schema"
import type { Movement } from "@/types"
type MovementListResult = {
id: string
@@ -49,12 +47,12 @@ export const MovementService = {
},
})
},
create: async (data: CreateMovementFormType): Promise<Movement> => {
return await prisma.movement.create({
data: {
...data,
userId: await getAuthenticatedUserId(),
},
create: async (
data: CreateMovementFormType & { userId: string },
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Movement> => {
return await db.movement.create({
data,
})
},
+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,
}
})
}