9a826c16b0
1. 注册时自动创建根部门(公司名,level=0),作为组织架构顶层节点 2. 部门列表查询时兼容历史组织:若无任何部门,自动补建根部门 3. 创建部门时若未指定父部门,默认挂到根部门下 4. 前端 OrgChart: - 根部门显示"公司"标签,不可编辑/删除 - 新增部门时默认父部门为根部门 - 上级部门下拉移除"无(根部门)"选项,改为列出所有可选部门 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
158 lines
4.5 KiB
TypeScript
158 lines
4.5 KiB
TypeScript
import bcrypt from 'bcryptjs'
|
|
import prisma from '../lib/prisma'
|
|
import { signAccessToken, signRefreshToken, verifyRefreshToken } from '../lib/jwt'
|
|
|
|
export async function register(orgName: string, phone: string, password: string) {
|
|
const existing = await prisma.user.findUnique({ where: { phone } })
|
|
if (existing) {
|
|
throw { code: 'DUPLICATE', message: '该手机号已注册' }
|
|
}
|
|
|
|
const org = await prisma.organization.create({
|
|
data: {
|
|
name: orgName,
|
|
plan: 'FREE',
|
|
maxEmployees: 20,
|
|
},
|
|
})
|
|
|
|
const passwordHash = await bcrypt.hash(password, 10)
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
orgId: org.id,
|
|
phone,
|
|
name: '管理员',
|
|
passwordHash,
|
|
role: 'ADMIN',
|
|
},
|
|
})
|
|
|
|
// 自动创建根部门(公司名),作为组织架构的顶层节点
|
|
await prisma.department.create({
|
|
data: {
|
|
orgId: org.id,
|
|
name: orgName,
|
|
parentId: null,
|
|
level: 0,
|
|
sortOrder: 0,
|
|
description: '组织根节点',
|
|
createdBy: user.id,
|
|
},
|
|
})
|
|
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: { lastLoginAt: new Date() },
|
|
})
|
|
|
|
const accessToken = signAccessToken({ id: user.id, orgId: user.orgId, role: user.role })
|
|
const refreshToken = signRefreshToken({ id: user.id, orgId: user.orgId, role: user.role })
|
|
|
|
return {
|
|
user: { id: user.id, orgId: user.orgId, name: user.name, phone: user.phone, role: user.role },
|
|
accessToken,
|
|
refreshToken,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 平台管理员登录(仅 SUPER_ADMIN 角色可登录平台端)
|
|
*/
|
|
export async function platformLogin(phone: string, password: string) {
|
|
const user = await prisma.user.findUnique({ where: { phone } })
|
|
if (!user) {
|
|
throw { code: 'NOT_FOUND', message: '手机号或密码错误' }
|
|
}
|
|
|
|
const valid = await bcrypt.compare(password, user.passwordHash)
|
|
if (!valid) {
|
|
throw { code: 'AUTH_FAILED', message: '手机号或密码错误' }
|
|
}
|
|
|
|
if (user.role !== 'SUPER_ADMIN') {
|
|
throw { code: 'FORBIDDEN', message: '该账号无平台管理权限' }
|
|
}
|
|
|
|
if (user.disabled) {
|
|
throw { code: 'ACCOUNT_DISABLED', message: '该账号已被禁用' }
|
|
}
|
|
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: { lastLoginAt: new Date() },
|
|
})
|
|
|
|
const accessToken = signAccessToken({ id: user.id, orgId: user.orgId, role: user.role })
|
|
const refreshToken = signRefreshToken({ id: user.id, orgId: user.orgId, role: user.role })
|
|
|
|
return {
|
|
user: { id: user.id, orgId: user.orgId, name: user.name, phone: user.phone, role: user.role },
|
|
accessToken,
|
|
refreshToken,
|
|
}
|
|
}
|
|
|
|
export async function login(phone: string, password: string) {
|
|
const user = await prisma.user.findUnique({ where: { phone } })
|
|
if (!user) {
|
|
throw { code: 'NOT_FOUND', message: '手机号或密码错误' }
|
|
}
|
|
|
|
const valid = await bcrypt.compare(password, user.passwordHash)
|
|
if (!valid) {
|
|
throw { code: 'AUTH_FAILED', message: '手机号或密码错误' }
|
|
}
|
|
|
|
if (user.disabled) {
|
|
throw { code: 'ACCOUNT_DISABLED', message: '该账号已被禁用,请联系管理员' }
|
|
}
|
|
|
|
if (user.role === 'SUPER_ADMIN') {
|
|
throw { code: 'FORBIDDEN', message: '平台管理员请使用平台管理入口登录' }
|
|
}
|
|
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: { lastLoginAt: new Date() },
|
|
})
|
|
|
|
const accessToken = signAccessToken({ id: user.id, orgId: user.orgId, role: user.role })
|
|
const refreshToken = signRefreshToken({ id: user.id, orgId: user.orgId, role: user.role })
|
|
|
|
return {
|
|
user: { id: user.id, orgId: user.orgId, name: user.name, phone: user.phone, role: user.role },
|
|
accessToken,
|
|
refreshToken,
|
|
}
|
|
}
|
|
|
|
export async function refresh(refreshToken: string) {
|
|
const payload = verifyRefreshToken(refreshToken)
|
|
if (!payload) {
|
|
throw { code: 'TOKEN_INVALID', message: 'Refresh Token 无效或已过期' }
|
|
}
|
|
|
|
const user = await prisma.user.findUnique({ where: { id: payload.id } })
|
|
if (!user) {
|
|
throw { code: 'NOT_FOUND', message: '用户不存在' }
|
|
}
|
|
|
|
const accessToken = signAccessToken({ id: user.id, orgId: user.orgId, role: user.role })
|
|
return { accessToken }
|
|
}
|
|
|
|
export async function resetPassword(phone: string, newPassword: string) {
|
|
const user = await prisma.user.findUnique({ where: { phone } })
|
|
if (!user) {
|
|
throw { code: 'NOT_FOUND', message: '手机号未注册' }
|
|
}
|
|
|
|
const passwordHash = await bcrypt.hash(newPassword, 10)
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: { passwordHash },
|
|
})
|
|
|
|
return { success: true }
|
|
}
|