123 lines
3.7 KiB
TypeScript
123 lines
3.7 KiB
TypeScript
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 }
|
||
)
|
||
}
|
||
}
|
||
|