refactor(assets): move workflows into use cases
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { flattenError } from "zod"
|
||||
import {
|
||||
type CreateAssetFormType,
|
||||
createAssetSchema,
|
||||
type UpdateAssetFormType,
|
||||
updateAssetSchema,
|
||||
} from "@/schemas/asset.schema"
|
||||
import { getAuthenticatedUserId } from "@/services/auth.service"
|
||||
import {
|
||||
createAssetUseCase,
|
||||
updateAssetUseCase,
|
||||
} from "@/use-cases/asset.use-cases"
|
||||
|
||||
export async function createAssetAction(formData: CreateAssetFormType) {
|
||||
try {
|
||||
const validatedFields = createAssetSchema.safeParse(formData)
|
||||
|
||||
if (!validatedFields.success) {
|
||||
return {
|
||||
errors: flattenError(validatedFields.error).fieldErrors,
|
||||
}
|
||||
}
|
||||
|
||||
const userId = await getAuthenticatedUserId()
|
||||
|
||||
const result = await createAssetUseCase({
|
||||
...validatedFields.data,
|
||||
actorId: userId,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
return result
|
||||
}
|
||||
|
||||
revalidatePath("/inventory/assets")
|
||||
revalidatePath("/inventory/items")
|
||||
revalidatePath("/assignments")
|
||||
revalidatePath("/movements")
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Asset created successfully",
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Database error:", error)
|
||||
return {
|
||||
success: false,
|
||||
message: "Error creating asset",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateAssetAction(formData: UpdateAssetFormType) {
|
||||
const validatedFields = updateAssetSchema.safeParse(formData)
|
||||
|
||||
if (!validatedFields.success) {
|
||||
return {
|
||||
errors: flattenError(validatedFields.error).fieldErrors,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const userId = await getAuthenticatedUserId()
|
||||
|
||||
const result = await updateAssetUseCase({
|
||||
...validatedFields.data,
|
||||
actorId: userId,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
return result
|
||||
}
|
||||
|
||||
revalidatePath("/inventory/assets")
|
||||
revalidatePath("/inventory/items")
|
||||
revalidatePath("/assignments")
|
||||
revalidatePath("/movements")
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Asset updated successfully",
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Database error:", error)
|
||||
return {
|
||||
success: false,
|
||||
message: "Error updating asset",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
"use server"
|
||||
|
||||
import type { AssetWithAssignment } from "@/lib/types"
|
||||
import { AssetService } from "@/services/asset.service"
|
||||
import { ItemService } from "@/services/item.service"
|
||||
import { RecipientService } from "@/services/recipient.service"
|
||||
import type { AssetWithAssignment } from "@/types"
|
||||
|
||||
import EditAssetForm from "../../_components/edit.asset.form"
|
||||
|
||||
|
||||
@@ -4,20 +4,19 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { updateAssetAction } from "@/actions/asset.actions"
|
||||
import { SubmitButton } from "@/components/forms/submitButton"
|
||||
import { ItemStatus } from "@/generated/prisma/client"
|
||||
import { updateAssetAction } from "@/lib/actions/asset.actions"
|
||||
import { ITEM_STATUS } from "@/lib/constants"
|
||||
import {
|
||||
type UpdateAssetFormType,
|
||||
updateAssetSchema,
|
||||
} from "@/lib/schemas/asset.schemas"
|
||||
} from "@/schemas/asset.schema"
|
||||
import type {
|
||||
AssetWithAssignment,
|
||||
Item,
|
||||
Recipient,
|
||||
UpdateAssetStatus,
|
||||
} from "@/lib/types"
|
||||
} from "@/types"
|
||||
|
||||
interface EditAssetFormProps {
|
||||
asset: AssetWithAssignment
|
||||
@@ -42,11 +41,11 @@ export default function EditAssetForm({
|
||||
resolver: zodResolver(updateAssetSchema),
|
||||
defaultValues: {
|
||||
id: asset.id,
|
||||
itemId: asset.itemId ?? "",
|
||||
itemId: asset.itemId ?? undefined,
|
||||
serialNumber: asset.serialNumber,
|
||||
deliveryNote: asset.deliveryNote ?? "",
|
||||
deliveryNote: asset.deliveryNote ?? undefined,
|
||||
status: asset.status as UpdateAssetStatus,
|
||||
recipientId: asset.assignment?.recipientId ?? "",
|
||||
recipientId: asset.assignment?.recipientId ?? undefined,
|
||||
},
|
||||
shouldFocusError: true,
|
||||
mode: "onSubmit",
|
||||
@@ -138,7 +137,7 @@ export default function EditAssetForm({
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a status</option>
|
||||
{Object.values(ItemStatus).map((status) => (
|
||||
{Object.values(ITEM_STATUS).map((status) => (
|
||||
<option key={status} value={status}>
|
||||
{status}
|
||||
</option>
|
||||
|
||||
@@ -4,15 +4,14 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { createAssetAction } from "@/actions/asset.actions"
|
||||
import { SubmitButton } from "@/components/forms/submitButton"
|
||||
import { ItemStatus } from "@/generated/prisma/client"
|
||||
import { createAssetAction } from "@/lib/actions/asset.actions"
|
||||
import { ITEM_STATUS } from "@/lib/constants"
|
||||
import {
|
||||
type CreateAssetFormType,
|
||||
createAssetSchema,
|
||||
} from "@/lib/schemas/asset.schemas"
|
||||
import type { ItemWithoutStock, Recipient } from "@/lib/types"
|
||||
} from "@/schemas/asset.schema"
|
||||
import type { ItemWithoutStock, Recipient } from "@/types"
|
||||
|
||||
interface NewAssetFormProps {
|
||||
items: ItemWithoutStock[]
|
||||
@@ -123,7 +122,7 @@ export default function NewAssetForm({ items, recipients }: NewAssetFormProps) {
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a status</option>
|
||||
{Object.values(ItemStatus).map((status) => (
|
||||
{Object.values(ITEM_STATUS).map((status) => (
|
||||
<option key={status} value={status}>
|
||||
{status}
|
||||
</option>
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
|
||||
import {
|
||||
createAssignment,
|
||||
updateAssignment,
|
||||
} from "@/lib/actions/assignament.actions"
|
||||
import {
|
||||
type CreateAssetFormType,
|
||||
createAssetSchema,
|
||||
type UpdateAssetFormType,
|
||||
updateAssetSchema,
|
||||
} from "@/lib/schemas/asset.schemas"
|
||||
import { AssetService } from "@/services/asset.service"
|
||||
import { AssignmentService } from "@/services/assignment.service"
|
||||
import { ItemService } from "@/services/item.service"
|
||||
import { MovementService } from "@/services/movement.service"
|
||||
|
||||
export async function createAssetAction(formData: CreateAssetFormType) {
|
||||
try {
|
||||
const validatedFields = createAssetSchema.safeParse(formData)
|
||||
|
||||
if (!validatedFields.success) {
|
||||
return {
|
||||
errors: validatedFields.error.flatten().fieldErrors,
|
||||
}
|
||||
}
|
||||
|
||||
const { itemId, serialNumber, deliveryNote, status, notes, recipientId } =
|
||||
validatedFields.data
|
||||
|
||||
const item = await ItemService.findByIdWithCategory(itemId)
|
||||
|
||||
if (!item) {
|
||||
return {
|
||||
success: false,
|
||||
errors: { itemId: ["Item not found"] },
|
||||
}
|
||||
}
|
||||
|
||||
const existentAsset = await AssetService.findBySerialNumber(serialNumber)
|
||||
|
||||
if (existentAsset) {
|
||||
return {
|
||||
success: false,
|
||||
errors: {
|
||||
serialNumber: ["This serial number already exists"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const newAsset = await AssetService.create({
|
||||
item: { connect: { id: itemId } },
|
||||
serialNumber,
|
||||
deliveryNote,
|
||||
status,
|
||||
notes,
|
||||
})
|
||||
|
||||
await MovementService.create({
|
||||
itemId,
|
||||
assetId: newAsset?.id,
|
||||
quantity: 1,
|
||||
type: status === "ASSIGNED" ? "ASSIGNMENT" : "IN",
|
||||
})
|
||||
|
||||
if (status === "AVAILABLE") {
|
||||
await ItemService.update(itemId, {
|
||||
stock: item.stock + 1,
|
||||
name: item.name,
|
||||
category: { connect: { id: item.category?.id } },
|
||||
})
|
||||
}
|
||||
|
||||
if (status === "ASSIGNED" && recipientId) {
|
||||
await AssignmentService.create({
|
||||
notes: "",
|
||||
itemId,
|
||||
assetId: newAsset?.id,
|
||||
quantity: 1,
|
||||
recipientId,
|
||||
assignmentDate: new Date(),
|
||||
})
|
||||
}
|
||||
|
||||
revalidatePath("/inventory/assets")
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Asset created successfully",
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Database error:", error)
|
||||
return {
|
||||
success: false,
|
||||
message: "Error creating asset",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateAssetAction(formData: UpdateAssetFormType) {
|
||||
const validatedFields = updateAssetSchema.safeParse(formData)
|
||||
|
||||
if (!validatedFields.success) {
|
||||
return {
|
||||
errors: validatedFields.error.flatten().fieldErrors,
|
||||
}
|
||||
}
|
||||
|
||||
const { id, itemId, serialNumber, deliveryNote, status, notes, recipientId } =
|
||||
validatedFields.data
|
||||
|
||||
try {
|
||||
const item = await ItemService.findByIdWithCategory(itemId)
|
||||
|
||||
if (!item) {
|
||||
return {
|
||||
success: false,
|
||||
errors: { itemId: ["Item not found"] },
|
||||
}
|
||||
}
|
||||
|
||||
const existentAsset = await AssetService.findBySerialNumber(serialNumber)
|
||||
|
||||
if (
|
||||
existentAsset &&
|
||||
id !== existentAsset.id &&
|
||||
existentAsset.serialNumber === serialNumber
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
errors: {
|
||||
serialNumber: ["This serial number already exists"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
await AssetService.update(id, {
|
||||
item: { connect: { id: itemId } },
|
||||
serialNumber,
|
||||
deliveryNote,
|
||||
status,
|
||||
notes,
|
||||
})
|
||||
|
||||
await MovementService.create({
|
||||
itemId,
|
||||
assetId: id,
|
||||
quantity: 1,
|
||||
type: status === "ASSIGNED" ? "ASSIGNMENT" : "IN",
|
||||
})
|
||||
|
||||
if (status === "AVAILABLE") {
|
||||
await ItemService.update(itemId, {
|
||||
stock: item.stock + 1,
|
||||
name: item.name,
|
||||
category: { connect: { id: item.category?.id } },
|
||||
})
|
||||
}
|
||||
|
||||
if (status === "ASSIGNED" && recipientId) {
|
||||
if (!recipientId) {
|
||||
return {
|
||||
success: false,
|
||||
errors: { recipientId: ["Recipient is required for assignment"] },
|
||||
}
|
||||
}
|
||||
|
||||
if (existentAsset?.assignment) {
|
||||
await updateAssignment({
|
||||
id: existentAsset.assignment.id,
|
||||
recipientId,
|
||||
quantity: 1,
|
||||
})
|
||||
} else {
|
||||
await createAssignment({
|
||||
itemId,
|
||||
assetId: id,
|
||||
quantity: 1,
|
||||
recipientId,
|
||||
assignmentDate: new Date(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Asset updated successfully",
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Database error:", error)
|
||||
return {
|
||||
success: false,
|
||||
message: "Error updating asset",
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -3,7 +3,17 @@ const isDemo = process.env.DEMO_MODE === "true"
|
||||
export const ENVIRONMENT = isDemo
|
||||
? "demo"
|
||||
: process.env.NODE_ENV || "development"
|
||||
|
||||
|
||||
export const SIGN_IN_URL = "/login"
|
||||
|
||||
export const TOKEN_EXPIRATION_SECONDS = 60 * 60 * 2 // 2 hour
|
||||
|
||||
export const ITEM_STATUS = {
|
||||
AVAILABLE: "AVAILABLE",
|
||||
ASSIGNED: "ASSIGNED",
|
||||
RESERVED: "RESERVED",
|
||||
IN_REPAIR: "IN_REPAIR",
|
||||
BROKEN: "BROKEN",
|
||||
STOLEN: "STOLEN",
|
||||
DISPOSED: "DISPOSED",
|
||||
} as const
|
||||
|
||||
@@ -3,11 +3,11 @@ import { z } from "zod"
|
||||
export const assetSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
itemId: z.string().min(1, {
|
||||
error: "Item is required"
|
||||
}),
|
||||
error: "Item is required",
|
||||
}),
|
||||
serialNumber: z.string().min(1, {
|
||||
error: "Serial number is required"
|
||||
}),
|
||||
error: "Serial number is required",
|
||||
}),
|
||||
deliveryNote: z.string().optional(),
|
||||
notes: z.string().optional(),
|
||||
recipientId: z.string().optional(),
|
||||
@@ -21,9 +21,17 @@ export type CreateAssetFormType = z.infer<typeof createAssetSchema>
|
||||
|
||||
export const updateAssetSchema = assetSchema.extend({
|
||||
id: z.string().min(1, {
|
||||
error: "ID is required"
|
||||
}),
|
||||
status: z.enum(["AVAILABLE", "ASSIGNED", "RESERVED", "IN_REPAIR"]),
|
||||
error: "ID is required",
|
||||
}),
|
||||
status: z.enum([
|
||||
"AVAILABLE",
|
||||
"ASSIGNED",
|
||||
"RESERVED",
|
||||
"IN_REPAIR",
|
||||
"BROKEN",
|
||||
"STOLEN",
|
||||
"DISPOSED",
|
||||
]),
|
||||
})
|
||||
|
||||
export type UpdateAssetFormType = z.infer<typeof updateAssetSchema>
|
||||
@@ -0,0 +1,515 @@
|
||||
import {
|
||||
type Assignment,
|
||||
type ItemStatus,
|
||||
Prisma,
|
||||
} from "@/generated/prisma/client"
|
||||
import prisma from "@/lib/prisma"
|
||||
import type {
|
||||
CreateAssetFormType,
|
||||
UpdateAssetFormType,
|
||||
} from "@/schemas/asset.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 CreateAssetUseCaseInput = CreateAssetFormType & {
|
||||
actorId: string
|
||||
}
|
||||
|
||||
type UpdateAssetUseCaseInput = UpdateAssetFormType & {
|
||||
actorId: string
|
||||
}
|
||||
|
||||
type CreateAssetUseCaseResult =
|
||||
| {
|
||||
success: true
|
||||
assetId: string
|
||||
}
|
||||
| {
|
||||
success: false
|
||||
errors: FieldErrors
|
||||
}
|
||||
|
||||
type UpdateAssetUseCaseResult =
|
||||
| {
|
||||
success: true
|
||||
}
|
||||
| {
|
||||
success: false
|
||||
errors: FieldErrors
|
||||
}
|
||||
|
||||
function createAssetError(errors: FieldErrors): CreateAssetUseCaseResult {
|
||||
return {
|
||||
success: false,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
function updateAssetError(errors: FieldErrors): UpdateAssetUseCaseResult {
|
||||
return {
|
||||
success: false,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
class AssetTransitionError extends Error {
|
||||
constructor(readonly errors: FieldErrors) {
|
||||
super("Asset transition failed")
|
||||
}
|
||||
}
|
||||
|
||||
type AssetTransitionInput = {
|
||||
previousStatus: ItemStatus
|
||||
nextStatus: ItemStatus
|
||||
previousItemId: string | null
|
||||
nextItemId: string
|
||||
activeAssignment: Assignment | null
|
||||
nextRecipientId?: string
|
||||
}
|
||||
|
||||
function getAssetTransition({
|
||||
previousStatus,
|
||||
nextStatus,
|
||||
previousItemId,
|
||||
nextItemId,
|
||||
activeAssignment,
|
||||
nextRecipientId,
|
||||
}: AssetTransitionInput) {
|
||||
return {
|
||||
previousStatus,
|
||||
nextStatus,
|
||||
previousItemId,
|
||||
nextItemId,
|
||||
statusChanged: previousStatus !== nextStatus,
|
||||
itemChanged: previousItemId !== nextItemId,
|
||||
activeAssignment,
|
||||
nextRecipientId,
|
||||
hasRecipient: Boolean(nextRecipientId),
|
||||
wasAssigned: previousStatus === "ASSIGNED",
|
||||
willBeAssigned: nextStatus === "ASSIGNED",
|
||||
wasAvailable: previousStatus === "AVAILABLE",
|
||||
willBeAvailable: nextStatus === "AVAILABLE",
|
||||
recipientChanged: activeAssignment?.recipientId !== nextRecipientId,
|
||||
}
|
||||
}
|
||||
|
||||
export async function createAssetUseCase(
|
||||
input: CreateAssetUseCaseInput,
|
||||
): Promise<CreateAssetUseCaseResult> {
|
||||
const {
|
||||
actorId,
|
||||
itemId,
|
||||
serialNumber,
|
||||
deliveryNote,
|
||||
status,
|
||||
notes,
|
||||
recipientId,
|
||||
} = input
|
||||
|
||||
try {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const item = await ItemService.findByIdWithCategory(itemId, tx)
|
||||
|
||||
if (!item) {
|
||||
return createAssetError({ itemId: ["Item not found"] })
|
||||
}
|
||||
|
||||
const existentAsset = await AssetService.findBySerialNumber(
|
||||
serialNumber,
|
||||
tx,
|
||||
)
|
||||
|
||||
if (existentAsset) {
|
||||
return createAssetError({
|
||||
serialNumber: ["This serial number already exists"],
|
||||
})
|
||||
}
|
||||
|
||||
const newAsset = await AssetService.create(
|
||||
{
|
||||
item: { connect: { id: itemId } },
|
||||
serialNumber,
|
||||
deliveryNote,
|
||||
status,
|
||||
notes,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
|
||||
const createdAssignment =
|
||||
status === "ASSIGNED" && recipientId
|
||||
? await AssignmentService.create(
|
||||
{
|
||||
notes: "",
|
||||
itemId,
|
||||
assetId: newAsset.id,
|
||||
quantity: 1,
|
||||
recipientId,
|
||||
assignmentDate: new Date(),
|
||||
createdBy: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
: null
|
||||
|
||||
await MovementService.create(
|
||||
{
|
||||
itemId,
|
||||
assetId: newAsset.id,
|
||||
quantity: 1,
|
||||
type: status === "ASSIGNED" ? "ASSIGNMENT" : "IN",
|
||||
recipientId: createdAssignment?.recipientId || undefined,
|
||||
assignmentId: createdAssignment?.id,
|
||||
userId: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
|
||||
if (status === "AVAILABLE") {
|
||||
await ItemService.updateStock(itemId, 1, tx)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
assetId: newAsset.id,
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof Prisma.PrismaClientKnownRequestError &&
|
||||
error.code === "P2002"
|
||||
) {
|
||||
return createAssetError({
|
||||
serialNumber: ["This serial number already exists"],
|
||||
})
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateAssetUseCase(
|
||||
input: UpdateAssetUseCaseInput,
|
||||
): Promise<UpdateAssetUseCaseResult> {
|
||||
const {
|
||||
actorId,
|
||||
id,
|
||||
itemId,
|
||||
serialNumber,
|
||||
deliveryNote,
|
||||
status,
|
||||
notes,
|
||||
recipientId,
|
||||
} = input
|
||||
|
||||
try {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const item = await ItemService.findByIdWithCategory(itemId, tx)
|
||||
|
||||
if (!item) {
|
||||
return updateAssetError({ itemId: ["Item not found"] })
|
||||
}
|
||||
|
||||
const currentAsset = await AssetService.findById(id, tx)
|
||||
|
||||
if (!currentAsset) {
|
||||
return updateAssetError({ id: ["Asset not found"] })
|
||||
}
|
||||
|
||||
const transition = getAssetTransition({
|
||||
previousStatus: currentAsset.status,
|
||||
nextStatus: status,
|
||||
previousItemId: currentAsset.itemId,
|
||||
nextItemId: itemId,
|
||||
activeAssignment: currentAsset.assignment,
|
||||
nextRecipientId: recipientId,
|
||||
})
|
||||
|
||||
const existentAsset = await AssetService.findBySerialNumber(
|
||||
serialNumber,
|
||||
tx,
|
||||
)
|
||||
|
||||
if (
|
||||
existentAsset &&
|
||||
id !== existentAsset.id &&
|
||||
existentAsset.serialNumber === serialNumber
|
||||
) {
|
||||
return updateAssetError({
|
||||
serialNumber: ["This serial number already exists"],
|
||||
})
|
||||
}
|
||||
|
||||
await AssetService.update(
|
||||
id,
|
||||
{
|
||||
item: { connect: { id: itemId } },
|
||||
serialNumber,
|
||||
deliveryNote,
|
||||
status,
|
||||
notes,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
|
||||
let closedActiveAssignment = false
|
||||
|
||||
if (transition.activeAssignment && !transition.willBeAssigned) {
|
||||
const activeAssignment = transition.activeAssignment
|
||||
const assignmentWasReturned =
|
||||
await AssignmentService.markReturnedIfActive(activeAssignment.id, tx)
|
||||
|
||||
if (!assignmentWasReturned) {
|
||||
throw new AssetTransitionError({
|
||||
id: ["Assignment already returned"],
|
||||
})
|
||||
}
|
||||
|
||||
await MovementService.create(
|
||||
{
|
||||
type: "RETURN",
|
||||
quantity: activeAssignment.quantity || 1,
|
||||
itemId: transition.willBeAvailable
|
||||
? transition.nextItemId
|
||||
: activeAssignment.itemId || undefined,
|
||||
assetId: activeAssignment.assetId || undefined,
|
||||
recipientId: activeAssignment.recipientId || undefined,
|
||||
assignmentId: activeAssignment.id,
|
||||
userId: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
|
||||
closedActiveAssignment = true
|
||||
}
|
||||
|
||||
const shouldIncrementNextItemStock =
|
||||
transition.willBeAvailable &&
|
||||
(!transition.wasAvailable || transition.itemChanged)
|
||||
const shouldDecrementPreviousItemStock =
|
||||
transition.wasAvailable &&
|
||||
(!transition.willBeAvailable || transition.itemChanged)
|
||||
|
||||
if (
|
||||
transition.statusChanged &&
|
||||
!transition.hasRecipient &&
|
||||
!closedActiveAssignment
|
||||
) {
|
||||
const statusMovementItemId = transition.willBeAssigned
|
||||
? transition.nextItemId
|
||||
: shouldDecrementPreviousItemStock &&
|
||||
!shouldIncrementNextItemStock &&
|
||||
transition.previousItemId
|
||||
? transition.previousItemId
|
||||
: transition.nextItemId
|
||||
|
||||
await MovementService.create(
|
||||
{
|
||||
itemId: statusMovementItemId,
|
||||
assetId: id,
|
||||
quantity: 1,
|
||||
type: transition.willBeAssigned
|
||||
? "ASSIGNMENT"
|
||||
: transition.nextStatus === "AVAILABLE"
|
||||
? "IN"
|
||||
: "ADJUSTMENT",
|
||||
details: `Status changed from ${transition.previousStatus} to ${transition.nextStatus}`,
|
||||
userId: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
transition.itemChanged &&
|
||||
transition.wasAvailable &&
|
||||
transition.willBeAvailable
|
||||
) {
|
||||
if (!transition.previousItemId) {
|
||||
throw new AssetTransitionError({
|
||||
itemId: ["Previous item not found for available asset"],
|
||||
})
|
||||
}
|
||||
|
||||
await MovementService.create(
|
||||
{
|
||||
itemId: transition.previousItemId,
|
||||
assetId: id,
|
||||
quantity: 1,
|
||||
type: "OUT",
|
||||
details: `Asset moved from item ${transition.previousItemId} to ${transition.nextItemId}`,
|
||||
userId: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
|
||||
await MovementService.create(
|
||||
{
|
||||
itemId: transition.nextItemId,
|
||||
assetId: id,
|
||||
quantity: 1,
|
||||
type: "IN",
|
||||
details: `Asset moved from item ${transition.previousItemId} to ${transition.nextItemId}`,
|
||||
userId: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
transition.itemChanged &&
|
||||
transition.wasAvailable &&
|
||||
transition.willBeAssigned
|
||||
) {
|
||||
if (!transition.previousItemId) {
|
||||
throw new AssetTransitionError({
|
||||
itemId: ["Previous item not found for available asset"],
|
||||
})
|
||||
}
|
||||
|
||||
await MovementService.create(
|
||||
{
|
||||
itemId: transition.previousItemId,
|
||||
assetId: id,
|
||||
quantity: 1,
|
||||
type: "OUT",
|
||||
details: `Asset assigned from item ${transition.previousItemId} to item ${transition.nextItemId}`,
|
||||
userId: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
}
|
||||
|
||||
if (shouldIncrementNextItemStock) {
|
||||
await ItemService.updateStock(transition.nextItemId, 1, tx)
|
||||
}
|
||||
|
||||
if (shouldDecrementPreviousItemStock) {
|
||||
if (!transition.previousItemId) {
|
||||
throw new AssetTransitionError({
|
||||
itemId: ["Previous item not found for available asset"],
|
||||
})
|
||||
}
|
||||
|
||||
const stockWasDecremented = await ItemService.decrementStockIfAvailable(
|
||||
transition.previousItemId,
|
||||
1,
|
||||
tx,
|
||||
)
|
||||
|
||||
if (!stockWasDecremented) {
|
||||
throw new AssetTransitionError({
|
||||
stock: ["Item does not have enough stock"],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (transition.willBeAssigned && transition.nextRecipientId) {
|
||||
const activeAssignment = transition.activeAssignment
|
||||
|
||||
if (activeAssignment) {
|
||||
if (transition.recipientChanged) {
|
||||
await MovementService.create(
|
||||
{
|
||||
type: "RETURN",
|
||||
quantity: activeAssignment.quantity || 1,
|
||||
itemId: activeAssignment.itemId || undefined,
|
||||
assetId: activeAssignment.assetId || undefined,
|
||||
recipientId: activeAssignment.recipientId || undefined,
|
||||
assignmentId: activeAssignment.id,
|
||||
userId: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
|
||||
await MovementService.create(
|
||||
{
|
||||
type: "ASSIGNMENT",
|
||||
quantity: 1,
|
||||
itemId,
|
||||
assetId: id,
|
||||
recipientId: transition.nextRecipientId,
|
||||
assignmentId: activeAssignment.id,
|
||||
userId: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
|
||||
await AssignmentService.update(
|
||||
activeAssignment.id,
|
||||
{
|
||||
createdBy: actorId,
|
||||
itemId,
|
||||
assetId: id,
|
||||
recipientId: transition.nextRecipientId,
|
||||
quantity: 1,
|
||||
returnDate: null,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
} else {
|
||||
await AssignmentService.update(
|
||||
activeAssignment.id,
|
||||
{
|
||||
itemId,
|
||||
assetId: id,
|
||||
recipientId: transition.nextRecipientId,
|
||||
quantity: 1,
|
||||
returnDate: null,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const createdAssignment = await AssignmentService.create(
|
||||
{
|
||||
itemId,
|
||||
assetId: id,
|
||||
quantity: 1,
|
||||
recipientId: transition.nextRecipientId,
|
||||
assignmentDate: new Date(),
|
||||
createdBy: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
|
||||
await MovementService.create(
|
||||
{
|
||||
type: "ASSIGNMENT",
|
||||
quantity: 1,
|
||||
itemId,
|
||||
assetId: id,
|
||||
recipientId: transition.nextRecipientId,
|
||||
assignmentId: createdAssignment.id,
|
||||
userId: actorId,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof AssetTransitionError) {
|
||||
return updateAssetError(error.errors)
|
||||
}
|
||||
|
||||
if (
|
||||
error instanceof Prisma.PrismaClientKnownRequestError &&
|
||||
error.code === "P2002"
|
||||
) {
|
||||
return updateAssetError({
|
||||
serialNumber: ["This serial number already exists"],
|
||||
})
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user