webadmin端
批量删除用户 模板上传图片数量限制改成30 小飞侠接口配置
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
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 { AdminUsersQueryDto } from './dto/admin-query.dto';
|
||||
|
||||
const FINISHED_ORDER_STATUSES = ['COMPLETED', 'CANCELLED', 'REFUNDED'] as const;
|
||||
|
||||
function mapAdminUserRow(u: {
|
||||
id: bigint;
|
||||
userNo: string;
|
||||
@@ -115,46 +117,108 @@ export class AdminUsersService {
|
||||
});
|
||||
}
|
||||
|
||||
async previewBatchDelete(ids: bigint[]) {
|
||||
const uniqueIds = [...new Set(ids)];
|
||||
if (!uniqueIds.length) {
|
||||
return { items: [], hasRisk: false, total: 0 };
|
||||
}
|
||||
|
||||
const users = await this.prisma.user.findMany({
|
||||
where: { id: { in: uniqueIds } },
|
||||
select: {
|
||||
id: true,
|
||||
userNo: true,
|
||||
nickname: true,
|
||||
phone: true,
|
||||
orders: {
|
||||
where: { status: { notIn: [...FINISHED_ORDER_STATUSES] } },
|
||||
select: { id: true, orderNo: true, status: true, payAmount: true, createdAt: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
},
|
||||
redeemRecords: {
|
||||
select: {
|
||||
id: true,
|
||||
redeemNo: true,
|
||||
amount: true,
|
||||
createdAt: true,
|
||||
payout: { select: { status: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
const foundIds = new Set(users.map((u) => u.id.toString()));
|
||||
const missing = uniqueIds.filter((id) => !foundIds.has(id.toString()));
|
||||
if (missing.length) {
|
||||
throw new NotFoundException('部分用户不存在');
|
||||
}
|
||||
|
||||
const items = users.map((u) => {
|
||||
const unfinishedOrders = u.orders;
|
||||
const redeemRecords = u.redeemRecords.map((r) => ({
|
||||
id: r.id,
|
||||
redeemNo: r.redeemNo,
|
||||
amount: r.amount,
|
||||
createdAt: r.createdAt,
|
||||
payoutStatus: r.payout?.status ?? null,
|
||||
}));
|
||||
const hasRisk = unfinishedOrders.length > 0 || redeemRecords.length > 0;
|
||||
return {
|
||||
id: u.id,
|
||||
userNo: u.userNo,
|
||||
nickname: u.nickname,
|
||||
phone: u.phone,
|
||||
unfinishedOrders,
|
||||
redeemRecords,
|
||||
hasRisk,
|
||||
};
|
||||
});
|
||||
|
||||
return serializeBigInt({
|
||||
items,
|
||||
hasRisk: items.some((i) => i.hasRisk),
|
||||
total: items.length,
|
||||
});
|
||||
}
|
||||
|
||||
async batchDeleteUsers(ids: bigint[], confirmRisk: boolean) {
|
||||
const preview = await this.previewBatchDelete(ids);
|
||||
if (preview.hasRisk && !confirmRisk) {
|
||||
const riskyUsers = preview.items.filter((i) => i.hasRisk);
|
||||
throw new BadRequestException({
|
||||
message: '所选用户存在未完成订单或核销记录,需二次确认后删除',
|
||||
code: 'USER_DELETE_RISK',
|
||||
riskyUsers: riskyUsers.map((u) => ({
|
||||
id: u.id,
|
||||
userNo: u.userNo,
|
||||
unfinishedOrderCount: u.unfinishedOrders.length,
|
||||
redeemRecordCount: u.redeemRecords.length,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
const userIds = preview.items.map((i) => BigInt(String(i.id)));
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
for (const id of userIds) {
|
||||
await this.deleteUserInTx(tx, id);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
deleted: userIds.length,
|
||||
message: '用户及关联业务数据已删除,行为日志已保留',
|
||||
};
|
||||
}
|
||||
|
||||
async deleteUser(id: bigint) {
|
||||
const user = await this.prisma.user.findUnique({ where: { id } });
|
||||
if (!user) throw new NotFoundException('用户不存在');
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
const orderIds = (
|
||||
await tx.order.findMany({ where: { userId: id }, select: { id: true } })
|
||||
).map((o) => o.id);
|
||||
|
||||
const redeemIds = (
|
||||
await tx.redeemRecord.findMany({ where: { userId: id }, select: { id: true } })
|
||||
).map((r) => r.id);
|
||||
|
||||
if (redeemIds.length) {
|
||||
await tx.storePayout.deleteMany({ where: { redeemRecordId: { in: redeemIds } } });
|
||||
await tx.storeRating.deleteMany({ where: { redeemRecordId: { in: redeemIds } } });
|
||||
await tx.redeemRecord.deleteMany({ where: { id: { in: redeemIds } } });
|
||||
}
|
||||
|
||||
await tx.benefitCoupon.deleteMany({ where: { userId: id } });
|
||||
|
||||
if (orderIds.length) {
|
||||
await tx.order.updateMany({
|
||||
where: { originOrderId: { in: orderIds } },
|
||||
data: { originOrderId: null },
|
||||
});
|
||||
await tx.commonTicket.deleteMany({
|
||||
where: { refType: 'ORDER', refId: { in: orderIds } },
|
||||
});
|
||||
await tx.order.deleteMany({ where: { id: { in: orderIds } } });
|
||||
}
|
||||
|
||||
if (user.avatarResourceId) {
|
||||
await tx.commonResource.updateMany({
|
||||
where: { id: user.avatarResourceId, ownerType: 'USER', ownerId: id },
|
||||
data: { status: 'DELETED' },
|
||||
});
|
||||
}
|
||||
|
||||
await tx.user.delete({ where: { id } });
|
||||
await this.deleteUserInTx(tx, id);
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -162,4 +226,50 @@ export class AdminUsersService {
|
||||
message: '用户及关联业务数据已删除,行为日志已保留',
|
||||
};
|
||||
}
|
||||
|
||||
private async deleteUserInTx(tx: Prisma.TransactionClient, id: bigint) {
|
||||
const user = await tx.user.findUnique({ where: { id } });
|
||||
if (!user) return;
|
||||
|
||||
await tx.user.updateMany({
|
||||
where: { mergedIntoUserId: id },
|
||||
data: { mergedIntoUserId: null },
|
||||
});
|
||||
|
||||
const orderIds = (
|
||||
await tx.order.findMany({ where: { userId: id }, select: { id: true } })
|
||||
).map((o) => o.id);
|
||||
|
||||
const redeemIds = (
|
||||
await tx.redeemRecord.findMany({ where: { userId: id }, select: { id: true } })
|
||||
).map((r) => r.id);
|
||||
|
||||
if (redeemIds.length) {
|
||||
await tx.storePayout.deleteMany({ where: { redeemRecordId: { in: redeemIds } } });
|
||||
await tx.storeRating.deleteMany({ where: { redeemRecordId: { in: redeemIds } } });
|
||||
await tx.redeemRecord.deleteMany({ where: { id: { in: redeemIds } } });
|
||||
}
|
||||
|
||||
await tx.benefitCoupon.deleteMany({ where: { userId: id } });
|
||||
|
||||
if (orderIds.length) {
|
||||
await tx.order.updateMany({
|
||||
where: { originOrderId: { in: orderIds } },
|
||||
data: { originOrderId: null },
|
||||
});
|
||||
await tx.commonTicket.deleteMany({
|
||||
where: { refType: 'ORDER', refId: { in: orderIds } },
|
||||
});
|
||||
await tx.order.deleteMany({ where: { id: { in: orderIds } } });
|
||||
}
|
||||
|
||||
if (user.avatarResourceId) {
|
||||
await tx.commonResource.updateMany({
|
||||
where: { id: user.avatarResourceId, ownerType: 'USER', ownerId: id },
|
||||
data: { status: 'DELETED' },
|
||||
});
|
||||
}
|
||||
|
||||
await tx.user.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user