feat(admin): HQ手机号编辑、合伙人子账号日志与子账号H5体验
支持 HQ 账户改手机号(格式校验)、合伙人日志包含子账号且子账号不显示主账号信息;改手机号时清除微信绑定。合伙人 H5 子账号页签与我的页、录入门店底部按钮及上传失败提示。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user