Spaces:
Runtime error
Runtime error
| import { randomInt } from "node:crypto"; | |
| import type { Prisma } from "@prisma/client"; | |
| import { CASES_PAGE_SIZE } from "@/lib/constants"; | |
| import type { CaseTypeValue } from "@/lib/constants"; | |
| import { trimOrUndefined } from "@/lib/utils"; | |
| import { db } from "@/server/db"; | |
| import { AppError, isPrismaUniqueConstraintError } from "@/server/errors"; | |
| import type { | |
| CaseCreateInput, | |
| CaseListQueryInput, | |
| CaseUpdateInput, | |
| } from "@/server/validation/case"; | |
| export type CaseListFilters = { | |
| search?: string; | |
| caseType?: CaseTypeValue; | |
| page: number; | |
| }; | |
| export function normalizeCaseListFilters(input: CaseListQueryInput): CaseListFilters { | |
| return { | |
| search: trimOrUndefined(input.caseSearch), | |
| caseType: input.caseType, | |
| page: 1, | |
| }; | |
| } | |
| function buildCaseWhere( | |
| ownerId: string, | |
| customerId: string, | |
| filters?: CaseListFilters, | |
| ): Prisma.CaseWhereInput { | |
| const clauses: Prisma.CaseWhereInput[] = [ | |
| { | |
| ownerId, | |
| customerId, | |
| }, | |
| ]; | |
| if (filters?.search) { | |
| clauses.push({ | |
| OR: [ | |
| { | |
| caseName: { | |
| contains: filters.search, | |
| mode: "insensitive", | |
| }, | |
| }, | |
| { | |
| opponentFullName: { | |
| contains: filters.search, | |
| mode: "insensitive", | |
| }, | |
| }, | |
| { | |
| subType: { | |
| contains: filters.search, | |
| mode: "insensitive", | |
| }, | |
| }, | |
| ], | |
| }); | |
| } | |
| if (filters?.caseType) { | |
| clauses.push({ | |
| type: filters.caseType, | |
| }); | |
| } | |
| return { | |
| AND: clauses, | |
| }; | |
| } | |
| export async function listCasesForCustomer( | |
| ownerId: string, | |
| customerId: string, | |
| filters: CaseListFilters, | |
| ) { | |
| const where = buildCaseWhere(ownerId, customerId, filters); | |
| const skip = (filters.page - 1) * CASES_PAGE_SIZE; | |
| const [totalCount, items] = await db.$transaction([ | |
| db.case.count({ where }), | |
| db.case.findMany({ | |
| where, | |
| include: { | |
| customer: true, | |
| }, | |
| orderBy: [{ status: "asc" }, { updatedAt: "desc" }], | |
| skip, | |
| take: CASES_PAGE_SIZE, | |
| }), | |
| ]); | |
| return { | |
| items, | |
| totalCount, | |
| totalPages: Math.max(1, Math.ceil(totalCount / CASES_PAGE_SIZE)), | |
| page: filters.page, | |
| pageSize: CASES_PAGE_SIZE, | |
| }; | |
| } | |
| async function generateCasePublicId(ownerId: string) { | |
| for (let attempt = 0; attempt < 20; attempt += 1) { | |
| const publicId = String(randomInt(0, 1_000_000)).padStart(6, "0"); | |
| const existingCase = await db.case.findFirst({ | |
| where: { | |
| ownerId, | |
| publicId, | |
| }, | |
| select: { | |
| id: true, | |
| }, | |
| }); | |
| if (!existingCase) { | |
| return publicId; | |
| } | |
| } | |
| throw new AppError("تعذر توليد رقم قضية جديد، حاول تاني.", 500); | |
| } | |
| export async function createCaseForCustomer( | |
| ownerId: string, | |
| customerId: string, | |
| input: CaseCreateInput, | |
| ) { | |
| const customer = await db.customer.findFirst({ | |
| where: { | |
| id: customerId, | |
| ownerId, | |
| }, | |
| }); | |
| if (!customer) { | |
| throw new AppError("العميل ده مش موجود.", 404); | |
| } | |
| for (let attempt = 0; attempt < 5; attempt += 1) { | |
| try { | |
| return await db.case.create({ | |
| data: { | |
| ownerId, | |
| customerId, | |
| caseName: input.caseName.trim(), | |
| publicId: await generateCasePublicId(ownerId), | |
| type: input.type, | |
| subType: trimOrUndefined(input.subType), | |
| opponentFullName: input.opponentFullName.trim(), | |
| description: trimOrUndefined(input.description), | |
| status: "OPEN", | |
| }, | |
| include: { | |
| customer: true, | |
| }, | |
| }); | |
| } catch (error) { | |
| if (isPrismaUniqueConstraintError(error, "publicId")) { | |
| continue; | |
| } | |
| throw error; | |
| } | |
| } | |
| throw new AppError("تعذر إنشاء القضية دلوقتي، حاول تاني.", 500); | |
| } | |
| export async function getCaseByIdForOwner( | |
| ownerId: string, | |
| customerId: string, | |
| caseId: string, | |
| ) { | |
| return db.case.findFirst({ | |
| where: { | |
| id: caseId, | |
| ownerId, | |
| customerId, | |
| }, | |
| include: { | |
| customer: true, | |
| }, | |
| }); | |
| } | |
| export async function requireCaseByIdForOwner( | |
| ownerId: string, | |
| customerId: string, | |
| caseId: string, | |
| ) { | |
| const caseItem = await getCaseByIdForOwner(ownerId, customerId, caseId); | |
| if (!caseItem) { | |
| throw new AppError("القضية دي مش موجودة.", 404); | |
| } | |
| return caseItem; | |
| } | |
| export async function updateCaseForOwner( | |
| ownerId: string, | |
| customerId: string, | |
| caseId: string, | |
| input: CaseUpdateInput, | |
| ) { | |
| await requireCaseByIdForOwner(ownerId, customerId, caseId); | |
| if (input.action === "close") { | |
| return db.case.update({ | |
| where: { | |
| id: caseId, | |
| }, | |
| data: { | |
| status: "CLOSED", | |
| closedAt: new Date(), | |
| }, | |
| include: { | |
| customer: true, | |
| }, | |
| }); | |
| } | |
| if (input.action === "reopen") { | |
| return db.case.update({ | |
| where: { | |
| id: caseId, | |
| }, | |
| data: { | |
| status: "OPEN", | |
| closedAt: null, | |
| }, | |
| include: { | |
| customer: true, | |
| }, | |
| }); | |
| } | |
| return db.case.update({ | |
| where: { | |
| id: caseId, | |
| }, | |
| data: { | |
| caseName: input.caseName.trim(), | |
| type: input.type, | |
| subType: trimOrUndefined(input.subType), | |
| opponentFullName: input.opponentFullName.trim(), | |
| description: trimOrUndefined(input.description), | |
| }, | |
| include: { | |
| customer: true, | |
| }, | |
| }); | |
| } | |