/** * 平台管理员路由 * 管理所有企业租户、用户、数据总览 * 所有接口需要 SUPER_ADMIN 权限 */ import { Router } from 'express' import prisma from '../lib/prisma' import { AuthRequest, authMiddleware, platformAdminMiddleware } from '../middleware/auth' import { parsePagination } from '../lib/pagination' import { createOrgSchema, updateOrgSchema, updateOrgAdminSchema, createPlatformAdminSchema } from '../schemas/platform.schema' const router = Router() // 所有平台路由都需要认证 + SUPER_ADMIN 权限 router.use(authMiddleware, platformAdminMiddleware) // ========== 数据总览 ========== /** * 平台总览数据 */ router.get('/dashboard', async (_req: AuthRequest, res, next) => { try { const now = new Date() const [orgs, users, employees, contracts, activeContracts, payslips] = await Promise.all([ prisma.organization.count(), prisma.user.count({ where: { role: { not: 'SUPER_ADMIN' } } }), prisma.employee.count(), prisma.laborContract.count(), prisma.laborContract.count({ where: { OR: [ { endDate: null }, { endDate: { gte: now } }, ], }, }), prisma.payslip.count(), ]) // 按套餐分组 const orgsByPlan = await prisma.organization.groupBy({ by: ['plan'], _count: true, }) // 最近 7 天注册的企业 const recentOrgs = await prisma.organization.findMany({ orderBy: { createdAt: 'desc' }, take: 5, select: { id: true, name: true, plan: true, city: true, createdAt: true, maxEmployees: true, _count: { select: { employees: true, users: true } }, }, }) res.json({ success: true, data: { totalOrgs: orgs, totalUsers: users, totalEmployees: employees, totalContracts: contracts, activeContracts, totalPayslips: payslips, orgsByPlan: orgsByPlan.map((g: any) => ({ plan: g.plan, count: g._count })), recentOrgs: recentOrgs.map((o: any) => ({ id: o.id, name: o.name, plan: o.plan, city: o.city, createdAt: o.createdAt, maxEmployees: o.maxEmployees, employeeCount: o._count.employees, userCount: o._count.users, })), }, }) } catch (err) { next(err) } }) // ========== 企业租户管理 ========== /** * 企业列表(分页 + 搜索) */ router.get('/orgs', async (req: AuthRequest, res, next) => { try { const { page, pageSize } = parsePagination(req.query) const search = (req.query.search as string) || '' const planFilter = (req.query.plan as string) || '' const where: any = {} if (search) { where.OR = [ { name: { contains: search, mode: 'insensitive' } }, { city: { contains: search, mode: 'insensitive' } }, { contactName: { contains: search, mode: 'insensitive' } }, { contactPhone: { contains: search } }, ] } if (planFilter) { where.plan = planFilter } const [orgs, total] = await Promise.all([ prisma.organization.findMany({ where, orderBy: { createdAt: 'desc' }, skip: (page - 1) * pageSize, take: pageSize, select: { id: true, name: true, plan: true, maxEmployees: true, city: true, contactName: true, contactPhone: true, payrollDays: true, retirementReminderEnabled: true, createdAt: true, updatedAt: true, _count: { select: { employees: true, users: true, contracts: true, payslips: true }, }, }, }), prisma.organization.count({ where }), ]) res.json({ success: true, data: { list: orgs.map((o: any) => ({ ...o, employeeCount: o._count.employees, userCount: o._count.users, contractCount: o._count.contracts, payslipCount: o._count.payslips, _count: undefined, })), total, page, pageSize, }, }) } catch (err) { next(err) } }) /** * 创建企业客户(含管理员账号) */ router.post('/orgs', async (req: AuthRequest, res, next) => { try { const { name, plan, maxEmployees, city, contactName, contactPhone, adminName, adminPhone, adminPassword, } = createOrgSchema.parse(req.body) const existing = await prisma.user.findUnique({ where: { phone: adminPhone } }) if (existing) { return res.status(400).json({ success: false, error: { code: 'DUPLICATE', message: '该手机号已存在' } }) } // 创建企业 const org = await prisma.organization.create({ data: { name, plan: (plan as any) || 'FREE', maxEmployees: maxEmployees || 20, city: city || null, contactName: contactName || null, contactPhone: contactPhone || null, }, }) // 创建管理员账号 const bcrypt = await import('bcryptjs') const passwordHash = await bcrypt.default.hash(adminPassword, 10) const admin = await prisma.user.create({ data: { orgId: org.id, phone: adminPhone, name: adminName || '管理员', passwordHash, role: 'ADMIN', }, select: { id: true, name: true, phone: true, role: true }, }) res.json({ success: true, data: { org: { id: org.id, name: org.name, plan: org.plan, maxEmployees: org.maxEmployees, city: org.city }, admin, }, }) } catch (err) { next(err) } }) /** * 企业详情 */ router.get('/orgs/:id', async (req: AuthRequest, res, next) => { try { const org = await prisma.organization.findUnique({ where: { id: req.params.id }, include: { _count: { select: { employees: true, users: true, contracts: true, payslips: true }, }, users: { select: { id: true, name: true, phone: true, role: true, disabled: true, lastLoginAt: true, createdAt: true }, orderBy: { createdAt: 'asc' }, }, }, }) if (!org) { return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '企业不存在' } }) } res.json({ success: true, data: org }) } catch (err) { next(err) } }) /** * 更新企业信息(套餐、员工上限等) */ router.put('/orgs/:id', async (req: AuthRequest, res, next) => { try { const { name, plan, maxEmployees, city, contactName, contactPhone } = updateOrgSchema.parse(req.body) const updateData: any = {} if (name) updateData.name = name if (plan) updateData.plan = plan if (maxEmployees !== undefined) updateData.maxEmployees = maxEmployees if (city !== undefined) updateData.city = city if (contactName !== undefined) updateData.contactName = contactName if (contactPhone !== undefined) updateData.contactPhone = contactPhone const org = await prisma.organization.update({ where: { id: req.params.id }, data: updateData, select: { id: true, name: true, plan: true, maxEmployees: true, city: true, contactName: true, contactPhone: true }, }) res.json({ success: true, data: org }) } catch (err) { next(err) } }) /** * 编辑企业管理员(姓名、手机号、可选重置密码) */ router.put('/orgs/:id/admin', async (req: AuthRequest, res, next) => { try { const { adminName, adminPhone, adminPassword } = updateOrgAdminSchema.parse(req.body) // 找到该企业的 ADMIN 角色用户(第一个管理员) const admin = await prisma.user.findFirst({ where: { orgId: req.params.id, role: 'ADMIN' }, orderBy: { createdAt: 'asc' }, }) if (!admin) { return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '该企业未设置管理员' } }) } // 如果修改了手机号,检查是否已被其他用户占用 if (adminPhone && adminPhone !== admin.phone) { const existing = await prisma.user.findUnique({ where: { phone: adminPhone } }) if (existing && existing.id !== admin.id) { return res.status(409).json({ success: false, error: { code: 'DUPLICATE_PHONE', message: '该手机号已被使用' } }) } } const updateData: any = {} if (adminName) updateData.name = adminName if (adminPhone) updateData.phone = adminPhone if (adminPassword && adminPassword.length >= 8) { const bcrypt = require('bcryptjs') updateData.passwordHash = await bcrypt.hash(adminPassword, 10) } const updated = await prisma.user.update({ where: { id: admin.id }, data: updateData, select: { id: true, name: true, phone: true, role: true }, }) res.json({ success: true, data: updated }) } catch (err) { next(err) } }) /** * 删除企业(级联删除所有数据) */ router.delete('/orgs/:id', async (req: AuthRequest, res, next) => { try { await prisma.organization.delete({ where: { id: req.params.id } }) res.json({ success: true, data: { message: '企业已删除' } }) } catch (err) { next(err) } }) // ========== 用户管理 ========== /** * 所有企业用户列表(分页 + 搜索) */ router.get('/users', async (req: AuthRequest, res, next) => { try { const { page, pageSize } = parsePagination(req.query) const search = (req.query.search as string) || '' const orgId = (req.query.orgId as string) || '' const where: any = { role: { not: 'SUPER_ADMIN' } } if (search) { where.OR = [ { name: { contains: search, mode: 'insensitive' } }, { phone: { contains: search } }, ] } if (orgId) { where.orgId = orgId } const [users, total] = await Promise.all([ prisma.user.findMany({ where, orderBy: { createdAt: 'desc' }, skip: (page - 1) * pageSize, take: pageSize, select: { id: true, name: true, phone: true, role: true, disabled: true, lastLoginAt: true, createdAt: true, org: { select: { id: true, name: true } }, }, }), prisma.user.count({ where }), ]) res.json({ success: true, data: { list: users.map((u: any) => ({ ...u, orgName: u.org?.name || null, org: undefined, })), total, page, pageSize, }, }) } catch (err) { next(err) } }) /** * 启用/禁用用户 */ router.put('/users/:id/toggle', async (req: AuthRequest, res, next) => { try { const user = await prisma.user.findUnique({ where: { id: req.params.id } }) if (!user) { return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '用户不存在' } }) } if (user.role === 'SUPER_ADMIN') { return res.status(400).json({ success: false, error: { code: 'FORBIDDEN', message: '不能操作平台管理员账号' } }) } const updated = await prisma.user.update({ where: { id: req.params.id }, data: { disabled: !user.disabled }, select: { id: true, name: true, phone: true, role: true, disabled: true }, }) res.json({ success: true, data: updated }) } catch (err) { next(err) } }) // ========== 平台管理员管理 ========== /** * 平台管理员列表 */ router.get('/admins', async (_req: AuthRequest, res, next) => { try { const admins = await prisma.user.findMany({ where: { role: 'SUPER_ADMIN' }, select: { id: true, name: true, phone: true, disabled: true, lastLoginAt: true, createdAt: true }, orderBy: { createdAt: 'asc' }, }) res.json({ success: true, data: admins }) } catch (err) { next(err) } }) /** * 创建平台管理员 */ router.post('/admins', async (req: AuthRequest, res, next) => { try { const { name, phone, password } = createPlatformAdminSchema.parse(req.body) const existing = await prisma.user.findUnique({ where: { phone } }) if (existing) { return res.status(400).json({ success: false, error: { code: 'DUPLICATE', message: '该手机号已存在' } }) } const bcrypt = await import('bcryptjs') const passwordHash = await bcrypt.default.hash(password, 10) const admin = await prisma.user.create({ data: { name, phone, passwordHash, role: 'SUPER_ADMIN', orgId: null }, select: { id: true, name: true, phone: true, role: true }, }) res.json({ success: true, data: admin }) } catch (err) { next(err) } }) export default router