// app/api/pdftron-comments/xfdf/count/route.ts import { NextRequest, NextResponse } from "next/server" import { getServerSession } from "next-auth" import { authOptions } from "@/app/api/auth/[...nextauth]/route" import db from "@/db/db" import { rfqLastTbePdftronComments } from "@/db/schema" import { inArray } from "drizzle-orm" import { parseStringPromise } from "xml2js" type Counts = { totalCount: number; openCount: number } function fromCommentSummary(summary: any | null | undefined): Counts | null { if (!summary) return null // commentSummary가 다음 형태를 따른다고 가정: // { totalCount?: number, openCount?: number } 또는 유사 구조 const t = Number((summary as any)?.totalCount) const o = Number((summary as any)?.openCount) if (Number.isFinite(t)) { return { totalCount: t, openCount: Number.isFinite(o) ? o : t } } return null } async function fromXfdfString(xfdf: string | null | undefined): Promise { if (!xfdf) return null try { const xml = await parseStringPromise(xfdf, { explicitArray: true }) // XFDF 기본 구조: xfdf.annotations[0].annotation = [...] const ann = xml?.xfdf?.annotations?.[0]?.annotation ?? xml?.xfdf?.fdf?.annots?.[0]?.annot ?? [] // 방어적 const total = Array.isArray(ann) ? ann.length : 0 // “오픈/클로즈드” 판단 로직은 팀의 규칙에 맞게 조정: // - 상태(StateModel/State) 혹은 CustomData를 쓰는 경우가 많음. // - 기본 폴백: 전부 오픈으로 간주. let open = total // 예: Completed 이면 클로즈드로 처리 // (실제 저장 스키마에 맞춰 커스터마이즈하세요.) let closed = 0 if (Array.isArray(ann)) { for (const a of ann) { const status = a?.status?.[0] || a?.["it:status"]?.[0] || a?.state?.[0] || a?.custom?.[0]?.status?.[0] if ( typeof status === "string" && ["Completed", "Resolved", "Accepted", "Rejected", "Closed"].includes(status) ) { closed += 1 } } } open = Math.max(total - closed, 0) return { totalCount: total, openCount: open } } catch { return null } } type CommentSummary = { total?: number open?: number resolved?: number rejected?: number deferred?: number byAuthor?: Record byCategory?: Record bySeverity?: Record } type Counts = { totalCount: number; openCount: number } function countsFromSummary(s?: CommentSummary | null): Counts | null { if (!s) return null // 1) open이 있으면 그걸 신뢰 if (Number.isFinite(s.open) && Number.isFinite(s.total)) { return { totalCount: s.total!, openCount: s.open! } } // 2) open이 없으면 상태 기반으로 계산 if (Number.isFinite(s.total)) { const resolved = Number(s.resolved ?? 0) const rejected = Number(s.rejected ?? 0) const deferred = Number(s.deferred ?? 0) const open = Math.max(s.total! - resolved - rejected - deferred, 0) return { totalCount: s.total!, openCount: open } } // 3) total이 누락된 희귀 케이스 → 분포 합으로 추정 const sum = (...recs: (Record | undefined)[]) => recs.reduce((acc, r) => acc + (r ? Object.values(r).reduce((a, b) => a + (b || 0), 0) : 0), 0) const guessedTotal = sum(s.byAuthor, s.byCategory, s.bySeverity) if (guessedTotal > 0) { const open = Number(s.open ?? Math.max(guessedTotal - Number(s.resolved ?? 0) - Number(s.rejected ?? 0) - Number(s.deferred ?? 0), 0)) return { totalCount: guessedTotal, openCount: open } } return null } export async function GET(request: NextRequest) { try { const session = await getServerSession(authOptions) if (!session?.user) { return NextResponse.json({ error: "인증이 필요합니다." }, { status: 401 }) } const idsParam = request.nextUrl.searchParams.get("ids") if (!idsParam) { return NextResponse.json({ error: "ids is required (comma-separated)" }, { status: 400 }) } const ids = idsParam .split(",") .map((s) => s.trim()) .filter(Boolean) .map((s) => Number(s)) .filter((n) => Number.isFinite(n)) if (ids.length === 0) { return NextResponse.json({ error: "no valid ids" }, { status: 400 }) } // 한 번에 조회 const rows = await db .select() .from(rfqLastTbePdftronComments) .where(inArray(rfqLastTbePdftronComments.documentReviewId, ids)) const result: Record< number, { totalCount: number; openCount: number; updatedAt: string | null } > = {} // 기본값: 코멘트 없음 → 0/0 for (const id of ids) { result[id] = { totalCount: 0, openCount: 0, updatedAt: null } } // 요약 우선 → XFDF 파싱 폴백 await Promise.all( rows.map(async (r: any) => { const id = Number(r.documentReviewId) let counts = countsFromSummary(r.commentSummary as CommentSummary) || (await fromXfdfString(r.xfdfString)) || // 폴백 { totalCount: 0, openCount: 0 } result[id] = { totalCount: counts.totalCount, openCount: counts.openCount, updatedAt: r.updatedAt ?? null, } }) ) return NextResponse.json({ data: result }) } catch (err) { console.error("xfdf/count GET error:", err) return NextResponse.json({ error: "Failed to fetch counts" }, { status: 500 }) } }