feat(ops): add global test whitelist and exclude test accounts from settlement
Unify product/store visibility on HQ whitelist, mark isTest snapshots, and fix SUPER_ADMIN access for the new module.
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
HQ_DANGEROUS_PERMISSION_KEYS,
|
||||
HQ_PERMISSION_CATALOG,
|
||||
HQ_ROLE_DEFAULT_PERMISSIONS,
|
||||
LEGACY_SYSTEM_SETTINGS_KEY,
|
||||
expandHqPermissionKeys,
|
||||
hqBasePermissionKeys,
|
||||
type HqPermissionKey,
|
||||
} from '@dukang/shared-types';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
@@ -83,10 +81,9 @@ export class AdminHqPermissionsService {
|
||||
const userPermissionKeys = expandHqPermissionKeys(userPerms.map((p) => p.permissionKey));
|
||||
|
||||
if (account.adminRole === 'SUPER_ADMIN') {
|
||||
const rolePermissionKeys = [
|
||||
...hqBasePermissionKeys(),
|
||||
...HQ_DANGEROUS_PERMISSION_KEYS,
|
||||
] as HqPermissionKey[];
|
||||
const rolePermissionKeys = HQ_PERMISSION_CATALOG.map(
|
||||
(p) => p.key,
|
||||
) as HqPermissionKey[];
|
||||
const effectivePermissionKeys = [
|
||||
...new Set([...rolePermissionKeys, ...userPermissionKeys]),
|
||||
] as HqPermissionKey[];
|
||||
|
||||
@@ -43,6 +43,7 @@ export class AdminOrdersService {
|
||||
if (query.createdFrom) where.createdAt.gte = new Date(query.createdFrom);
|
||||
if (query.createdTo) where.createdAt.lte = new Date(query.createdTo);
|
||||
}
|
||||
if (query.excludeTest) where.isTest = false;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.order.findMany({
|
||||
|
||||
@@ -42,6 +42,7 @@ export class AdminPartnersService {
|
||||
if (query.phone) where.phone = { contains: query.phone };
|
||||
if (query.cityId) where.cityId = BigInt(query.cityId);
|
||||
if (query.partnerId) where.id = BigInt(query.partnerId);
|
||||
if (query.excludeTest) where.isTest = false;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.partnerAccount.findMany({
|
||||
|
||||
@@ -5,6 +5,7 @@ import { serializeBigInt } from '../../common/decorators/current-user.decorator'
|
||||
import { groupResourcesByProductId, mapProductMedia } from '../catalog/catalog.mapper';
|
||||
import type { AdminProductsQueryDto } from './dto/admin-query.dto';
|
||||
import type { CreateProductDto, UpdateProductDto } from './dto/admin-mutate.dto';
|
||||
import { TestWhitelistService } from '../../common/test-whitelist/test-whitelist.service';
|
||||
|
||||
function normalizePhones(phones?: string[]): string[] {
|
||||
if (!phones?.length) return [];
|
||||
@@ -57,7 +58,10 @@ function resolveFulfillmentFlags(input: {
|
||||
|
||||
@Injectable()
|
||||
export class AdminProductsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly testWhitelist: TestWhitelistService,
|
||||
) {}
|
||||
|
||||
async list(query: AdminProductsQueryDto) {
|
||||
const page = query.page ?? 1;
|
||||
@@ -140,6 +144,11 @@ export class AdminProductsService {
|
||||
|
||||
const phones = normalizePhones(dto.visibilityPhones);
|
||||
const whitelistEnabled = !!dto.visibilityWhitelistEnabled;
|
||||
if (whitelistEnabled) {
|
||||
await this.testWhitelist.assertGlobalWhitelistNotEmpty();
|
||||
}
|
||||
// 手机号统一在「白名单管理」维护;此处忽略分实体 phones(兼容旧客户端传参)
|
||||
void phones;
|
||||
|
||||
const product = await this.createWithGeneratedSku({
|
||||
barcode69: dto.barcode69,
|
||||
@@ -158,13 +167,6 @@ export class AdminProductsService {
|
||||
...(dto.detailContent !== undefined
|
||||
? { detailContent: dto.detailContent as Prisma.InputJsonValue }
|
||||
: {}),
|
||||
...(phones.length
|
||||
? {
|
||||
visibilityPhones: {
|
||||
create: phones.map((phone) => ({ phone })),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
if (dto.coverUrl) {
|
||||
@@ -225,9 +227,10 @@ export class AdminProductsService {
|
||||
},
|
||||
});
|
||||
|
||||
if (dto.visibilityPhones !== undefined) {
|
||||
await this.syncVisibilityPhones(id, normalizePhones(dto.visibilityPhones));
|
||||
if (dto.visibilityWhitelistEnabled) {
|
||||
await this.testWhitelist.assertGlobalWhitelistNotEmpty();
|
||||
}
|
||||
// 分实体手机号已废弃;忽略 dto.visibilityPhones
|
||||
|
||||
if (dto.coverUrl) {
|
||||
await this.syncCover(id, dto.coverUrl);
|
||||
|
||||
@@ -42,6 +42,7 @@ export class AdminRedeemService {
|
||||
if (query.channel === 'SCAN' || query.channel === 'PHONE') {
|
||||
where.channel = query.channel;
|
||||
}
|
||||
if (query.excludeTest) where.isTest = false;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.redeemRecord.findMany({
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
UpdateStoreMediaDto,
|
||||
UpdateStoreStatusDto,
|
||||
} from './dto/admin-mutate.dto';
|
||||
import { TestWhitelistService } from '../../common/test-whitelist/test-whitelist.service';
|
||||
|
||||
/** 选填文案:空 / null / "null" 一律存库为 null,避免 String(null)==="null" */
|
||||
function normalizeStoreOptionalText(value: unknown): string | null {
|
||||
@@ -51,6 +52,7 @@ export class AdminStoresService {
|
||||
private readonly partnerCityService: PartnerCityService,
|
||||
private readonly storeCategoryService: StoreCategoryService,
|
||||
private readonly analyticsService: AnalyticsService,
|
||||
private readonly testWhitelist: TestWhitelistService,
|
||||
) {}
|
||||
|
||||
async listStores(query: AdminStoresQueryDto) {
|
||||
@@ -65,6 +67,7 @@ export class AdminStoresService {
|
||||
if (query.cityId) where.cityId = BigInt(query.cityId);
|
||||
if (query.partnerId) where.partnerAccountId = BigInt(query.partnerId);
|
||||
if (query.phone) where.phone = { contains: query.phone };
|
||||
if (query.excludeTest) where.isTest = false;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.store.findMany({
|
||||
@@ -283,18 +286,7 @@ export class AdminStoresService {
|
||||
? !!dto.visibilityWhitelistEnabled
|
||||
: current.visibilityWhitelistEnabled;
|
||||
if (nextEnabled) {
|
||||
const phones =
|
||||
dto.visibilityPhones !== undefined
|
||||
? normalizeVisibilityPhones(dto.visibilityPhones)
|
||||
: (
|
||||
await this.prisma.storeVisibilityPhone.findMany({
|
||||
where: { storeId: id },
|
||||
select: { phone: true },
|
||||
})
|
||||
).map((p) => p.phone);
|
||||
if (!phones.length) {
|
||||
throw new BadRequestException('开启白名单时请至少添加一个手机号');
|
||||
}
|
||||
await this.testWhitelist.assertGlobalWhitelistNotEmpty();
|
||||
}
|
||||
}
|
||||
const bankTouched =
|
||||
@@ -372,18 +364,13 @@ export class AdminStoresService {
|
||||
...(dto.visibilityWhitelistEnabled !== undefined
|
||||
? { visibilityWhitelistEnabled: !!dto.visibilityWhitelistEnabled }
|
||||
: {}),
|
||||
...(dto.isTest !== undefined ? { isTest: !!dto.isTest } : {}),
|
||||
...(latitude != null && longitude != null ? { latitude, longitude } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (dto.visibilityPhones !== undefined) {
|
||||
const phones = normalizeVisibilityPhones(dto.visibilityPhones);
|
||||
await tx.storeVisibilityPhone.deleteMany({ where: { storeId: id } });
|
||||
if (phones.length) {
|
||||
await tx.storeVisibilityPhone.createMany({
|
||||
data: phones.map((phone) => ({ storeId: id, phone })),
|
||||
});
|
||||
}
|
||||
// 分实体手机号已废弃,忽略写入
|
||||
}
|
||||
|
||||
if (dto.coverUrl) {
|
||||
@@ -505,11 +492,14 @@ export class AdminStoresService {
|
||||
throw new BadRequestException('好客权益券使用规则最多 1000 字');
|
||||
}
|
||||
|
||||
const visibilityPhones = normalizeVisibilityPhones(dto.visibilityPhones);
|
||||
const whitelistEnabled = !!dto.visibilityWhitelistEnabled;
|
||||
if (whitelistEnabled && !visibilityPhones.length) {
|
||||
throw new BadRequestException('开启白名单时请至少添加一个手机号');
|
||||
if (whitelistEnabled) {
|
||||
await this.testWhitelist.assertGlobalWhitelistNotEmpty();
|
||||
}
|
||||
const isTest =
|
||||
dto.isTest !== undefined
|
||||
? !!dto.isTest
|
||||
: await this.testWhitelist.isPhoneInWhitelist(normalizedPhone);
|
||||
|
||||
const store = await this.prisma.store.create({
|
||||
data: {
|
||||
@@ -531,18 +521,12 @@ export class AdminStoresService {
|
||||
openTime2: openTime2 || null,
|
||||
closeTime2: closeTime2 || null,
|
||||
visibilityWhitelistEnabled: whitelistEnabled,
|
||||
isTest,
|
||||
...(latitude != null && longitude != null ? { latitude, longitude } : {}),
|
||||
status: 'OPEN',
|
||||
auditStatus: 'APPROVED',
|
||||
auditedAt: new Date(),
|
||||
rejectReason: null,
|
||||
...(visibilityPhones.length
|
||||
? {
|
||||
visibilityPhones: {
|
||||
create: visibilityPhones.map((phone) => ({ phone })),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -746,6 +730,7 @@ export class AdminStoresService {
|
||||
where.bindings = { some: { storeId: BigInt(query.storeId) } };
|
||||
}
|
||||
if (query.status) where.status = query.status as Prisma.EnumAccountStatusFilter['equals'];
|
||||
if (query.excludeTest) where.isTest = false;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.storeAccount.findMany({
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { IsBoolean, IsIn, IsInt, IsOptional, IsString, Max, MaxLength, Min } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||
import {
|
||||
HqPermissionGuard,
|
||||
RequireHqPermissions,
|
||||
} from '../../common/guards/hq-permission.guard';
|
||||
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
import type { AuthUser } from '../../common/guards/jwt-auth.guard';
|
||||
import { TestWhitelistService } from '../../common/test-whitelist/test-whitelist.service';
|
||||
import { SystemConfigService } from '../../common/system-config/system-config.service';
|
||||
|
||||
class ListPhonesQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phone?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number = 1;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
pageSize?: number = 20;
|
||||
}
|
||||
|
||||
class AddPhoneDto {
|
||||
@IsString()
|
||||
phone!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(256)
|
||||
note?: string;
|
||||
}
|
||||
|
||||
class UpdatePhoneDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(256)
|
||||
note?: string | null;
|
||||
}
|
||||
|
||||
class ListAccountsQueryDto {
|
||||
@IsIn(['user', 'store_account', 'partner', 'store', 'order'])
|
||||
type!: 'user' | 'store_account' | 'partner' | 'store' | 'order';
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phone?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number = 1;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
pageSize?: number = 20;
|
||||
}
|
||||
|
||||
class UpdateMockFlagsDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
mockSms?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
mockWechat?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
mockPay?: boolean;
|
||||
}
|
||||
|
||||
@Controller('admin/test-whitelist')
|
||||
@UseGuards(HqAuthGuard, HqPermissionGuard)
|
||||
@RequireHqPermissions('test_whitelist')
|
||||
export class AdminTestWhitelistController {
|
||||
constructor(
|
||||
private readonly testWhitelist: TestWhitelistService,
|
||||
private readonly systemConfig: SystemConfigService,
|
||||
) {}
|
||||
|
||||
@Get('mock-flags')
|
||||
async getMockFlags() {
|
||||
const form = await this.systemConfig.getForm(['feature']);
|
||||
const v = form.values;
|
||||
return {
|
||||
mockSms: v.MOCK_SMS === 'true' || v.MOCK_SMS === '1',
|
||||
mockWechat: v.MOCK_WECHAT === 'true' || v.MOCK_WECHAT === '1',
|
||||
mockPay: v.MOCK_PAY === 'true' || v.MOCK_PAY === '1',
|
||||
};
|
||||
}
|
||||
|
||||
@Put('mock-flags')
|
||||
async updateMockFlags(@Body() dto: UpdateMockFlagsDto) {
|
||||
const values: Record<string, string> = {};
|
||||
if (dto.mockSms !== undefined) values.MOCK_SMS = String(dto.mockSms);
|
||||
if (dto.mockWechat !== undefined) values.MOCK_WECHAT = String(dto.mockWechat);
|
||||
if (dto.mockPay !== undefined) values.MOCK_PAY = String(dto.mockPay);
|
||||
if (Object.keys(values).length) {
|
||||
await this.systemConfig.update({ values }, ['feature']);
|
||||
}
|
||||
return this.getMockFlags();
|
||||
}
|
||||
|
||||
@Get('phones')
|
||||
listPhones(@Query() query: ListPhonesQueryDto) {
|
||||
return this.testWhitelist.listPhones(query);
|
||||
}
|
||||
|
||||
@Post('phones')
|
||||
addPhone(@Body() dto: AddPhoneDto, @CurrentUser() actor: AuthUser) {
|
||||
return this.testWhitelist.addPhone({
|
||||
phone: dto.phone,
|
||||
note: dto.note,
|
||||
createdByHqId: actor.actorId,
|
||||
});
|
||||
}
|
||||
|
||||
@Patch('phones/:id')
|
||||
updatePhone(@Param('id') id: string, @Body() dto: UpdatePhoneDto) {
|
||||
return this.testWhitelist.updatePhone(BigInt(id), { note: dto.note });
|
||||
}
|
||||
|
||||
@Delete('phones/:id')
|
||||
removePhone(@Param('id') id: string) {
|
||||
return this.testWhitelist.removePhone(BigInt(id));
|
||||
}
|
||||
|
||||
@Get('accounts')
|
||||
listAccounts(@Query() query: ListAccountsQueryDto) {
|
||||
return this.testWhitelist.listAccounts(query);
|
||||
}
|
||||
|
||||
@Get('phones/:id/linked')
|
||||
linked(@Param('id') id: string) {
|
||||
return this.testWhitelist.linkedForPhoneId(BigInt(id));
|
||||
}
|
||||
|
||||
@Post('migrate-visibility')
|
||||
migrate(@CurrentUser() actor: AuthUser) {
|
||||
return this.testWhitelist.migrateVisibilityPhones(actor.actorId);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ function mapAdminUserRow(u: {
|
||||
sourceType: string;
|
||||
sourceRefId: bigint | null;
|
||||
sourceLabel: string | null;
|
||||
isTest?: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
_count: { orders: number };
|
||||
@@ -37,6 +38,7 @@ function mapAdminUserRow(u: {
|
||||
sourceType: u.sourceType,
|
||||
sourceRefId: u.sourceRefId,
|
||||
sourceLabel: u.sourceLabel,
|
||||
isTest: !!u.isTest,
|
||||
createdAt: u.createdAt,
|
||||
updatedAt: u.updatedAt,
|
||||
orderCount: u._count.orders,
|
||||
@@ -58,6 +60,7 @@ export class AdminUsersService {
|
||||
if (query.status !== undefined) where.status = query.status;
|
||||
if (query.phoneVerified === '1') where.phoneVerifiedAt = { not: null };
|
||||
if (query.phoneVerified === '0') where.phoneVerifiedAt = null;
|
||||
if (query.excludeTest) where.isTest = false;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.user.findMany({
|
||||
@@ -78,6 +81,7 @@ export class AdminUsersService {
|
||||
sourceType: true,
|
||||
sourceRefId: true,
|
||||
sourceLabel: true,
|
||||
isTest: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
_count: { select: { orders: true } },
|
||||
|
||||
@@ -128,16 +128,21 @@ export class CreateStoreDto {
|
||||
@Min(0)
|
||||
avgPrice?: number;
|
||||
|
||||
/** 开启后仅白名单手机号在 C 端可见 */
|
||||
/** 开启后仅全局测试白名单手机号在 C 端可见 */
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
visibilityWhitelistEnabled?: boolean;
|
||||
|
||||
/** 可见白名单手机号列表 */
|
||||
/** @deprecated 已并入全局白名单,忽略 */
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
visibilityPhones?: string[];
|
||||
|
||||
/** 测试门店标记 */
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isTest?: boolean;
|
||||
}
|
||||
|
||||
export class UpdateStoreDto {
|
||||
@@ -227,16 +232,21 @@ export class UpdateStoreDto {
|
||||
@IsString()
|
||||
bankBranch?: string | null;
|
||||
|
||||
/** 开启后仅白名单手机号在 C 端可见 */
|
||||
/** 开启后仅全局测试白名单手机号在 C 端可见 */
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
visibilityWhitelistEnabled?: boolean;
|
||||
|
||||
/** 可见白名单手机号列表 */
|
||||
/** @deprecated 已并入全局白名单,忽略 */
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
visibilityPhones?: string[];
|
||||
|
||||
/** 测试门店标记 */
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isTest?: boolean;
|
||||
}
|
||||
|
||||
export class CreateStoreAccountDto {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsIn, IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
|
||||
import { Type, Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsIn, IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
|
||||
|
||||
function toOptionalBoolean(value: unknown): boolean | undefined {
|
||||
if (value === undefined || value === null || value === '') return undefined;
|
||||
if (value === true || value === 'true' || value === '1' || value === 1) return true;
|
||||
if (value === false || value === 'false' || value === '0' || value === 0) return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export class PaginationQueryDto {
|
||||
@IsOptional()
|
||||
@@ -37,6 +44,12 @@ export class AdminUsersQueryDto extends PaginationQueryDto {
|
||||
@Type(() => Number)
|
||||
@IsIn([0, 1])
|
||||
status?: number;
|
||||
|
||||
/** 勾选「过滤测试账号」时传 true */
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => toOptionalBoolean(value))
|
||||
@IsBoolean()
|
||||
excludeTest?: boolean;
|
||||
}
|
||||
|
||||
export class AdminOrdersQueryDto extends PaginationQueryDto {
|
||||
@@ -75,6 +88,11 @@ export class AdminOrdersQueryDto extends PaginationQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
createdTo?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => toOptionalBoolean(value))
|
||||
@IsBoolean()
|
||||
excludeTest?: boolean;
|
||||
}
|
||||
|
||||
/** 概览页用户/订单 ECharts 聚合筛选 */
|
||||
@@ -128,6 +146,11 @@ export class AdminStoresQueryDto extends PaginationQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
auditStatus?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => toOptionalBoolean(value))
|
||||
@IsBoolean()
|
||||
excludeTest?: boolean;
|
||||
}
|
||||
|
||||
export class AdminStoreAccountsQueryDto extends PaginationQueryDto {
|
||||
@@ -142,6 +165,11 @@ export class AdminStoreAccountsQueryDto extends PaginationQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
status?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => toOptionalBoolean(value))
|
||||
@IsBoolean()
|
||||
excludeTest?: boolean;
|
||||
}
|
||||
|
||||
export class AdminPartnersQueryDto extends PaginationQueryDto {
|
||||
@@ -164,6 +192,11 @@ export class AdminPartnersQueryDto extends PaginationQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
partnerId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => toOptionalBoolean(value))
|
||||
@IsBoolean()
|
||||
excludeTest?: boolean;
|
||||
}
|
||||
|
||||
export class AdminCityWarehousesQueryDto extends PaginationQueryDto {
|
||||
@@ -247,6 +280,11 @@ export class AdminRedeemRecordsQueryDto extends PaginationQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
channel?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => toOptionalBoolean(value))
|
||||
@IsBoolean()
|
||||
excludeTest?: boolean;
|
||||
}
|
||||
|
||||
export class AdminStoreRatingsQueryDto extends PaginationQueryDto {
|
||||
|
||||
@@ -80,6 +80,7 @@ import { AdminDevPlanController } from '../dev-plan/admin-dev-plan.controller';
|
||||
import { AdminFulfillmentProvidersController } from './admin-fulfillment-providers.controller';
|
||||
import { AdminDomainEventsController } from './admin-domain-events.controller';
|
||||
import { AdminDomainEventsService } from './admin-domain-events.service';
|
||||
import { AdminTestWhitelistController } from './admin-test-whitelist.controller';
|
||||
|
||||
@Module({
|
||||
imports: [CityScopeModule, IamModule, TradeModule, AnalyticsModule, FulfillmentModule, BenefitModule, CommonModule, IntegrationsModule, WecomModule, LlmModule, RedeemModule, StoreModule, DevPlanModule],
|
||||
@@ -128,6 +129,7 @@ import { AdminDomainEventsService } from './admin-domain-events.service';
|
||||
AdminKnowledgeBasesController,
|
||||
AdminDevPlanController,
|
||||
AdminFulfillmentProvidersController,
|
||||
AdminTestWhitelistController,
|
||||
],
|
||||
providers: [
|
||||
AdminDashboardService,
|
||||
|
||||
Reference in New Issue
Block a user