feat(admin): HQ手机号编辑、合伙人子账号日志与子账号H5体验

支持 HQ 账户改手机号(格式校验)、合伙人日志包含子账号且子账号不显示主账号信息;改手机号时清除微信绑定。合伙人 H5 子账号页签与我的页、录入门店底部按钮及上传失败提示。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-12 13:29:09 +08:00
parent b8623f59f0
commit 63af3321dd
16 changed files with 259 additions and 40 deletions
@@ -139,10 +139,22 @@ export class AdminHqAccountsService {
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
@@ -64,23 +64,39 @@ export class AdminPartnerLogsService {
return where;
}
private async expandPrimaryWithChildren(primaryIds: bigint[]): Promise<bigint[]> {
if (!primaryIds.length) return [];
const children = await this.prisma.partnerAccount.findMany({
where: { parentAccountId: { in: primaryIds } },
select: { id: true },
});
return [...primaryIds, ...children.map((c) => c.id)];
}
private async resolvePartnerAccountIds(
query: AdminPartnerLogsQueryDto,
): Promise<bigint[] | undefined> {
if (query.partnerAccountId) return [BigInt(query.partnerAccountId)];
if (query.partnerId) return [BigInt(query.partnerId)];
if (query.partnerId) {
return this.expandPrimaryWithChildren([BigInt(query.partnerId)]);
}
const accountWhere: Prisma.PartnerAccountWhereInput = { isPrimary: 1 };
if (query.companyName) accountWhere.companyName = { contains: query.companyName };
if (query.phone) accountWhere.phone = { contains: query.phone };
if (query.companyName || query.phone) {
if (query.phone) {
const accounts = await this.prisma.partnerAccount.findMany({
where: accountWhere,
where: { phone: { contains: query.phone } },
select: { id: true },
take: 200,
});
return accounts.map((a) => a.id);
}
if (query.companyName) {
const primaries = await this.prisma.partnerAccount.findMany({
where: { isPrimary: 1, companyName: { contains: query.companyName } },
select: { id: true },
take: 100,
});
return accounts.map((a) => a.id);
return this.expandPrimaryWithChildren(primaries.map((a) => a.id));
}
return undefined;
@@ -102,7 +118,16 @@ export class AdminPartnerLogsService {
const accounts = accountIds.length
? await this.prisma.partnerAccount.findMany({
where: { id: { in: accountIds } },
select: { id: true, name: true, phone: true, companyName: true },
select: {
id: true,
name: true,
phone: true,
companyName: true,
isPrimary: true,
parentAccountId: true,
staffRole: true,
parent: { select: { id: true, companyName: true } },
},
})
: [];
@@ -110,13 +135,19 @@ export class AdminPartnerLogsService {
return rows.map((row) => {
const account = accountMap.get(row.partnerAccountId.toString());
const isSubAccount = !!account?.parentAccountId;
const primaryId = isSubAccount
? account?.parent?.id?.toString() ?? null
: account?.id.toString() ?? null;
return {
id: row.id.toString(),
partnerId: row.partnerAccountId.toString(),
partnerId: primaryId ?? row.partnerAccountId.toString(),
partnerAccountId: row.partnerAccountId.toString(),
accountName: account?.name ?? null,
accountPhone: account?.phone ?? null,
companyName: account?.companyName ?? null,
companyName: isSubAccount ? null : account?.companyName ?? null,
isSubAccount,
staffRole: account?.staffRole ?? null,
category: resolvePartnerLogCategory(row.eventName),
eventName: row.eventName,
clientApp: row.clientApp,
@@ -252,11 +252,15 @@ export class AdminPartnersService {
assertPartnerCommissionRates(city, orderCommissionRate, redeemCommissionRate);
}
const phoneChanged =
dto.phone !== undefined && dto.phone.trim() !== existing.phone;
const account = await this.prisma.partnerAccount.update({
where: { id },
data: {
...(dto.name !== undefined ? { name: dto.name.trim() } : {}),
...(dto.phone !== undefined ? { phone: dto.phone.trim() } : {}),
...(phoneChanged ? { wxOpenId: null, wxUnionId: null } : {}),
...(dto.companyName !== undefined ? { companyName: dto.companyName.trim() } : {}),
...(dto.address !== undefined ? { address: dto.address.trim() } : {}),
...(dto.contactPhone !== undefined ? { contactPhone: dto.contactPhone.trim() } : {}),
@@ -446,6 +450,10 @@ export class AdminPartnersService {
throw new BadRequestException('该手机号已被使用');
}
data.phone = phone;
if (phone !== existing.phone) {
data.wxOpenId = null;
data.wxUnionId = null;
}
}
const account = await this.prisma.partnerAccount.update({ where: { id }, data });
@@ -734,6 +734,10 @@ export class UpdateHqAccountDto {
@IsString()
loginName?: string;
@IsOptional()
@IsString()
phone?: string;
@IsOptional()
@IsString()
@MinLength(6)
@@ -1,4 +1,5 @@
import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
import { BadRequestException, Body, Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { IsOptional, IsString } from 'class-validator';
import { SettlementService } from './settlement.service';
import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard';
import { PartnerPrimaryGuard } from '../../common/guards/partner-primary.guard';
@@ -9,6 +10,12 @@ import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
import { PrismaService } from '../../common/prisma/prisma.module';
class UpdatePartnerMeDto {
@IsOptional()
@IsString()
name?: string;
}
@Controller('partner/settlement')
@UseGuards(JwtAuthGuard, PartnerPrimaryGuard)
export class SettlementController {
@@ -156,8 +163,26 @@ export class PartnerMeController {
@Get()
async me(@CurrentUser() user: AuthUser) {
return this.buildPartnerMe(user.actorId);
}
@Put()
async update(@CurrentUser() user: AuthUser, @Body() dto: UpdatePartnerMeDto) {
if (dto.name !== undefined && !dto.name.trim()) {
throw new BadRequestException('姓名不能为空');
}
if (dto.name !== undefined) {
await this.prisma.partnerAccount.update({
where: { id: user.actorId },
data: { name: dto.name.trim() },
});
}
return this.buildPartnerMe(user.actorId);
}
private async buildPartnerMe(actorId: bigint) {
const account = await this.prisma.partnerAccount.findUniqueOrThrow({
where: { id: user.actorId },
where: { id: actorId },
});
let primary = account;
if (account.isPrimary !== 1 && account.parentAccountId) {