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 type CreateItemUseCaseInput = Omit & Partial> & { 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 { 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 { 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 { 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, } }) }