213 lines
4.8 KiB
TypeScript
213 lines
4.8 KiB
TypeScript
import { Prisma } from "@/generated/prisma/client"
|
|
import prisma from "@/lib/prisma"
|
|
import type { CreateItemData, UpdateItemData } from "@/schemas/item.schema"
|
|
import { ItemService } from "@/services/item.service"
|
|
import { MovementService } from "@/services/movement.service"
|
|
import { buildItemSku } from "./item.helpers"
|
|
|
|
type FieldErrors = Record<string, string[]>
|
|
|
|
type CreateItemUseCaseInput = Omit<CreateItemData, "trackingType" | "status"> &
|
|
Partial<Pick<CreateItemData, "trackingType" | "status">> & {
|
|
actorId: string
|
|
}
|
|
|
|
type UpdateItemUseCaseInput = UpdateItemData & {
|
|
actorId: string
|
|
}
|
|
|
|
type ItemUseCaseResult =
|
|
| {
|
|
success: true
|
|
}
|
|
| {
|
|
success: false
|
|
errors: FieldErrors
|
|
}
|
|
|
|
function itemError(errors: FieldErrors): ItemUseCaseResult {
|
|
return {
|
|
success: false,
|
|
errors,
|
|
}
|
|
}
|
|
|
|
function isUniqueConstraintError(error: unknown) {
|
|
return (
|
|
error instanceof Prisma.PrismaClientKnownRequestError &&
|
|
error.code === "P2002"
|
|
)
|
|
}
|
|
|
|
export async function createItemUseCase(
|
|
input: CreateItemUseCaseInput,
|
|
): Promise<ItemUseCaseResult> {
|
|
const {
|
|
actorId,
|
|
name,
|
|
categoryId,
|
|
stock,
|
|
trackingType = "QUANTITY",
|
|
status = "ACTIVE",
|
|
minStock,
|
|
targetStock,
|
|
} = input
|
|
|
|
if (stock < 0) {
|
|
return itemError({ stock: ["Stock cannot be negative"] })
|
|
}
|
|
|
|
try {
|
|
return await prisma.$transaction(async (tx) => {
|
|
const existingItem = await ItemService.findByName(name, tx)
|
|
|
|
if (existingItem) {
|
|
return itemError({
|
|
name: ["An item with this name already exists"],
|
|
})
|
|
}
|
|
|
|
const skuBase = buildItemSku(name)
|
|
const existingSkuCount = await tx.item.count({
|
|
where: {
|
|
sku: {
|
|
startsWith: skuBase,
|
|
},
|
|
},
|
|
})
|
|
|
|
const item = await ItemService.create(
|
|
{
|
|
sku: buildItemSku(name, existingSkuCount),
|
|
name,
|
|
trackingType,
|
|
status,
|
|
minStock,
|
|
targetStock,
|
|
category: { connect: { id: categoryId } },
|
|
stock: stock || 0,
|
|
},
|
|
tx,
|
|
)
|
|
|
|
if (stock > 0) {
|
|
await MovementService.create(
|
|
{
|
|
type: "IN",
|
|
itemId: item.id,
|
|
userId: actorId,
|
|
quantity: stock,
|
|
},
|
|
tx,
|
|
)
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
}
|
|
})
|
|
} catch (error) {
|
|
if (isUniqueConstraintError(error)) {
|
|
return itemError({ name: ["An item with this name already exists"] })
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export async function updateItemUseCase(
|
|
input: UpdateItemUseCaseInput,
|
|
): Promise<ItemUseCaseResult> {
|
|
const {
|
|
actorId,
|
|
id,
|
|
stock,
|
|
name,
|
|
categoryId,
|
|
trackingType,
|
|
status,
|
|
minStock,
|
|
targetStock,
|
|
} = input
|
|
|
|
try {
|
|
return await prisma.$transaction(async (tx) => {
|
|
const existingItem = await ItemService.findByIdWithAssetCount(id, tx)
|
|
|
|
if (!existingItem) {
|
|
return itemError({ id: ["Item not found"] })
|
|
}
|
|
|
|
const existingItemByName = await ItemService.findByName(name, tx)
|
|
|
|
if (existingItemByName && existingItemByName.id !== id) {
|
|
return itemError({ name: ["An item with this name already exists"] })
|
|
}
|
|
|
|
await ItemService.update(
|
|
id,
|
|
{
|
|
stock: stock ?? existingItem.stock,
|
|
name: name || existingItem.name,
|
|
trackingType: trackingType ?? existingItem.trackingType,
|
|
status: status ?? existingItem.status,
|
|
minStock: minStock ?? existingItem.minStock,
|
|
targetStock: targetStock ?? existingItem.targetStock,
|
|
category: { connect: { id: categoryId } },
|
|
},
|
|
tx,
|
|
)
|
|
|
|
const updatedStock = stock ?? existingItem.stock
|
|
|
|
if (updatedStock > existingItem.stock) {
|
|
await MovementService.create(
|
|
{
|
|
type: "IN",
|
|
itemId: id,
|
|
quantity: updatedStock - existingItem.stock,
|
|
userId: actorId,
|
|
},
|
|
tx,
|
|
)
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
}
|
|
})
|
|
} catch (error) {
|
|
if (isUniqueConstraintError(error)) {
|
|
return itemError({ name: ["An item with this name already exists"] })
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export async function deleteItemUseCase(
|
|
id: string,
|
|
): Promise<ItemUseCaseResult> {
|
|
return prisma.$transaction(async (tx) => {
|
|
const existingItem = await ItemService.findByIdWithAssetCount(id, tx)
|
|
|
|
if (!existingItem) {
|
|
return itemError({ id: ["Item not found"] })
|
|
}
|
|
|
|
if (existingItem._count.assets > 0) {
|
|
return itemError({ id: ["Item has assets, you cannot delete it"] })
|
|
}
|
|
|
|
if (existingItem.stock > 0) {
|
|
return itemError({ id: ["Item has stock, you cannot delete it"] })
|
|
}
|
|
|
|
await ItemService.delete(id, tx)
|
|
|
|
return {
|
|
success: true,
|
|
}
|
|
})
|
|
}
|