webadmin增加显示子账号显示列表

修复api服务端报错
This commit is contained in:
2026-07-10 10:36:01 +08:00
parent b1d5b3214f
commit 2368121972
6 changed files with 401 additions and 45 deletions
@@ -6,6 +6,7 @@ export const HqOperationAction = {
PARTNER_UPDATE: 'PARTNER_UPDATE',
PARTNER_ACCOUNT_CREATE: 'PARTNER_ACCOUNT_CREATE',
PARTNER_ACCOUNT_UPDATE: 'PARTNER_ACCOUNT_UPDATE',
PARTNER_ACCOUNT_DELETE: 'PARTNER_ACCOUNT_DELETE',
HQ_ACCOUNT_CREATE: 'HQ_ACCOUNT_CREATE',
HQ_ACCOUNT_UPDATE: 'HQ_ACCOUNT_UPDATE',
HQ_PERMISSION_UPDATE: 'HQ_PERMISSION_UPDATE',
@@ -50,6 +51,7 @@ export const HQ_OPERATION_ACTION_LABELS: Record<string, string> = {
[HqOperationAction.PARTNER_UPDATE]: '编辑城市合伙人',
[HqOperationAction.PARTNER_ACCOUNT_CREATE]: '新增合伙人账户',
[HqOperationAction.PARTNER_ACCOUNT_UPDATE]: '编辑合伙人账户',
[HqOperationAction.PARTNER_ACCOUNT_DELETE]: '删除合伙人子账号',
[HqOperationAction.HQ_ACCOUNT_CREATE]: '新增 HQ 管理员',
[HqOperationAction.HQ_ACCOUNT_UPDATE]: '编辑 HQ 管理员',
[HqOperationAction.HQ_PERMISSION_UPDATE]: '配置 HQ 权限',
@@ -1,4 +1,4 @@
import { Body, Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
@@ -54,6 +54,11 @@ export class AdminPartnersController {
export class AdminPartnerAccountsController {
constructor(private readonly service: AdminPartnersService) {}
@Get('tree')
tree(@Query('partnerId') partnerId?: string) {
return this.service.listPartnerAccountTree(partnerId ? BigInt(partnerId) : undefined);
}
@Get()
list(@Query() query: AdminPartnerAccountsQueryDto) {
return this.service.listPartnerAccounts(query);
@@ -85,4 +90,14 @@ export class AdminPartnerAccountsController {
update(@Param('id') id: string, @Body() dto: UpdatePartnerAccountDto) {
return this.service.updatePartnerAccount(BigInt(id), dto);
}
@Delete(':id')
@HqOperation({
action: HqOperationAction.PARTNER_ACCOUNT_DELETE,
refType: 'PARTNER_ACCOUNT',
refIdParam: 'id',
})
remove(@Param('id') id: string) {
return this.service.deletePartnerSubAccount(BigInt(id));
}
}
@@ -94,10 +94,65 @@ export class AdminPartnersService {
return serializeBigInt({ items, total, page, pageSize });
}
async listPartnerAccountTree(partnerId?: bigint) {
const where: Prisma.PartnerAccountWhereInput = {};
if (partnerId) where.partnerId = partnerId;
const accounts = await this.prisma.partnerAccount.findMany({
where,
orderBy: [{ isPrimary: 'desc' }, { createdAt: 'asc' }],
include: { partner: { select: { id: true, companyName: true } } },
});
type TreeNode = (typeof accounts)[number] & { children: TreeNode[] };
const nodeMap = new Map<string, TreeNode>();
const roots: TreeNode[] = [];
for (const account of accounts) {
nodeMap.set(account.id.toString(), { ...account, children: [] });
}
for (const account of accounts) {
const node = nodeMap.get(account.id.toString())!;
if (account.parentAccountId) {
const parent = nodeMap.get(account.parentAccountId.toString());
if (parent) parent.children.push(node);
else roots.push(node);
} else {
roots.push(node);
}
}
const mapNode = (node: TreeNode) => ({
id: node.id,
phone: node.phone,
name: node.name,
status: node.status,
isPrimary: node.isPrimary,
staffRole: node.staffRole,
parentAccountId: node.parentAccountId,
partner: node.partner,
createdAt: node.createdAt,
lastLoginAt: node.lastLoginAt,
children: node.children.length ? node.children.map(mapNode) : undefined,
});
return serializeBigInt(roots.map(mapNode));
}
async detailPartnerAccount(id: bigint) {
const account = await this.prisma.partnerAccount.findUnique({
where: { id },
include: { partner: true },
include: {
partner: {
select: {
id: true,
companyName: true,
contactPhone: true,
address: true,
},
},
},
});
if (!account) throw new NotFoundException('开城合伙人账号不存在');
@@ -126,16 +181,49 @@ export class AdminPartnersService {
}
async createPartnerAccount(dto: CreatePartnerAccountDto) {
const partner = await this.prisma.partner.findUnique({ where: { id: BigInt(dto.partnerId) } });
const phone = dto.phone.trim();
if (!/^1[3-9]\d{9}$/.test(phone)) {
throw new BadRequestException('请输入正确的手机号码');
}
const phoneTaken = await this.prisma.partnerAccount.findUnique({ where: { phone } });
if (phoneTaken) throw new BadRequestException('该手机号已被使用');
if (dto.parentAccountId) {
const parent = await this.prisma.partnerAccount.findUnique({
where: { id: BigInt(dto.parentAccountId) },
});
if (!parent) throw new BadRequestException('主账号不存在');
if (parent.isPrimary !== 1) throw new BadRequestException('仅可向主账号添加子账号');
if (dto.partnerId && dto.partnerId !== parent.partnerId.toString()) {
throw new BadRequestException('开城合伙人与主账号不匹配');
}
const account = await this.prisma.partnerAccount.create({
data: {
partnerId: parent.partnerId,
phone,
name: dto.name.trim(),
staffRole: (dto.staffRole ?? 'INTERNAL') as 'PARTNER' | 'INTERNAL' | 'PROMOTER',
isPrimary: 0,
parentAccountId: parent.id,
status: 'ACTIVE',
},
include: { partner: { select: { id: true, companyName: true } } },
});
return serializeBigInt(account);
}
const partner = await this.prisma.partner.findUnique({ where: { id: BigInt(dto.partnerId!) } });
if (!partner) throw new BadRequestException('开城合伙人不存在');
const account = await this.prisma.partnerAccount.create({
data: {
partnerId: partner.id,
phone: dto.phone,
name: dto.name,
phone,
name: dto.name.trim(),
staffRole: dto.staffRole ? (dto.staffRole as 'PARTNER' | 'INTERNAL' | 'PROMOTER') : undefined,
isPrimary: 0,
},
include: { partner: { select: { id: true, companyName: true } } },
});
return serializeBigInt(account);
}
@@ -162,4 +250,14 @@ export class AdminPartnersService {
const account = await this.prisma.partnerAccount.update({ where: { id }, data });
return serializeBigInt(account);
}
async deletePartnerSubAccount(id: bigint) {
const account = await this.prisma.partnerAccount.findUnique({ where: { id } });
if (!account) throw new NotFoundException('开城合伙人账号不存在');
if (!account.parentAccountId) {
throw new BadRequestException('仅可删除子账号');
}
await this.prisma.partnerAccount.delete({ where: { id } });
return { ok: true };
}
}
@@ -211,9 +211,10 @@ export class UpdatePartnerAccountDto {
}
export class CreatePartnerAccountDto {
@ValidateIf((o: CreatePartnerAccountDto) => !o.parentAccountId)
@IsString()
@IsNotEmpty()
partnerId: string;
partnerId?: string;
@IsString()
@IsNotEmpty()
@@ -226,6 +227,11 @@ export class CreatePartnerAccountDto {
@IsOptional()
@IsIn(['PARTNER', 'INTERNAL', 'PROMOTER'])
staffRole?: string;
/** 主账号 ID;传入则创建子账号 */
@IsOptional()
@IsString()
parentAccountId?: string;
}
export class CreateCityDto {