190 lines
6.4 KiB
TypeScript
190 lines
6.4 KiB
TypeScript
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
|
import { Prisma } from '@prisma/client';
|
|
import { PrismaService } from '../../common/prisma/prisma.module';
|
|
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
|
import type { AdminHqAccountsQueryDto } from './dto/admin-query.dto';
|
|
import type { CreateHqAccountDto, UpdateHqAccountDto } from './dto/admin-mutate.dto';
|
|
import { hashPassword } from '../../common/crypto/password.util';
|
|
|
|
function mapHqAccountRow(account: {
|
|
id: bigint;
|
|
phone: string;
|
|
loginName: string | null;
|
|
passwordHash: string | null;
|
|
name: string;
|
|
adminRole: string;
|
|
status: string;
|
|
lastLoginAt: Date | null;
|
|
createdAt: Date;
|
|
}) {
|
|
return {
|
|
id: account.id,
|
|
phone: account.phone,
|
|
loginName: account.loginName,
|
|
hasPassword: !!account.passwordHash,
|
|
name: account.name,
|
|
adminRole: account.adminRole,
|
|
status: account.status,
|
|
lastLoginAt: account.lastLoginAt,
|
|
createdAt: account.createdAt,
|
|
};
|
|
}
|
|
|
|
@Injectable()
|
|
export class AdminHqAccountsService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async list(query: AdminHqAccountsQueryDto) {
|
|
const page = query.page ?? 1;
|
|
const pageSize = query.pageSize ?? 20;
|
|
const where: Prisma.HqAccountWhereInput = {};
|
|
if (query.phone) where.phone = { contains: query.phone };
|
|
if (query.adminRole) where.adminRole = query.adminRole as Prisma.EnumHqAdminRoleFilter['equals'];
|
|
if (query.status) where.status = query.status as Prisma.EnumAccountStatusFilter['equals'];
|
|
|
|
const [items, total] = await Promise.all([
|
|
this.prisma.hqAccount.findMany({
|
|
where,
|
|
orderBy: { createdAt: 'desc' },
|
|
skip: (page - 1) * pageSize,
|
|
take: pageSize,
|
|
select: {
|
|
id: true,
|
|
phone: true,
|
|
loginName: true,
|
|
passwordHash: true,
|
|
name: true,
|
|
adminRole: true,
|
|
status: true,
|
|
lastLoginAt: true,
|
|
createdAt: true,
|
|
},
|
|
}),
|
|
this.prisma.hqAccount.count({ where }),
|
|
]);
|
|
return serializeBigInt({
|
|
items: items.map(mapHqAccountRow),
|
|
total,
|
|
page,
|
|
pageSize,
|
|
});
|
|
}
|
|
|
|
async detail(id: bigint) {
|
|
const account = await this.prisma.hqAccount.findUnique({
|
|
where: { id },
|
|
select: {
|
|
id: true,
|
|
phone: true,
|
|
loginName: true,
|
|
passwordHash: true,
|
|
name: true,
|
|
adminRole: true,
|
|
status: true,
|
|
lastLoginAt: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
if (!account) throw new NotFoundException('HQ 账号不存在');
|
|
return serializeBigInt(mapHqAccountRow(account));
|
|
}
|
|
|
|
async create(dto: CreateHqAccountDto) {
|
|
const adminRole = (dto.adminRole ?? 'OPS') as 'SUPER_ADMIN' | 'OPS' | 'FINANCE' | 'CUSTOMER_SERVICE';
|
|
|
|
if (dto.credentialType === 'phone') {
|
|
if (!dto.phone?.trim()) throw new BadRequestException('请填写手机号');
|
|
const phone = dto.phone.trim();
|
|
const exists = await this.prisma.hqAccount.findUnique({ where: { phone } });
|
|
if (exists) throw new BadRequestException('手机号已存在');
|
|
const account = await this.prisma.hqAccount.create({
|
|
data: { phone, name: dto.name, adminRole },
|
|
});
|
|
return serializeBigInt(mapHqAccountRow({ ...account, passwordHash: null }));
|
|
}
|
|
|
|
if (!dto.loginName?.trim() || !dto.password) {
|
|
throw new BadRequestException('账号密码模式需填写用户名和密码');
|
|
}
|
|
const loginName = dto.loginName.trim();
|
|
const loginTaken = await this.prisma.hqAccount.findUnique({ where: { loginName } });
|
|
if (loginTaken) throw new BadRequestException('用户名已存在');
|
|
|
|
const phone = dto.phone?.trim() || (await this.generatePlaceholderPhone());
|
|
const phoneTaken = await this.prisma.hqAccount.findUnique({ where: { phone } });
|
|
if (phoneTaken) throw new BadRequestException('手机号已存在');
|
|
|
|
const account = await this.prisma.hqAccount.create({
|
|
data: {
|
|
phone,
|
|
loginName,
|
|
passwordHash: hashPassword(dto.password),
|
|
name: dto.name,
|
|
adminRole,
|
|
},
|
|
});
|
|
return serializeBigInt(mapHqAccountRow(account));
|
|
}
|
|
|
|
async update(id: bigint, dto: UpdateHqAccountDto) {
|
|
const current = await this.prisma.hqAccount.findUnique({ where: { id } });
|
|
if (!current) throw new NotFoundException('HQ 账号不存在');
|
|
|
|
if (dto.loginName !== undefined) {
|
|
const loginName = dto.loginName.trim();
|
|
if (!loginName) throw new BadRequestException('用户名不能为空');
|
|
const conflict = await this.prisma.hqAccount.findFirst({
|
|
where: { loginName, id: { not: id } },
|
|
});
|
|
if (conflict) throw new BadRequestException('用户名已存在');
|
|
}
|
|
|
|
if (dto.phone !== undefined) {
|
|
const phone = dto.phone.trim();
|
|
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
|
throw new BadRequestException('请输入正确的手机号');
|
|
}
|
|
const phoneTaken = await this.prisma.hqAccount.findUnique({ where: { phone } });
|
|
if (phoneTaken && phoneTaken.id !== id) {
|
|
throw new BadRequestException('手机号已存在');
|
|
}
|
|
}
|
|
|
|
const account = await this.prisma.hqAccount.update({
|
|
where: { id },
|
|
data: {
|
|
...(dto.name !== undefined ? { name: dto.name } : {}),
|
|
...(dto.phone !== undefined ? { phone: dto.phone.trim() } : {}),
|
|
...(dto.loginName !== undefined ? { loginName: dto.loginName.trim() } : {}),
|
|
...(dto.password ? { passwordHash: hashPassword(dto.password) } : {}),
|
|
...(dto.adminRole !== undefined
|
|
? { adminRole: dto.adminRole as 'SUPER_ADMIN' | 'OPS' | 'FINANCE' | 'CUSTOMER_SERVICE' }
|
|
: {}),
|
|
...(dto.status !== undefined ? { status: dto.status as 'ACTIVE' | 'DISABLED' } : {}),
|
|
},
|
|
select: {
|
|
id: true,
|
|
phone: true,
|
|
loginName: true,
|
|
passwordHash: true,
|
|
name: true,
|
|
adminRole: true,
|
|
status: true,
|
|
lastLoginAt: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
return serializeBigInt(mapHqAccountRow(account));
|
|
}
|
|
|
|
private async generatePlaceholderPhone(): Promise<string> {
|
|
for (let i = 0; i < 8; i += 1) {
|
|
const suffix = `${Date.now()}${Math.floor(Math.random() * 1000)}`.slice(-8);
|
|
const phone = `199${suffix}`;
|
|
const exists = await this.prisma.hqAccount.findUnique({ where: { phone } });
|
|
if (!exists) return phone;
|
|
}
|
|
throw new BadRequestException('无法生成占位手机号,请手动填写');
|
|
}
|
|
}
|