feat(ops): partner proxy order list and HQ order delete
CI / verify (pull_request) Has been cancelled

Add partner-facing proxy order list/detail; unlock SUPER_ADMIN order delete with cascade cleanup and type filter.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-31 10:10:39 +08:00
parent 51f2b5fbfa
commit 8d6c0f9ac0
16 changed files with 523 additions and 8 deletions
@@ -7,6 +7,7 @@ import {
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import {
HQ_DANGEROUS_PERMISSION_KEYS,
HQ_ROLE_DEFAULT_PERMISSIONS,
expandHqPermissionKeys,
hasAnySystemSettingsPermission,
@@ -43,8 +44,12 @@ export class HqPermissionsResolver {
const userKeys = userRows.map((r) => r.permissionKey);
if (account.adminRole === 'SUPER_ADMIN') {
// 超管默认拥有业务/管理权限,但不含危险操作;危险权限需按用户显式勾选
return expandHqPermissionKeys([...hqBasePermissionKeys(), ...userKeys]);
// 超管含危险操作(删用户/订单/城市);其他角色仍需在权限分配中显式勾选
return expandHqPermissionKeys([
...hqBasePermissionKeys(),
...HQ_DANGEROUS_PERMISSION_KEYS,
...userKeys,
]);
}
const roleRows = await this.prisma.hqRolePermission.findMany({
@@ -22,6 +22,7 @@ export const HqOperationAction = {
ORDER_SHIP: 'ORDER_SHIP',
ORDER_STATUS_DEBUG: 'ORDER_STATUS_DEBUG',
ORDER_BATCH_DELETE: 'ORDER_BATCH_DELETE',
ORDER_DELETE: 'ORDER_DELETE',
ORDER_PROXY_CREATE: 'ORDER_PROXY_CREATE',
STORE_CREATE: 'STORE_CREATE',
STORE_UPDATE: 'STORE_UPDATE',
@@ -123,6 +124,7 @@ export const HQ_OPERATION_ACTION_LABELS: Record<string, string> = {
[HqOperationAction.ORDER_SHIP]: '订单发货',
[HqOperationAction.ORDER_STATUS_DEBUG]: '订单状态调试',
[HqOperationAction.ORDER_BATCH_DELETE]: '批量删除订单',
[HqOperationAction.ORDER_DELETE]: '删除订单',
[HqOperationAction.ORDER_PROXY_CREATE]: '总部代下单',
[HqOperationAction.STORE_CREATE]: '新增门店',
[HqOperationAction.STORE_UPDATE]: '编辑门店',
@@ -1,5 +1,6 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import {
HQ_DANGEROUS_PERMISSION_KEYS,
HQ_PERMISSION_CATALOG,
HQ_ROLE_DEFAULT_PERMISSIONS,
LEGACY_SYSTEM_SETTINGS_KEY,
@@ -82,7 +83,10 @@ export class AdminHqPermissionsService {
const userPermissionKeys = expandHqPermissionKeys(userPerms.map((p) => p.permissionKey));
if (account.adminRole === 'SUPER_ADMIN') {
const rolePermissionKeys = hqBasePermissionKeys();
const rolePermissionKeys = [
...hqBasePermissionKeys(),
...HQ_DANGEROUS_PERMISSION_KEYS,
] as HqPermissionKey[];
const effectivePermissionKeys = [
...new Set([...rolePermissionKeys, ...userPermissionKeys]),
] as HqPermissionKey[];
@@ -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 {
HqPermissionGuard,
@@ -43,6 +43,18 @@ export class AdminOrdersController {
return this.ordersService.detail(BigInt(id));
}
@Delete(':id')
@UseGuards(HqPermissionGuard)
@RequireHqPermissions('orders_delete')
@HqOperation({
action: HqOperationAction.ORDER_DELETE,
refType: 'ORDER',
refIdParam: 'id',
})
remove(@Param('id') id: string) {
return this.ordersService.deleteOrder(BigInt(id));
}
/** HQ 发货:调用小飞侠创建运单并更新配送信息 */
@Post(':id/ship')
@HqOperation({
@@ -31,6 +31,7 @@ export class AdminOrdersService {
if (query.orderNo) where.orderNo = { contains: query.orderNo };
if (query.status) where.status = query.status as Prisma.EnumOrderStatusFilter['equals'];
if (query.orderType) where.orderType = query.orderType as Prisma.EnumOrderTypeFilter['equals'];
if (query.userId) where.userId = BigInt(query.userId);
if (query.cityId) where.cityId = BigInt(query.cityId);
if (query.receiverPhone) where.receiverPhone = { contains: query.receiverPhone };
@@ -335,6 +336,25 @@ export class AdminOrdersService {
};
}
async deleteOrder(id: bigint) {
const order = await this.prisma.order.findUnique({
where: { id },
select: { id: true, orderNo: true },
});
if (!order) throw new NotFoundException('订单不存在');
await this.prisma.$transaction(async (tx) => {
await this.deleteOrdersInTx(tx, [id]);
});
return {
ok: true,
deleted: 1,
orderNo: order.orderNo,
message: `订单 ${order.orderNo} 及关联业务数据已删除`,
};
}
private async deleteOrdersInTx(tx: Prisma.TransactionClient, orderIds: bigint[]) {
if (!orderIds.length) return;
@@ -368,6 +388,10 @@ export class AdminOrdersService {
await tx.benefitCoupon.deleteMany({ where: { id: { in: couponIds } } });
}
await tx.userInvoice.deleteMany({ where: { orderId: { in: orderIds } } });
await tx.wineryBillItem.deleteMany({ where: { orderId: { in: orderIds } } });
await tx.logisticsBillItem.deleteMany({ where: { orderId: { in: orderIds } } });
await tx.order.updateMany({
where: { originOrderId: { in: orderIds } },
data: { originOrderId: null },
@@ -48,6 +48,10 @@ export class AdminOrdersQueryDto extends PaginationQueryDto {
@IsString()
status?: string;
@IsOptional()
@IsIn(['NORMAL', 'PROXY'])
orderType?: string;
@IsOptional()
@IsString()
userId?: string;
@@ -208,6 +208,26 @@ export class PartnerProxyOrderController {
return this.tradeService.getPartnerProxyOrderOptions(user.actorId);
}
@Get()
list(
@CurrentUser() user: AuthUser,
@Query('page') page = '1',
@Query('pageSize') pageSize = '20',
@Query('keyword') keyword?: string,
) {
return this.tradeService.listPartnerProxyOrders(
user.actorId,
Number(page),
Number(pageSize),
keyword,
);
}
@Get(':id')
detail(@CurrentUser() user: AuthUser, @Param('id') id: string) {
return this.tradeService.getPartnerProxyOrder(user.actorId, BigInt(id));
}
@Post('preview')
preview(@CurrentUser() user: AuthUser, @Body() dto: PartnerProxyOrderPreviewDto) {
return this.tradeService.previewPartnerProxyOrderForPartner(user.actorId, dto);
@@ -1359,7 +1359,76 @@ export class TradeService {
},
});
return this.getPartnerOrder(partnerAccountId, order.id);
return serializeBigInt(mapOrderCompat(order));
}
/** 合伙人代下单列表:按 proxyPartnerAccountId 归属,与管仓订单无关 */
async listPartnerProxyOrders(
partnerAccountId: bigint,
page = 1,
pageSize = 20,
keyword?: string,
) {
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
const kw = keyword?.trim();
const where = {
orderType: 'PROXY' as const,
proxyPartnerAccountId: primary.id,
...(kw
? {
OR: [
{ orderNo: { contains: kw } },
{ receiverPhone: { contains: kw } },
{ receiverName: { contains: kw } },
{ productName: { contains: kw } },
],
}
: {}),
};
const take = Math.min(Math.max(pageSize, 1), 50);
const skip = (Math.max(page, 1) - 1) * take;
const [list, total] = await Promise.all([
this.prisma.order.findMany({
where,
include: {
delivery: true,
imageResource: true,
user: { select: { phone: true, nickname: true } },
},
orderBy: { createdAt: 'desc' },
skip,
take,
}),
this.prisma.order.count({ where }),
]);
return {
list: serializeBigInt(list.map(mapOrderCompat)),
total,
page: Math.max(page, 1),
pageSize: take,
};
}
async getPartnerProxyOrder(partnerAccountId: bigint, orderId: bigint) {
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
const order = await this.prisma.order.findFirst({
where: {
id: orderId,
orderType: 'PROXY',
proxyPartnerAccountId: primary.id,
},
include: {
delivery: true,
user: true,
imageResource: true,
},
});
if (!order) throw new NotFoundException('代下单不存在');
const statusLogs = await this.prisma.commonEvent.findMany({
where: orderStatusLogWhere(orderId),
orderBy: { createdAt: 'desc' },
});
return serializeBigInt(mapOrderCompat({ ...order, statusLogs: mapStatusLogCompat(statusLogs) }));
}
/** HQ 代下单:商品/推广码选项(运营侧可看白名单测试酒) */