2026-06-03 18:41:52 +08:00

123 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { Prisma } from '@prisma/client'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import { planDepartmentScopeWithPaidCore } from '@/lib/license'
import {
buildMaterialManagementScopeWhere,
getMaterialPermissionActor,
type MaterialPermissionActor,
} from '@/lib/material-permissions'
export const dynamic = 'force-dynamic'
const normalizeDepartment = (department?: string | null) => (department || '').trim()
async function buildMonthlyStatsWhereSql(actor: MaterialPermissionActor) {
const conditions: Prisma.Sql[] = [Prisma.sql`m."deletedAt" IS NULL`]
if (!actor.isSuperAdmin) {
const visibleConditions: Prisma.Sql[] = [
Prisma.sql`m."userId" = ${actor.id}`,
Prisma.sql`(m."ownerType" = 'USER' AND m."ownerId" = ${actor.id})`,
]
const scopeDepartment = normalizeDepartment(actor.managedDepartment)
if (actor.role === 'ADMIN' && scopeDepartment) {
const departments = await prisma.department.findMany({ select: { name: true } })
const scopePlan = await planDepartmentScopeWithPaidCore({
scopeDepartment,
departments,
})
if (scopePlan.valid && scopePlan.scopedDepartments.length > 0) {
visibleConditions.push(Prisma.sql`
EXISTS (
SELECT 1
FROM "users" u
WHERE u."id" = m."userId"
AND (
u."department" IN (${Prisma.join(scopePlan.scopedDepartments)})
OR u."department" LIKE ${`${scopeDepartment}-%`}
)
)
`)
}
}
conditions.push(Prisma.sql`(${Prisma.join(visibleConditions, ' OR ')})`)
}
return Prisma.sql`WHERE ${Prisma.join(conditions, ' AND ')}`
}
// Get material statistics (total count and monthly breakdown)
// 优化:使用SQL聚合查询,避免加载所有数据到内存
export async function GET() {
try {
const session = await getServerSession(authOptions)
if (!session?.user) {
return NextResponse.json(
{ error: '请先登录' },
{ status: 401 }
)
}
const actor = await getMaterialPermissionActor(session)
if (!actor) {
return NextResponse.json(
{ error: '账号不可用,请重新登录或联系管理员' },
{ status: 403 }
)
}
const scopeWhere = await buildMaterialManagementScopeWhere(actor)
const where: Prisma.MaterialWhereInput = Object.keys(scopeWhere).length > 0
? { AND: [{ deletedAt: null }, scopeWhere] }
: { deletedAt: null }
const monthlyStatsWhereSql = await buildMonthlyStatsWhereSql(actor)
// 并行查询总数和按月统计
const [total, monthlyStats] = await Promise.all([
prisma.material.count({ where }),
prisma.$queryRaw<Array<{
year: number
month: number
count: bigint
}>>`
SELECT
EXTRACT(YEAR FROM "createdAt")::int as year,
EXTRACT(MONTH FROM "createdAt")::int as month,
COUNT(*)::bigint as count
FROM "materials" m
${monthlyStatsWhereSql}
GROUP BY EXTRACT(YEAR FROM "createdAt"), EXTRACT(MONTH FROM "createdAt")
ORDER BY year DESC, month DESC
`
])
// 格式化月份数据
const months = monthlyStats.map(item => {
const monthKey = `${item.year}-${String(item.month).padStart(2, '0')}`
return {
value: monthKey,
label: `${item.year}年${item.month}月`,
count: Number(item.count)
}
})
return NextResponse.json({
total,
months
})
} catch (error) {
console.error('[MaterialStats] Get error:', error)
return NextResponse.json(
{ error: '获取统计数据失败' },
{ status: 500 }
)
}
}