refactor(assignments): move workflows into use cases
This commit is contained in:
@@ -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",
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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> => {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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() },
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user