webadmin端
批量删除用户 模板上传图片数量限制改成30 小飞侠接口配置
This commit is contained in:
@@ -67,3 +67,10 @@ XIAOFEIXIA_MCH_ID=
|
||||
XIAOFEIXIA_API_KEY=
|
||||
XIAOFEIXIA_SIGN_TYPE=MD5
|
||||
# XIAOFEIXIA_APP_ID=
|
||||
# HQ 订单发货默认寄件信息
|
||||
SHIP_FROM_NAME=杜康仓库
|
||||
SHIP_FROM_MOBILE=13800000000
|
||||
SHIP_FROM_ADDRESS=河南省郑州市金水区
|
||||
SHIP_FROM_ADDRESS_DETAIL=杜康酒业仓
|
||||
SHIP_FROM_LNG=113.665
|
||||
SHIP_FROM_LAT=34.757
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Body, Controller, Get, Param, Put, Query, UseGuards } from '@nestjs/common';
|
||||
import { Body, Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
|
||||
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
|
||||
import { AdminOrdersService } from './admin-orders.service';
|
||||
import { UpdateOrderStatusDto } from './dto/admin-mutate.dto';
|
||||
import { AdminShipOrderDto, BatchDeleteOrdersDto, UpdateOrderStatusDto } from './dto/admin-mutate.dto';
|
||||
import { AdminOrdersQueryDto } from './dto/admin-query.dto';
|
||||
|
||||
@Controller('admin/orders')
|
||||
@@ -14,11 +15,28 @@ export class AdminOrdersController {
|
||||
return this.ordersService.list(query);
|
||||
}
|
||||
|
||||
@Post('batch-delete')
|
||||
@UseGuards(SuperAdminGuard)
|
||||
batchDelete(@Body() dto: BatchDeleteOrdersDto) {
|
||||
return this.ordersService.batchDeleteOrders(dto.ids.map((id) => BigInt(id)));
|
||||
}
|
||||
|
||||
@Get('ship-defaults')
|
||||
shipDefaults() {
|
||||
return this.ordersService.getShipDefaults();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
detail(@Param('id') id: string) {
|
||||
return this.ordersService.detail(BigInt(id));
|
||||
}
|
||||
|
||||
/** HQ 发货:调用小飞侠创建运单并更新配送信息 */
|
||||
@Post(':id/ship')
|
||||
ship(@Param('id') id: string, @Body() dto: AdminShipOrderDto) {
|
||||
return this.ordersService.shipOrder(BigInt(id), dto);
|
||||
}
|
||||
|
||||
/** preV1 调试:直接改订单状态,不走业务校验 */
|
||||
@Put(':id/status')
|
||||
updateStatus(@Param('id') id: string, @Body() dto: UpdateOrderStatusDto) {
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
||||
import { orderStatusLogWhere } from '../../common/event/event.helpers';
|
||||
import { mapOrderCompat, mapStatusLogCompat } from '../../common/compat/v31-compat';
|
||||
import { TradeService } from '../trade/trade.service';
|
||||
import { AdminXiaofeixiaService } from './admin-xiaofeixia.service';
|
||||
import type { AdminOrdersQueryDto } from './dto/admin-query.dto';
|
||||
import type { AdminShipOrderDto } from './dto/admin-mutate.dto';
|
||||
import type { XiaofeixiaCreateShipmentDto } from './dto/admin-courier.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AdminOrdersService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly tradeService: TradeService,
|
||||
private readonly xiaofeixiaService: AdminXiaofeixiaService,
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
async list(query: AdminOrdersQueryDto) {
|
||||
@@ -87,4 +93,156 @@ export class AdminOrdersService {
|
||||
await this.tradeService.applyStatusTransition(id, order.status, status, 'HQ_DEBUG');
|
||||
return this.detail(id);
|
||||
}
|
||||
|
||||
getShipDefaults() {
|
||||
return {
|
||||
provider: 'XFX',
|
||||
providerLabel: '小飞侠',
|
||||
fromName: this.config.get<string>('SHIP_FROM_NAME') || '杜康仓库',
|
||||
fromMobile: this.config.get<string>('SHIP_FROM_MOBILE') || '13800000000',
|
||||
fromAddress: this.config.get<string>('SHIP_FROM_ADDRESS') || '河南省郑州市金水区',
|
||||
fromAddressDetail: this.config.get<string>('SHIP_FROM_ADDRESS_DETAIL') || '杜康酒业仓',
|
||||
fromLng: Number(this.config.get<string>('SHIP_FROM_LNG') || 113.665),
|
||||
fromLat: Number(this.config.get<string>('SHIP_FROM_LAT') || 34.757),
|
||||
weight: 2,
|
||||
payMode: '1',
|
||||
};
|
||||
}
|
||||
|
||||
async shipOrder(id: bigint, dto: AdminShipOrderDto) {
|
||||
if (dto.provider !== 'XFX') {
|
||||
throw new BadRequestException('暂仅支持小飞侠配送');
|
||||
}
|
||||
|
||||
const order = await this.prisma.order.findUnique({
|
||||
where: { id },
|
||||
include: { delivery: true },
|
||||
});
|
||||
if (!order) throw new NotFoundException('订单不存在');
|
||||
|
||||
if (!['PENDING_SHIP', 'OUT_WAREHOUSE'].includes(order.status)) {
|
||||
throw new BadRequestException('当前订单状态不可发货');
|
||||
}
|
||||
if (order.delivery?.trackingNo) {
|
||||
throw new BadRequestException('该订单已有运单号,请勿重复发货');
|
||||
}
|
||||
|
||||
const defaults = this.getShipDefaults();
|
||||
const shipmentDto: XiaofeixiaCreateShipmentDto = {
|
||||
outNumber: order.orderNo,
|
||||
fromName: dto.fromName || defaults.fromName,
|
||||
fromMobile: dto.fromMobile || defaults.fromMobile,
|
||||
fromAddress: dto.fromAddress || defaults.fromAddress,
|
||||
fromAddressDetail: dto.fromAddressDetail || defaults.fromAddressDetail,
|
||||
fromLng: dto.fromLng ?? defaults.fromLng,
|
||||
fromLat: dto.fromLat ?? defaults.fromLat,
|
||||
toName: order.receiverName,
|
||||
toMobile: order.receiverPhone,
|
||||
toAddress: `${order.receiverProvince}${order.receiverCity}${order.receiverDistrict}`,
|
||||
toAddressDetail: order.receiverAddress,
|
||||
goodsName: order.productName,
|
||||
goodsNum: order.quantity,
|
||||
weight: dto.weight ?? defaults.weight,
|
||||
payMode: dto.payMode || defaults.payMode,
|
||||
remark: dto.remark || `HQ发货 ${order.orderNo}`,
|
||||
};
|
||||
|
||||
const result = await this.xiaofeixiaService.createShipment(shipmentDto);
|
||||
if (!result.ok || !result.data) {
|
||||
throw new BadRequestException(result.error || '小飞侠创建运单失败');
|
||||
}
|
||||
|
||||
const { providerShipmentId, trackingNumber } = result.data;
|
||||
const now = new Date();
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
if (order.delivery) {
|
||||
await tx.orderDelivery.update({
|
||||
where: { orderId: id },
|
||||
data: {
|
||||
provider: 'XFX',
|
||||
trackingNo: trackingNumber,
|
||||
providerOrderNo: String(providerShipmentId),
|
||||
shippingAt: now,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await tx.orderDelivery.create({
|
||||
data: {
|
||||
orderId: id,
|
||||
provider: 'XFX',
|
||||
trackingNo: trackingNumber,
|
||||
providerOrderNo: String(providerShipmentId),
|
||||
shippingAt: now,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await this.tradeService.applyStatusTransition(id, order.status, 'SHIPPING', 'HQ_SHIP');
|
||||
return this.detail(id);
|
||||
}
|
||||
|
||||
async batchDeleteOrders(ids: bigint[]) {
|
||||
const uniqueIds = [...new Set(ids)];
|
||||
if (!uniqueIds.length) {
|
||||
return { ok: true, deleted: 0, message: '未选择订单' };
|
||||
}
|
||||
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: { id: { in: uniqueIds } },
|
||||
select: { id: true, orderNo: true },
|
||||
});
|
||||
if (!orders.length) throw new NotFoundException('订单不存在');
|
||||
|
||||
const orderIds = orders.map((o) => o.id);
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
await this.deleteOrdersInTx(tx, orderIds);
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
deleted: orderIds.length,
|
||||
orderNos: orders.map((o) => o.orderNo),
|
||||
message: '订单及关联业务数据已删除,状态流转等业务日志已保留',
|
||||
};
|
||||
}
|
||||
|
||||
private async deleteOrdersInTx(tx: Prisma.TransactionClient, orderIds: bigint[]) {
|
||||
if (!orderIds.length) return;
|
||||
|
||||
const couponIds = (
|
||||
await tx.benefitCoupon.findMany({
|
||||
where: { orderId: { in: orderIds } },
|
||||
select: { id: true },
|
||||
})
|
||||
).map((c) => c.id);
|
||||
|
||||
if (couponIds.length) {
|
||||
const redeemIds = (
|
||||
await tx.redeemRecord.findMany({
|
||||
where: { couponId: { in: couponIds } },
|
||||
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: { id: { in: couponIds } } });
|
||||
}
|
||||
|
||||
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 } } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type TemplateRow = {
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
const MAX_TEMPLATE_DETAIL_IMAGES = 20;
|
||||
const MAX_TEMPLATE_DETAIL_IMAGES = 30;
|
||||
|
||||
@Injectable()
|
||||
export class AdminProductDetailTemplatesService {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
|
||||
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||
import { AdminRedeemDebugService } from './admin-redeem-debug.service';
|
||||
import type {
|
||||
AdminRedeemDebugCreateTokenDto,
|
||||
AdminRedeemDebugStoreTokenDto,
|
||||
} from './dto/admin-mutate.dto';
|
||||
|
||||
@Controller('admin/redeem/debug')
|
||||
@UseGuards(HqAuthGuard)
|
||||
export class AdminRedeemDebugController {
|
||||
constructor(private readonly service: AdminRedeemDebugService) {}
|
||||
|
||||
/** preV1 调试:为用户生成核销码 */
|
||||
@Post('create-token')
|
||||
createToken(@Body() dto: AdminRedeemDebugCreateTokenDto) {
|
||||
return this.service.createToken(dto);
|
||||
}
|
||||
|
||||
/** preV1 调试:门店侧预览核销 */
|
||||
@Post('preview')
|
||||
preview(@Body() dto: AdminRedeemDebugStoreTokenDto) {
|
||||
return this.service.preview(dto);
|
||||
}
|
||||
|
||||
/** preV1 调试:门店侧确认核销 */
|
||||
@Post('confirm')
|
||||
confirm(@Body() dto: AdminRedeemDebugStoreTokenDto) {
|
||||
return this.service.confirm(dto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { RedeemService } from '../redeem/redeem.service';
|
||||
import type {
|
||||
AdminRedeemDebugCreateTokenDto,
|
||||
AdminRedeemDebugStoreTokenDto,
|
||||
} from './dto/admin-mutate.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AdminRedeemDebugService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly redeemService: RedeemService,
|
||||
) {}
|
||||
|
||||
private async resolveStoreAccountId(storeId: string): Promise<bigint> {
|
||||
const account = await this.prisma.storeAccount.findFirst({
|
||||
where: { storeId: BigInt(storeId), status: 'ACTIVE' },
|
||||
orderBy: { id: 'asc' },
|
||||
select: { id: true, store: { select: { name: true } } },
|
||||
});
|
||||
if (!account) {
|
||||
throw new NotFoundException('该门店无可用账户,请先创建门店账户');
|
||||
}
|
||||
return account.id;
|
||||
}
|
||||
|
||||
async createToken(dto: AdminRedeemDebugCreateTokenDto) {
|
||||
return this.redeemService.createToken(BigInt(dto.userId), {
|
||||
amount: dto.amount,
|
||||
couponId: dto.couponId,
|
||||
storeId: dto.storeId,
|
||||
});
|
||||
}
|
||||
|
||||
async preview(dto: AdminRedeemDebugStoreTokenDto) {
|
||||
const storeAccountId = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.previewRedeem(storeAccountId, dto.token);
|
||||
}
|
||||
|
||||
async confirm(dto: AdminRedeemDebugStoreTokenDto) {
|
||||
const storeAccountId = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.confirmRedeem(storeAccountId, { token: dto.token });
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Controller, Delete, Get, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import { Body, Controller, Delete, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
|
||||
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
|
||||
import { AdminUsersService } from './admin-users.service';
|
||||
import { AdminUsersQueryDto } from './dto/admin-query.dto';
|
||||
import { BatchDeleteUsersConfirmDto, BatchDeleteUsersDto } from './dto/admin-mutate.dto';
|
||||
|
||||
@Controller('admin/users')
|
||||
@UseGuards(HqAuthGuard)
|
||||
@@ -14,6 +15,21 @@ export class AdminUsersController {
|
||||
return this.usersService.list(query);
|
||||
}
|
||||
|
||||
@Post('batch-delete/preview')
|
||||
@UseGuards(SuperAdminGuard)
|
||||
previewBatchDelete(@Body() dto: BatchDeleteUsersDto) {
|
||||
return this.usersService.previewBatchDelete(dto.ids.map((id) => BigInt(id)));
|
||||
}
|
||||
|
||||
@Post('batch-delete')
|
||||
@UseGuards(SuperAdminGuard)
|
||||
batchDelete(@Body() dto: BatchDeleteUsersConfirmDto) {
|
||||
return this.usersService.batchDeleteUsers(
|
||||
dto.ids.map((id) => BigInt(id)),
|
||||
dto.confirmRisk,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
detail(@Param('id') id: string) {
|
||||
return this.usersService.detail(BigInt(id));
|
||||
|
||||
@@ -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 } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsArray, IsIn, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsBoolean, IsIn, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString, Min } from 'class-validator';
|
||||
|
||||
export class UpdateStoreStatusDto {
|
||||
@IsString()
|
||||
@@ -307,6 +308,96 @@ export class UpdateOrderStatusDto {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export class BatchDeleteOrdersDto {
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
ids: string[];
|
||||
}
|
||||
|
||||
export class BatchDeleteUsersDto {
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
ids: string[];
|
||||
}
|
||||
|
||||
export class BatchDeleteUsersConfirmDto extends BatchDeleteUsersDto {
|
||||
@IsBoolean()
|
||||
confirmRisk: boolean;
|
||||
}
|
||||
|
||||
/** HQ 订单发货(目前仅小飞侠 XFX) */
|
||||
export class AdminShipOrderDto {
|
||||
@IsIn(['XFX'])
|
||||
provider: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
fromName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
fromMobile?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
fromAddress?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
fromAddressDetail?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
fromLng?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
fromLat?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0.01)
|
||||
weight?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['1', '2'])
|
||||
payMode?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export class AdminRedeemDebugCreateTokenDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
userId: string;
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0.01)
|
||||
amount: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
couponId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
storeId?: string;
|
||||
}
|
||||
|
||||
export class AdminRedeemDebugStoreTokenDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
storeId: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
token: string;
|
||||
}
|
||||
|
||||
export class UpdateDeliveryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
@@ -33,9 +33,12 @@ import { AdminXiaofeixiaController } from './admin-xiaofeixia.controller';
|
||||
import { AdminXiaofeixiaService } from './admin-xiaofeixia.service';
|
||||
import { AdminProductDetailTemplatesController } from './admin-product-detail-templates.controller';
|
||||
import { AdminProductDetailTemplatesService } from './admin-product-detail-templates.service';
|
||||
import { RedeemModule } from '../redeem/redeem.module';
|
||||
import { AdminRedeemDebugController } from './admin-redeem-debug.controller';
|
||||
import { AdminRedeemDebugService } from './admin-redeem-debug.service';
|
||||
|
||||
@Module({
|
||||
imports: [IamModule, TradeModule, BenefitModule, CommonModule, IntegrationsModule],
|
||||
imports: [IamModule, TradeModule, BenefitModule, CommonModule, IntegrationsModule, RedeemModule],
|
||||
controllers: [
|
||||
AdminDashboardController,
|
||||
AdminUsersController,
|
||||
@@ -56,6 +59,7 @@ import { AdminProductDetailTemplatesService } from './admin-product-detail-templ
|
||||
AdminTicketsController,
|
||||
AdminXiaofeixiaController,
|
||||
AdminProductDetailTemplatesController,
|
||||
AdminRedeemDebugController,
|
||||
],
|
||||
providers: [
|
||||
AdminDashboardService,
|
||||
@@ -73,6 +77,7 @@ import { AdminProductDetailTemplatesService } from './admin-product-detail-templ
|
||||
AdminTicketsService,
|
||||
AdminXiaofeixiaService,
|
||||
AdminProductDetailTemplatesService,
|
||||
AdminRedeemDebugService,
|
||||
SuperAdminGuard,
|
||||
],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user