城市合伙人端的修改(后台)

This commit is contained in:
2026-07-12 10:19:39 +08:00
parent d0f0fa09af
commit 7f031cc4c2
74 changed files with 5430 additions and 975 deletions
@@ -1,13 +1,18 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { resolveMaxPartnerCommissionRate, validatePartnerCommissionRates } from '@dukang/domain';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { PartnerCityService } from '../city-scope/partner-city.service';
import type { AdminCitiesQueryDto } from './dto/admin-query.dto';
import type { CreateCityDto, UpdateCityDto } from './dto/admin-mutate.dto';
@Injectable()
export class AdminCitiesService {
constructor(private readonly prisma: PrismaService) {}
constructor(
private readonly prisma: PrismaService,
private readonly partnerCityService: PartnerCityService,
) {}
async list(query: AdminCitiesQueryDto) {
const page = query.page ?? 1;
@@ -16,7 +21,9 @@ export class AdminCitiesService {
if (query.name) where.name = { contains: query.name };
if (query.code) where.code = { contains: query.code };
if (query.status) where.status = query.status as Prisma.EnumCityStatusFilter['equals'];
if (query.partnerId) where.partnerId = BigInt(query.partnerId);
if (query.partnerId) {
where.partnerAccounts = { some: { id: BigInt(query.partnerId), isPrimary: 1 } };
}
const [items, total] = await Promise.all([
this.prisma.commonCity.findMany({
@@ -25,8 +32,12 @@ export class AdminCitiesService {
skip: (page - 1) * pageSize,
take: pageSize,
include: {
partner: { select: { id: true, companyName: true } },
_count: { select: { stores: true, orders: true } },
partnerAccounts: {
where: { isPrimary: 1 },
select: { id: true, companyName: true, scopeType: true, bindingStatus: true },
orderBy: { createdAt: 'asc' },
},
_count: { select: { stores: true, orders: true, partnerAccounts: true, warehouses: true } },
},
}),
this.prisma.commonCity.count({ where }),
@@ -34,8 +45,18 @@ export class AdminCitiesService {
return serializeBigInt({
items: items.map((c) => ({
...c,
partnerBindings: c.partnerAccounts.map((bp) => ({
id: bp.id.toString(),
partnerAccountId: bp.id.toString(),
partnerCompanyName: bp.companyName,
scopeType: bp.scopeType,
status: bp.bindingStatus,
})),
partnerAccounts: undefined,
storeCount: c._count.stores,
orderCount: c._count.orders,
partnerBindingCount: c.partnerAccounts.length,
warehouseCount: c._count.warehouses,
_count: undefined,
})),
total,
@@ -48,13 +69,22 @@ export class AdminCitiesService {
const city = await this.prisma.commonCity.findUnique({
where: { id },
include: {
partner: true,
commissionRule: true,
warehouses: {
include: { partnerAccount: { select: { id: true, companyName: true } } },
orderBy: { createdAt: 'desc' },
},
_count: { select: { stores: true, orders: true } },
},
});
if (!city) throw new NotFoundException('开城城市不存在');
return serializeBigInt(city);
const cityPartners = await this.partnerCityService.listByCity(id);
return serializeBigInt({
...city,
cityPartners,
storeCount: city._count.stores,
orderCount: city._count.orders,
_count: undefined,
});
}
async create(dto: CreateCityDto) {
@@ -65,31 +95,48 @@ export class AdminCitiesService {
code: dto.code,
name: dto.name,
province: dto.province,
partnerId: dto.partnerId ? BigInt(dto.partnerId) : null,
status: (dto.status ?? 'PENDING') as 'PENDING' | 'ACTIVE' | 'PAUSED',
commissionRule: {
create: {
orderCommissionRate: 0,
redeemCommissionRate: 0.03,
},
},
},
});
return serializeBigInt(city);
}
async update(id: bigint, dto: UpdateCityDto) {
if (dto.maxPartnerCommissionRate !== undefined) {
const maxRate = resolveMaxPartnerCommissionRate(dto.maxPartnerCommissionRate);
const partners = await this.prisma.partnerAccount.findMany({
where: { cityId: id, isPrimary: 1 },
select: {
companyName: true,
orderCommissionRate: true,
redeemCommissionRate: true,
},
});
for (const partner of partners) {
const check = validatePartnerCommissionRates(
Number(partner.orderCommissionRate ?? 0),
Number(partner.redeemCommissionRate ?? 0.03),
maxRate,
);
if (!check.ok) {
throw new BadRequestException(
`无法保存:合伙人「${partner.companyName ?? '—'}${check.message}`,
);
}
}
}
const city = await this.prisma.commonCity.update({
where: { id },
data: {
...(dto.name !== undefined ? { name: dto.name } : {}),
...(dto.province !== undefined ? { province: dto.province } : {}),
...(dto.partnerId !== undefined
? { partnerId: dto.partnerId ? BigInt(dto.partnerId) : null }
: {}),
...(dto.status !== undefined ? { status: dto.status as 'PENDING' | 'ACTIVE' | 'PAUSED' } : {}),
...(dto.localMinQty !== undefined ? { localMinQty: dto.localMinQty } : {}),
...(dto.crossMinQty !== undefined ? { crossMinQty: dto.crossMinQty } : {}),
...(dto.maxPartnerCommissionRate !== undefined
? { maxPartnerCommissionRate: dto.maxPartnerCommissionRate }
: {}),
},
});
return serializeBigInt(city);
@@ -0,0 +1,83 @@
import { Body, Controller, Delete, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
import { CityWarehouseService } from '../city-scope/city-warehouse.service';
import { CreateCityWarehouseDto, UpdateCityWarehouseDto } from './dto/admin-mutate.dto';
import { AdminCityWarehousesQueryDto } from './dto/admin-query.dto';
import type { WarehouseManagerType, WarehouseStatus } from '@prisma/client';
@Controller('admin/cities/:cityId/warehouses')
@UseGuards(HqAuthGuard)
export class AdminCityWarehousesController {
constructor(private readonly service: CityWarehouseService) {}
@Get()
list(@Param('cityId') cityId: string) {
return this.service.listByCity(BigInt(cityId));
}
@Post()
@HqOperation({
action: HqOperationAction.WAREHOUSE_CREATE,
refType: 'WAREHOUSE',
refIdField: 'id',
includeBody: true,
})
create(@Param('cityId') cityId: string, @Body() dto: CreateCityWarehouseDto) {
return this.service.create(BigInt(cityId), {
name: dto.name,
address: dto.address,
contactName: dto.contactName,
contactPhone: dto.contactPhone,
managerType: dto.managerType as WarehouseManagerType,
partnerAccountId: dto.partnerAccountId ? BigInt(dto.partnerAccountId) : undefined,
status: dto.status as WarehouseStatus | undefined,
});
}
}
@Controller('admin/city-warehouses')
@UseGuards(HqAuthGuard)
export class AdminCityWarehouseMutationsController {
constructor(private readonly service: CityWarehouseService) {}
@Get()
listAll(@Query() query: AdminCityWarehousesQueryDto) {
return this.service.listAll(query);
}
@Put(':id')
@HqOperation({
action: HqOperationAction.WAREHOUSE_UPDATE,
refType: 'WAREHOUSE',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdateCityWarehouseDto) {
return this.service.update(BigInt(id), {
name: dto.name,
address: dto.address,
contactName: dto.contactName,
contactPhone: dto.contactPhone,
managerType: dto.managerType as WarehouseManagerType | undefined,
partnerAccountId:
dto.partnerAccountId === null
? undefined
: dto.partnerAccountId
? BigInt(dto.partnerAccountId)
: undefined,
status: dto.status as WarehouseStatus | undefined,
});
}
@Delete(':id')
@HqOperation({
action: HqOperationAction.WAREHOUSE_DELETE,
refType: 'WAREHOUSE',
refIdParam: 'id',
})
remove(@Param('id') id: string) {
return this.service.remove(BigInt(id));
}
}
@@ -38,7 +38,7 @@ export class AdminDashboardService {
_count: { status: true },
}),
this.prisma.store.count(),
this.prisma.partner.count(),
this.prisma.partnerAccount.count({ where: { isPrimary: 1 } }),
this.prisma.redeemRecord.count({ where: { createdAt: { gte: todayStart } } }),
this.prisma.orderDelivery.count(),
this.prisma.storePayout.count({ where: { status: 'PENDING' } }),
@@ -15,12 +15,12 @@ export class AdminPartnerLogsService {
async list(query: AdminPartnerLogsQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const partnerIds = await this.resolvePartnerIds(query);
if (partnerIds && partnerIds.length === 0) {
const partnerAccountIds = await this.resolvePartnerAccountIds(query);
if (partnerAccountIds && partnerAccountIds.length === 0) {
return { items: [], total: 0, page, pageSize };
}
const where = this.buildWhere(query, partnerIds);
const where = this.buildWhere(query, partnerAccountIds);
const [rows, total] = await Promise.all([
this.prisma.logPartnerAnalytics.findMany({
where,
@@ -44,10 +44,10 @@ export class AdminPartnerLogsService {
private buildWhere(
query: AdminPartnerLogsQueryDto,
partnerIds?: bigint[],
partnerAccountIds?: bigint[],
): Prisma.LogPartnerAnalyticsWhereInput {
const where: Prisma.LogPartnerAnalyticsWhereInput = {};
if (partnerIds) where.partnerId = { in: partnerIds };
if (partnerAccountIds) where.partnerAccountId = { in: partnerAccountIds };
if (query.partnerAccountId) where.partnerAccountId = BigInt(query.partnerAccountId);
const categoryEvents = query.eventName
? [query.eventName]
@@ -64,40 +64,23 @@ export class AdminPartnerLogsService {
return where;
}
private async resolvePartnerIds(query: AdminPartnerLogsQueryDto): Promise<bigint[] | undefined> {
private async resolvePartnerAccountIds(
query: AdminPartnerLogsQueryDto,
): Promise<bigint[] | undefined> {
if (query.partnerAccountId) return [BigInt(query.partnerAccountId)];
if (query.partnerId) return [BigInt(query.partnerId)];
const partnerWhere: Prisma.PartnerWhereInput = {};
if (query.companyName) partnerWhere.companyName = { contains: query.companyName };
const accountWhere: Prisma.PartnerAccountWhereInput = { isPrimary: 1 };
if (query.companyName) accountWhere.companyName = { contains: query.companyName };
if (query.phone) accountWhere.phone = { contains: query.phone };
if (query.partnerAccountId || query.phone) {
const accountWhere: Prisma.PartnerAccountWhereInput = {};
if (query.partnerAccountId) accountWhere.id = BigInt(query.partnerAccountId);
if (query.phone) accountWhere.phone = { contains: query.phone };
if (query.companyName || query.phone) {
const accounts = await this.prisma.partnerAccount.findMany({
where: accountWhere,
select: { partnerId: true },
take: 100,
});
if (accounts.length === 0) return [];
const ids = [...new Set(accounts.map((a) => a.partnerId))];
if (query.companyName) {
const partners = await this.prisma.partner.findMany({
where: { id: { in: ids }, ...partnerWhere },
select: { id: true },
});
return partners.map((p) => p.id);
}
return ids;
}
if (query.companyName) {
const partners = await this.prisma.partner.findMany({
where: partnerWhere,
select: { id: true },
take: 100,
});
return partners.map((p) => p.id);
return accounts.map((a) => a.id);
}
return undefined;
@@ -106,8 +89,7 @@ export class AdminPartnerLogsService {
private async enrichRows(
rows: Array<{
id: bigint;
partnerAccountId: bigint | null;
partnerId: bigint;
partnerAccountId: bigint;
eventName: string;
clientApp: string | null;
refType: string | null;
@@ -116,37 +98,25 @@ export class AdminPartnerLogsService {
createdAt: Date;
}>,
) {
const partnerIds = [...new Set(rows.map((r) => r.partnerId))];
const accountIds = [...new Set(rows.map((r) => r.partnerAccountId).filter((id): id is bigint => id != null))];
const accountIds = [...new Set(rows.map((r) => r.partnerAccountId))];
const accounts = accountIds.length
? await this.prisma.partnerAccount.findMany({
where: { id: { in: accountIds } },
select: { id: true, name: true, phone: true, companyName: true },
})
: [];
const [partners, accounts] = await Promise.all([
partnerIds.length
? this.prisma.partner.findMany({
where: { id: { in: partnerIds } },
select: { id: true, companyName: true },
})
: Promise.resolve([]),
accountIds.length
? this.prisma.partnerAccount.findMany({
where: { id: { in: accountIds } },
select: { id: true, name: true, phone: true },
})
: Promise.resolve([]),
]);
const partnerMap = new Map(partners.map((p) => [p.id.toString(), p] as const));
const accountMap = new Map(accounts.map((a) => [a.id.toString(), a] as const));
return rows.map((row) => {
const partner = partnerMap.get(row.partnerId.toString());
const account = row.partnerAccountId ? accountMap.get(row.partnerAccountId.toString()) : undefined;
const account = accountMap.get(row.partnerAccountId.toString());
return {
id: row.id.toString(),
partnerId: row.partnerId.toString(),
partnerAccountId: row.partnerAccountId?.toString() ?? null,
partnerId: row.partnerAccountId.toString(),
partnerAccountId: row.partnerAccountId.toString(),
accountName: account?.name ?? null,
accountPhone: account?.phone ?? null,
companyName: partner?.companyName ?? null,
companyName: account?.companyName ?? null,
category: resolvePartnerLogCategory(row.eventName),
eventName: row.eventName,
clientApp: row.clientApp,
@@ -1,7 +1,9 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { Prisma, CityPartnerScopeType, CityPartnerStatus } from '@prisma/client';
import { resolveMaxPartnerCommissionRate, validatePartnerCommissionRates } from '@dukang/domain';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { PartnerCityService } from '../city-scope/partner-city.service';
import type { AdminPartnerAccountsQueryDto, AdminPartnersQueryDto } from './dto/admin-query.dto';
import type {
CreatePartnerAccountDto,
@@ -10,77 +12,36 @@ import type {
UpdatePartnerDto,
} from './dto/admin-mutate.dto';
const PRIMARY_WHERE = { isPrimary: 1 } as const;
function assertPartnerCommissionRates(
city: { maxPartnerCommissionRate: Prisma.Decimal | number | null },
orderCommissionRate: number,
redeemCommissionRate: number,
) {
const maxRate = resolveMaxPartnerCommissionRate(
city.maxPartnerCommissionRate != null ? Number(city.maxPartnerCommissionRate) : null,
);
const check = validatePartnerCommissionRates(orderCommissionRate, redeemCommissionRate, maxRate);
if (!check.ok) throw new BadRequestException(check.message);
}
@Injectable()
export class AdminPartnersService {
constructor(private readonly prisma: PrismaService) {}
constructor(
private readonly prisma: PrismaService,
private readonly partnerCityService: PartnerCityService,
) {}
async listPartners(query: AdminPartnersQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where: Prisma.PartnerWhereInput = {};
const where: Prisma.PartnerAccountWhereInput = { ...PRIMARY_WHERE };
if (query.companyName) where.companyName = { contains: query.companyName };
if (query.contactPhone) where.contactPhone = { contains: query.contactPhone };
const [items, total] = await Promise.all([
this.prisma.partner.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
include: {
_count: { select: { stores: true, accounts: true, cities: true } },
},
}),
this.prisma.partner.count({ where }),
]);
return serializeBigInt({
items: items.map((p) => ({
...p,
storeCount: p._count.stores,
accountCount: p._count.accounts,
cityCount: p._count.cities,
_count: undefined,
})),
total,
page,
pageSize,
});
}
async detailPartner(id: bigint) {
const partner = await this.prisma.partner.findUnique({
where: { id },
include: {
cities: { select: { id: true, code: true, name: true, status: true } },
accounts: { select: { id: true, phone: true, name: true, isPrimary: true, status: true } },
stores: { select: { id: true, name: true, status: true }, take: 10, orderBy: { createdAt: 'desc' } },
_count: { select: { stores: true, accounts: true } },
},
});
if (!partner) throw new NotFoundException('开城合伙人不存在');
return serializeBigInt(partner);
}
async createPartner(dto: CreatePartnerDto) {
const partner = await this.prisma.partner.create({ data: dto });
return serializeBigInt(partner);
}
async updatePartner(id: bigint, dto: UpdatePartnerDto) {
const partner = await this.prisma.partner.update({ where: { id }, data: dto });
return serializeBigInt(partner);
}
async listPartnerAccounts(query: AdminPartnerAccountsQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where: Prisma.PartnerAccountWhereInput = {};
if (query.phone) where.phone = { contains: query.phone };
if (query.partnerId) where.partnerId = BigInt(query.partnerId);
if (query.status) where.status = query.status as Prisma.EnumAccountStatusFilter['equals'];
if (query.isPrimary === '0' || query.isPrimary === '1') {
where.isPrimary = Number(query.isPrimary);
}
if (query.cityId) where.cityId = BigInt(query.cityId);
if (query.partnerId) where.id = BigInt(query.partnerId);
const [items, total] = await Promise.all([
this.prisma.partnerAccount.findMany({
@@ -89,8 +50,257 @@ export class AdminPartnersService {
skip: (page - 1) * pageSize,
take: pageSize,
include: {
partner: { select: { id: true, companyName: true } },
parent: { select: { id: true, name: true, phone: true } },
city: { select: { id: true, code: true, name: true, status: true, maxPartnerCommissionRate: true } },
managedWarehouse: { select: { id: true, name: true } },
children: {
select: {
id: true,
phone: true,
name: true,
staffRole: true,
permissions: true,
status: true,
},
orderBy: { createdAt: 'asc' },
},
_count: { select: { stores: true, children: true } },
},
}),
this.prisma.partnerAccount.count({ where }),
]);
return serializeBigInt({
items: items.map((p) => ({
id: p.id.toString(),
companyName: p.companyName,
contactPhone: p.contactPhone,
phone: p.phone,
name: p.name,
cityId: p.cityId?.toString() ?? null,
cityName: p.city?.name ?? null,
maxPartnerCommissionRate:
p.city?.maxPartnerCommissionRate != null ? Number(p.city.maxPartnerCommissionRate) : null,
scopeType: p.scopeType,
orderCommissionRate: Number(p.orderCommissionRate ?? 0),
redeemCommissionRate: Number(p.redeemCommissionRate ?? 0.03),
bindingStatus: p.bindingStatus,
managedWarehouseId: p.managedWarehouseId?.toString() ?? null,
managedWarehouseName: p.managedWarehouse?.name ?? null,
storeCount: p._count.stores,
accountCount: p._count.children + 1,
children: p.children.map((c) => ({
id: c.id.toString(),
phone: c.phone,
name: c.name,
staffRole: c.staffRole,
permissions: c.permissions,
status: c.status,
})),
createdAt: p.createdAt,
})),
total,
page,
pageSize,
});
}
async detailPartner(id: bigint) {
const account = await this.prisma.partnerAccount.findFirst({
where: { id, ...PRIMARY_WHERE },
include: {
city: { select: { id: true, code: true, name: true, status: true, maxPartnerCommissionRate: true } },
managedWarehouse: { select: { id: true, name: true } },
children: {
where: { isPrimary: 0 },
select: {
id: true,
phone: true,
name: true,
staffRole: true,
permissions: true,
status: true,
createdAt: true,
},
orderBy: { createdAt: 'asc' },
},
stores: { select: { id: true, name: true, status: true }, take: 10, orderBy: { createdAt: 'desc' } },
_count: { select: { stores: true, children: true } },
},
});
if (!account) throw new NotFoundException('开城合伙人不存在');
return serializeBigInt({
...this.partnerCityService.toDto({
...account,
city: account.city,
}),
contactPhone: account.contactPhone,
address: account.address,
bankAccountName: account.bankAccountName,
bankAccountNo: account.bankAccountNo,
bankBranch: account.bankBranch,
managedWarehouseName: account.managedWarehouse?.name ?? null,
accountCount: account._count.children + 1,
maxPartnerCommissionRate:
account.city?.maxPartnerCommissionRate != null
? Number(account.city.maxPartnerCommissionRate)
: null,
children: account.children.map((c) => ({
id: c.id.toString(),
phone: c.phone,
name: c.name,
staffRole: c.staffRole,
permissions: c.permissions,
status: c.status,
createdAt: c.createdAt,
})),
});
}
async createPartner(dto: CreatePartnerDto) {
const phone = dto.phone.trim();
if (!/^1[3-9]\d{9}$/.test(phone)) {
throw new BadRequestException('请输入正确的登录手机号');
}
const phoneTaken = await this.prisma.partnerAccount.findUnique({ where: { phone } });
if (phoneTaken) throw new BadRequestException('该手机号已被使用');
const cityId = BigInt(dto.cityId);
const city = await this.prisma.commonCity.findUnique({ where: { id: cityId } });
if (!city) throw new NotFoundException('开城城市不存在');
await this.partnerCityService.validatePrimaryBinding(cityId, {
scopeType: dto.scopeType as CityPartnerScopeType,
districtCodes: dto.districtCodes,
});
const orderCommissionRate = dto.orderCommissionRate ?? 0;
const redeemCommissionRate = dto.redeemCommissionRate ?? 0.03;
assertPartnerCommissionRates(city, orderCommissionRate, redeemCommissionRate);
const account = await this.prisma.partnerAccount.create({
data: {
phone,
name: dto.name.trim(),
isPrimary: 1,
staffRole: 'PARTNER',
status: 'ACTIVE',
cityId,
scopeType: dto.scopeType as CityPartnerScopeType,
districtCodes:
dto.scopeType === 'DISTRICT' ? (dto.districtCodes ?? []) : Prisma.JsonNull,
orderCommissionRate: orderCommissionRate,
redeemCommissionRate: redeemCommissionRate,
bindingStatus: (dto.bindingStatus ?? 'ACTIVE') as CityPartnerStatus,
companyName: dto.companyName.trim(),
address: dto.address.trim(),
contactPhone: dto.contactPhone?.trim() ?? phone,
contractNo: dto.contractNo,
bankAccountName: dto.bankAccountName,
bankAccountNo: dto.bankAccountNo,
bankBranch: dto.bankBranch,
weeklyStoreTarget: dto.weeklyStoreTarget ?? 20,
},
include: { city: { select: { id: true, code: true, name: true } } },
});
return serializeBigInt(this.partnerCityService.toDto(account));
}
async updatePartner(id: bigint, dto: UpdatePartnerDto) {
const existing = await this.prisma.partnerAccount.findFirst({
where: { id, ...PRIMARY_WHERE },
});
if (!existing) throw new NotFoundException('开城合伙人不存在');
const city = await this.prisma.commonCity.findUniqueOrThrow({ where: { id: existing.cityId! } });
const cityId = existing.cityId!;
const scopeType = (dto.scopeType ?? existing.scopeType) as CityPartnerScopeType;
const districtCodes =
scopeType === 'DISTRICT'
? dto.districtCodes ?? this.partnerCityService.parseDistrictCodes(existing.districtCodes)
: null;
await this.partnerCityService.validatePrimaryBinding(
cityId,
{
partnerAccountId: id.toString(),
scopeType,
districtCodes: districtCodes ?? undefined,
},
id,
);
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.partnerAccount.findUnique({ where: { phone } });
if (phoneTaken && phoneTaken.id !== id) {
throw new BadRequestException('该手机号已被使用');
}
}
const orderCommissionRate =
dto.orderCommissionRate !== undefined
? dto.orderCommissionRate
: Number(existing.orderCommissionRate ?? 0);
const redeemCommissionRate =
dto.redeemCommissionRate !== undefined
? dto.redeemCommissionRate
: Number(existing.redeemCommissionRate ?? 0.03);
if (dto.orderCommissionRate !== undefined || dto.redeemCommissionRate !== undefined) {
assertPartnerCommissionRates(city, orderCommissionRate, redeemCommissionRate);
}
const account = await this.prisma.partnerAccount.update({
where: { id },
data: {
...(dto.name !== undefined ? { name: dto.name.trim() } : {}),
...(dto.phone !== undefined ? { phone: dto.phone.trim() } : {}),
...(dto.companyName !== undefined ? { companyName: dto.companyName.trim() } : {}),
...(dto.address !== undefined ? { address: dto.address.trim() } : {}),
...(dto.contactPhone !== undefined ? { contactPhone: dto.contactPhone.trim() } : {}),
...(dto.scopeType !== undefined ? { scopeType: dto.scopeType as CityPartnerScopeType } : {}),
...(dto.scopeType !== undefined || dto.districtCodes !== undefined
? {
districtCodes:
scopeType === 'DISTRICT'
? ((districtCodes ?? []) as Prisma.InputJsonValue)
: Prisma.JsonNull,
}
: {}),
...(dto.orderCommissionRate !== undefined ? { orderCommissionRate: dto.orderCommissionRate } : {}),
...(dto.redeemCommissionRate !== undefined ? { redeemCommissionRate: dto.redeemCommissionRate } : {}),
...(dto.bindingStatus !== undefined ? { bindingStatus: dto.bindingStatus as CityPartnerStatus } : {}),
...(dto.contractNo !== undefined ? { contractNo: dto.contractNo } : {}),
...(dto.bankAccountName !== undefined ? { bankAccountName: dto.bankAccountName } : {}),
...(dto.bankAccountNo !== undefined ? { bankAccountNo: dto.bankAccountNo } : {}),
...(dto.bankBranch !== undefined ? { bankBranch: dto.bankBranch } : {}),
...(dto.weeklyStoreTarget !== undefined ? { weeklyStoreTarget: dto.weeklyStoreTarget } : {}),
...(dto.status !== undefined ? { status: dto.status as 'ACTIVE' | 'DISABLED' } : {}),
},
include: { city: { select: { id: true, code: true, name: true } } },
});
return serializeBigInt(this.partnerCityService.toDto(account));
}
async listPartnerAccounts(query: AdminPartnerAccountsQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where: Prisma.PartnerAccountWhereInput = { isPrimary: 0 };
if (query.phone) where.phone = { contains: query.phone };
if (query.partnerId) where.parentAccountId = BigInt(query.partnerId);
if (query.status) where.status = query.status as Prisma.EnumAccountStatusFilter['equals'];
const [items, total] = await Promise.all([
this.prisma.partnerAccount.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
include: {
parent: { select: { id: true, name: true, phone: true, companyName: true } },
},
}),
this.prisma.partnerAccount.count({ where }),
@@ -98,14 +308,14 @@ export class AdminPartnersService {
return serializeBigInt({ items, total, page, pageSize });
}
async listPartnerAccountTree(partnerId?: bigint) {
const where: Prisma.PartnerAccountWhereInput = {};
if (partnerId) where.partnerId = partnerId;
async listPartnerAccountTree(primaryAccountId?: bigint) {
const where: Prisma.PartnerAccountWhereInput = primaryAccountId
? { OR: [{ id: primaryAccountId }, { parentAccountId: primaryAccountId }] }
: { isPrimary: 1 };
const accounts = await this.prisma.partnerAccount.findMany({
where,
orderBy: [{ isPrimary: 'desc' }, { createdAt: 'asc' }],
include: { partner: { select: { id: true, companyName: true } } },
});
type TreeNode = (typeof accounts)[number] & { children: TreeNode[] };
@@ -122,7 +332,7 @@ export class AdminPartnersService {
const parent = nodeMap.get(account.parentAccountId.toString());
if (parent) parent.children.push(node);
else roots.push(node);
} else {
} else if (account.isPrimary === 1) {
roots.push(node);
}
}
@@ -134,8 +344,9 @@ export class AdminPartnersService {
status: node.status,
isPrimary: node.isPrimary,
staffRole: node.staffRole,
permissions: node.permissions,
parentAccountId: node.parentAccountId,
partner: node.partner,
companyName: node.companyName,
createdAt: node.createdAt,
lastLoginAt: node.lastLoginAt,
children: node.children.length ? node.children.map(mapNode) : undefined,
@@ -148,27 +359,21 @@ export class AdminPartnersService {
const account = await this.prisma.partnerAccount.findUnique({
where: { id },
include: {
partner: {
select: {
id: true,
companyName: true,
contactPhone: true,
address: true,
},
},
parent: { select: { id: true, name: true, phone: true } },
parent: { select: { id: true, name: true, phone: true, companyName: true } },
},
});
if (!account) throw new NotFoundException('开城合伙人账号不存在');
if (!account) throw new NotFoundException('合伙人账号不存在');
const primary = await this.partnerCityService.resolvePrimaryAccount(id);
const orderWhere = await this.partnerCityService.buildPartnerOrderWhere(primary.id);
const [bills, orders] = await Promise.all([
this.prisma.partnerBill.findMany({
where: { partnerId: account.partnerId },
where: { partnerAccountId: primary.id },
orderBy: { createdAt: 'desc' },
take: 50,
}),
this.prisma.order.findMany({
where: { city: { partnerId: account.partnerId } },
where: orderWhere,
orderBy: { createdAt: 'desc' },
take: 50,
select: {
@@ -182,10 +387,14 @@ export class AdminPartnersService {
}),
]);
return serializeBigInt({ ...account, bills, orders });
return serializeBigInt({ ...account, primaryAccountId: primary.id, bills, orders });
}
async createPartnerAccount(dto: CreatePartnerAccountDto) {
if (!dto.parentAccountId) {
throw new BadRequestException('请指定主账号 parentAccountId 创建子账号');
}
const phone = dto.phone.trim();
if (!/^1[3-9]\d{9}$/.test(phone)) {
throw new BadRequestException('请输入正确的手机号码');
@@ -193,53 +402,40 @@ export class AdminPartnersService {
const phoneTaken = await this.prisma.partnerAccount.findUnique({ where: { phone } });
if (phoneTaken) throw new BadRequestException('该手机号已被使用');
if (dto.parentAccountId) {
const parent = await this.prisma.partnerAccount.findUnique({
where: { id: BigInt(dto.parentAccountId) },
});
if (!parent) throw new BadRequestException('主账号不存在');
if (parent.isPrimary !== 1) throw new BadRequestException('仅可向主账号添加子账号');
if (dto.partnerId && dto.partnerId !== parent.partnerId.toString()) {
throw new BadRequestException('开城合伙人与主账号不匹配');
}
const account = await this.prisma.partnerAccount.create({
data: {
partnerId: parent.partnerId,
phone,
name: dto.name.trim(),
staffRole: (dto.staffRole ?? 'INTERNAL') as 'PARTNER' | 'INTERNAL' | 'PROMOTER',
isPrimary: 0,
parentAccountId: parent.id,
status: 'ACTIVE',
},
include: { partner: { select: { id: true, companyName: true } } },
});
return serializeBigInt(account);
const parent = await this.prisma.partnerAccount.findUnique({
where: { id: BigInt(dto.parentAccountId) },
});
if (!parent) throw new BadRequestException('主账号不存在');
if (parent.isPrimary !== 1) {
throw new BadRequestException('仅可向主账号添加子账号,不支持多级子账号');
}
const partner = await this.prisma.partner.findUnique({ where: { id: BigInt(dto.partnerId!) } });
if (!partner) throw new BadRequestException('开城合伙人不存在');
const account = await this.prisma.partnerAccount.create({
data: {
partnerId: partner.id,
phone,
name: dto.name.trim(),
staffRole: dto.staffRole ? (dto.staffRole as 'PARTNER' | 'INTERNAL' | 'PROMOTER') : undefined,
staffRole: (dto.staffRole ?? 'INTERNAL') as 'PARTNER' | 'INTERNAL' | 'PROMOTER',
permissions: dto.permissions ?? undefined,
isPrimary: 0,
parentAccountId: parent.id,
status: 'ACTIVE',
},
include: { partner: { select: { id: true, companyName: true } } },
});
return serializeBigInt(account);
}
async updatePartnerAccount(id: bigint, dto: UpdatePartnerAccountDto) {
const existing = await this.prisma.partnerAccount.findUnique({ where: { id } });
if (!existing) throw new NotFoundException('开城合伙人账号不存在');
if (!existing) throw new NotFoundException('合伙人账号不存在');
if (existing.isPrimary === 1) {
throw new BadRequestException('请通过开城合伙人接口编辑主账号');
}
const data: Prisma.PartnerAccountUpdateInput = {};
if (dto.name !== undefined) data.name = dto.name;
if (dto.status !== undefined) data.status = dto.status as 'ACTIVE' | 'DISABLED';
if (dto.staffRole !== undefined) data.staffRole = dto.staffRole as 'PARTNER' | 'INTERNAL' | 'PROMOTER';
if (dto.permissions !== undefined) data.permissions = dto.permissions;
if (dto.phone !== undefined) {
const phone = dto.phone.trim();
if (!/^1[3-9]\d{9}$/.test(phone)) {
@@ -258,7 +454,7 @@ export class AdminPartnersService {
async deletePartnerSubAccount(id: bigint) {
const account = await this.prisma.partnerAccount.findUnique({ where: { id } });
if (!account) throw new NotFoundException('开城合伙人账号不存在');
if (!account) throw new NotFoundException('合伙人账号不存在');
if (!account.parentAccountId) {
throw new BadRequestException('仅可删除子账号');
}
@@ -40,7 +40,7 @@ export class AdminRedeemService {
where: { id },
include: {
user: true,
store: { include: { partner: { select: { id: true, companyName: true } } } },
store: { include: { partnerAccount: { select: { id: true, companyName: true } } } },
coupon: true,
payout: true,
},
@@ -4,6 +4,7 @@ import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { mapStoreCompat } from '../../common/compat/v31-compat';
import type { AdminStoreAccountsQueryDto, AdminStoreMediaQueryDto, AdminStoresQueryDto } from './dto/admin-query.dto';
import { PartnerCityService } from '../city-scope/partner-city.service';
import type {
CreateStoreAccountDto,
CreateStoreDto,
@@ -16,7 +17,10 @@ import type {
@Injectable()
export class AdminStoresService {
constructor(private readonly prisma: PrismaService) {}
constructor(
private readonly prisma: PrismaService,
private readonly partnerCityService: PartnerCityService,
) {}
async listStores(query: AdminStoresQueryDto) {
const page = query.page ?? 1;
@@ -25,7 +29,7 @@ export class AdminStoresService {
if (query.name) where.name = { contains: query.name };
if (query.status) where.status = query.status as Prisma.EnumStoreStatusFilter['equals'];
if (query.cityId) where.cityId = BigInt(query.cityId);
if (query.partnerId) where.partnerId = BigInt(query.partnerId);
if (query.partnerId) where.partnerAccountId = BigInt(query.partnerId);
if (query.phone) where.phone = { contains: query.phone };
const [items, total] = await Promise.all([
@@ -36,7 +40,7 @@ export class AdminStoresService {
take: pageSize,
include: {
cityRef: { select: { id: true, name: true, code: true } },
partner: { select: { id: true, companyName: true } },
partnerAccount: { select: { id: true, companyName: true } },
account: { select: { id: true, phone: true, name: true, status: true } },
coverResource: { select: { id: true, url: true } },
},
@@ -56,7 +60,7 @@ export class AdminStoresService {
where: { id },
include: {
cityRef: true,
partner: true,
partnerAccount: true,
category: true,
account: true,
coverResource: true,
@@ -123,6 +127,7 @@ export class AdminStoresService {
...(dto.intro !== undefined ? { intro: dto.intro } : {}),
...(dto.address !== undefined ? { address: dto.address } : {}),
...(dto.district !== undefined ? { district: dto.district } : {}),
...(dto.settlementRate !== undefined ? { settlementRate: dto.settlementRate } : {}),
},
});
@@ -162,18 +167,22 @@ export class AdminStoresService {
});
if (existingAccount) throw new BadRequestException('该手机号已绑定门店');
const partner = await this.prisma.partner.findUnique({ where: { id: BigInt(dto.partnerId) } });
if (!partner) throw new BadRequestException('开城合伙人不存在');
const partnerAccountId = BigInt(dto.partnerAccountId);
const partnerAccount = await this.prisma.partnerAccount.findUnique({
where: { id: partnerAccountId },
});
if (!partnerAccount || partnerAccount.isPrimary !== 1) {
throw new BadRequestException('开城合伙人不存在');
}
const city = await this.prisma.commonCity.findUnique({ where: { id: BigInt(dto.cityId) } });
if (!city) throw new BadRequestException('开城城市不存在');
if (city.partnerId && city.partnerId !== partner.id) {
throw new BadRequestException('开城城市与合伙人不匹配');
}
await this.partnerCityService.assertPartnerAccountBoundToCity(partnerAccountId, city.id);
const store = await this.prisma.store.create({
data: {
cityId: city.id,
partnerId: partner.id,
partnerAccountId,
settlementRate: dto.settlementRate ?? 0.6,
categoryId: dto.categoryId ? BigInt(dto.categoryId) : null,
name: dto.name,
phone: normalizedPhone,
@@ -359,7 +368,7 @@ export class AdminStoresService {
async detailStoreAccount(id: bigint) {
const account = await this.prisma.storeAccount.findUnique({
where: { id },
include: { store: { include: { cityRef: true, partner: true } } },
include: { store: { include: { cityRef: true, partnerAccount: true } } },
});
if (!account) throw new NotFoundException('门店账号不存在');
return serializeBigInt(account);
@@ -4,6 +4,7 @@ import { BenefitService } from '../benefit/benefit.service';
import { TradeService } from '../trade/trade.service';
import { TicketService } from '../common/ticket.service';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { PartnerCityService } from '../city-scope/partner-city.service';
import type { TicketListQueryDto } from '../common/dto/common-query.dto';
@Injectable()
@@ -13,6 +14,7 @@ export class AdminTicketsService {
private readonly ticketService: TicketService,
private readonly tradeService: TradeService,
private readonly benefitService: BenefitService,
private readonly partnerCityService: PartnerCityService,
) {}
list(query: TicketListQueryDto) {
@@ -82,15 +84,9 @@ export class AdminTicketsService {
}
async listPartnerReshipments(partnerAccountId: bigint) {
const account = await this.prisma.partnerAccount.findUniqueOrThrow({
where: { id: partnerAccountId },
});
const cities = await this.prisma.commonCity.findMany({
where: { partnerId: account.partnerId },
select: { id: true },
});
const orderWhere = await this.partnerCityService.buildPartnerOrderWhere(partnerAccountId);
const orders = await this.prisma.order.findMany({
where: { cityId: { in: cities.map((c) => c.id) } },
where: orderWhere,
select: { id: true },
});
const orderIds = orders.map((o) => o.id);
@@ -213,11 +213,23 @@ export class AdminWechatBindingsService {
const accounts = await this.prisma.partnerAccount.findMany({
where,
include: { partner: { select: { id: true, companyName: true } } },
select: {
id: true,
phone: true,
name: true,
wxOpenId: true,
wxUnionId: true,
lastLoginAt: true,
status: true,
isPrimary: true,
parentAccountId: true,
companyName: true,
},
});
for (const a of accounts) {
if (!a.wxOpenId) continue;
const refId = a.isPrimary === 1 ? a.id : (a.parentAccountId ?? a.id);
rows.push({
actorType: 'PARTNER',
actorId: a.id,
@@ -225,8 +237,8 @@ export class AdminWechatBindingsService {
name: a.name,
wxOpenId: a.wxOpenId,
wxUnionId: a.wxUnionId,
refId: a.partnerId,
refLabel: a.partner.companyName,
refId,
refLabel: a.companyName,
lastLoginAt: a.lastLoginAt,
status: a.status,
});
@@ -9,6 +9,7 @@ import {
IsOptional,
IsString,
Min,
Max,
MinLength,
ValidateIf,
} from 'class-validator';
@@ -22,7 +23,7 @@ export class UpdateStoreStatusDto {
export class CreateStoreDto {
@IsString()
@IsNotEmpty()
partnerId: string;
partnerAccountId: string;
@IsString()
@IsNotEmpty()
@@ -88,6 +89,11 @@ export class CreateStoreDto {
@IsOptional()
@IsString()
contractUrl?: string;
@IsOptional()
@IsNumber()
@Min(0)
settlementRate?: number;
}
export class UpdateStoreDto {
@@ -114,6 +120,11 @@ export class UpdateStoreDto {
@IsOptional()
@IsString()
district?: string;
@IsOptional()
@IsNumber()
@Min(0)
settlementRate?: number;
}
export class CreateStoreAccountDto {
@@ -145,6 +156,18 @@ export class UpdateStoreAccountDto {
}
export class CreatePartnerDto {
@IsString()
@IsNotEmpty()
cityId: string;
@IsString()
@IsNotEmpty()
phone: string;
@IsString()
@IsNotEmpty()
name: string;
@IsString()
@IsNotEmpty()
companyName: string;
@@ -153,9 +176,40 @@ export class CreatePartnerDto {
@IsNotEmpty()
address: string;
@IsOptional()
@IsString()
@IsNotEmpty()
contactPhone: string;
contactPhone?: string;
@IsString()
@IsIn(['CITY_WIDE', 'DISTRICT'])
scopeType: string;
@IsOptional()
@IsArray()
@IsString({ each: true })
districtCodes?: string[];
@IsOptional()
@IsNumber()
@Min(0)
orderCommissionRate?: number;
@IsOptional()
@IsNumber()
@Min(0)
redeemCommissionRate?: number;
@IsOptional()
@IsIn(['ACTIVE', 'PAUSED'])
bindingStatus?: string;
@IsOptional()
@IsString()
managedWarehouseId?: string;
@IsOptional()
@IsString()
contractNo?: string;
@IsOptional()
@IsString()
@@ -168,9 +222,21 @@ export class CreatePartnerDto {
@IsOptional()
@IsString()
bankBranch?: string;
@IsOptional()
@IsNumber()
weeklyStoreTarget?: number;
}
export class UpdatePartnerDto {
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
phone?: string;
@IsOptional()
@IsString()
companyName?: string;
@@ -183,6 +249,38 @@ export class UpdatePartnerDto {
@IsString()
contactPhone?: string;
@IsOptional()
@IsIn(['CITY_WIDE', 'DISTRICT'])
scopeType?: string;
@IsOptional()
@IsArray()
@IsString({ each: true })
districtCodes?: string[];
@IsOptional()
@IsNumber()
@Min(0)
orderCommissionRate?: number;
@IsOptional()
@IsNumber()
@Min(0)
redeemCommissionRate?: number;
@IsOptional()
@IsIn(['ACTIVE', 'PAUSED'])
bindingStatus?: string;
@IsOptional()
@ValidateIf((_, v) => v !== null)
@IsString()
managedWarehouseId?: string | null;
@IsOptional()
@IsString()
contractNo?: string;
@IsOptional()
@IsString()
bankAccountName?: string;
@@ -194,6 +292,14 @@ export class UpdatePartnerDto {
@IsOptional()
@IsString()
bankBranch?: string;
@IsOptional()
@IsNumber()
weeklyStoreTarget?: number;
@IsOptional()
@IsIn(['ACTIVE', 'DISABLED'])
status?: string;
}
export class UpdatePartnerAccountDto {
@@ -208,13 +314,21 @@ export class UpdatePartnerAccountDto {
@IsOptional()
@IsIn(['ACTIVE', 'DISABLED'])
status?: string;
@IsOptional()
@IsIn(['PARTNER', 'INTERNAL', 'PROMOTER'])
staffRole?: string;
@IsOptional()
@IsArray()
@IsString({ each: true })
permissions?: string[];
}
export class CreatePartnerAccountDto {
@ValidateIf((o: CreatePartnerAccountDto) => !o.parentAccountId)
@IsString()
@IsNotEmpty()
partnerId?: string;
parentAccountId: string;
@IsString()
@IsNotEmpty()
@@ -228,10 +342,10 @@ export class CreatePartnerAccountDto {
@IsIn(['PARTNER', 'INTERNAL', 'PROMOTER'])
staffRole?: string;
/** 主账号 ID;传入则创建子账号 */
@IsOptional()
@IsString()
parentAccountId?: string;
@IsArray()
@IsString({ each: true })
permissions?: string[];
}
export class CreateCityDto {
@@ -247,10 +361,6 @@ export class CreateCityDto {
@IsNotEmpty()
province: string;
@IsOptional()
@IsString()
partnerId?: string;
@IsOptional()
@IsIn(['PENDING', 'ACTIVE', 'PAUSED'])
status?: string;
@@ -265,10 +375,6 @@ export class UpdateCityDto {
@IsString()
province?: string;
@IsOptional()
@IsString()
partnerId?: string;
@IsOptional()
@IsIn(['PENDING', 'ACTIVE', 'PAUSED'])
status?: string;
@@ -278,6 +384,127 @@ export class UpdateCityDto {
@IsOptional()
crossMinQty?: number;
@IsOptional()
@IsNumber()
@Min(0)
@Max(1)
maxPartnerCommissionRate?: number;
}
export class BindCityPartnerDto {
@IsString()
@IsNotEmpty()
partnerId: string;
@IsString()
@IsIn(['CITY_WIDE', 'DISTRICT'])
scopeType: 'CITY_WIDE' | 'DISTRICT';
@IsOptional()
@IsArray()
@IsString({ each: true })
districtCodes?: string[];
@IsOptional()
@IsNumber()
@Min(0)
orderCommissionRate?: number;
@IsOptional()
@IsNumber()
@Min(0)
redeemCommissionRate?: number;
@IsOptional()
@IsIn(['ACTIVE', 'PAUSED'])
status?: 'ACTIVE' | 'PAUSED';
}
export class UpdateCityPartnerDto {
@IsOptional()
@IsIn(['CITY_WIDE', 'DISTRICT'])
scopeType?: 'CITY_WIDE' | 'DISTRICT';
@IsOptional()
@IsArray()
@IsString({ each: true })
districtCodes?: string[];
@IsOptional()
@IsNumber()
@Min(0)
orderCommissionRate?: number;
@IsOptional()
@IsNumber()
@Min(0)
redeemCommissionRate?: number;
@IsOptional()
@IsIn(['ACTIVE', 'PAUSED'])
status?: 'ACTIVE' | 'PAUSED';
}
export class CreateCityWarehouseDto {
@IsString()
@IsNotEmpty()
name: string;
@IsString()
@IsNotEmpty()
address: string;
@IsString()
@IsNotEmpty()
contactName: string;
@IsString()
@IsNotEmpty()
contactPhone: string;
@IsString()
@IsIn(['HQ', 'PARTNER'])
managerType: 'HQ' | 'PARTNER';
@IsOptional()
@IsString()
partnerAccountId?: string;
@IsOptional()
@IsIn(['ACTIVE', 'PAUSED'])
status?: 'ACTIVE' | 'PAUSED';
}
export class UpdateCityWarehouseDto {
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
address?: string;
@IsOptional()
@IsString()
contactName?: string;
@IsOptional()
@IsString()
contactPhone?: string;
@IsOptional()
@IsIn(['HQ', 'PARTNER'])
managerType?: 'HQ' | 'PARTNER';
@IsOptional()
@ValidateIf((_, v) => v !== null)
@IsString()
partnerAccountId?: string | null;
@IsOptional()
@IsIn(['ACTIVE', 'PAUSED'])
status?: 'ACTIVE' | 'PAUSED';
}
export class CreateStoreMediaDto {
@@ -109,6 +109,36 @@ export class AdminPartnersQueryDto extends PaginationQueryDto {
@IsOptional()
@IsString()
contactPhone?: string;
@IsOptional()
@IsString()
phone?: string;
@IsOptional()
@IsString()
cityId?: string;
@IsOptional()
@IsString()
partnerId?: string;
}
export class AdminCityWarehousesQueryDto extends PaginationQueryDto {
@IsOptional()
@IsString()
cityId?: string;
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
managerType?: string;
@IsOptional()
@IsString()
status?: string;
}
export class AdminPartnerAccountsQueryDto extends PaginationQueryDto {
@@ -1,4 +1,5 @@
import { Module } from '@nestjs/common';
import { CityScopeModule } from '../city-scope/city-scope.module';
import { IamModule } from '../iam/iam.module';
import { TradeModule } from '../trade/trade.module';
import { AdminDashboardController } from './admin-dashboard.controller';
@@ -12,6 +13,7 @@ import { AdminStoresService } from './admin-stores.service';
import { AdminPartnersController, AdminPartnerAccountsController } from './admin-partners.controller';
import { AdminPartnersService } from './admin-partners.service';
import { AdminCitiesController } from './admin-cities.controller';
import { AdminCityWarehousesController, AdminCityWarehouseMutationsController } from './admin-city-warehouses.controller';
import { AdminCitiesService } from './admin-cities.service';
import { AdminBenefitCouponsController, AdminBenefitLedgersController } from './admin-benefit.controller';
import { AdminBenefitService } from './admin-benefit.service';
@@ -50,7 +52,7 @@ import { AdminHqPermissionsController } from './admin-hq-permissions.controller'
import { AdminHqPermissionsService } from './admin-hq-permissions.service';
@Module({
imports: [IamModule, TradeModule, BenefitModule, CommonModule, IntegrationsModule, RedeemModule],
imports: [CityScopeModule, IamModule, TradeModule, BenefitModule, CommonModule, IntegrationsModule, RedeemModule],
controllers: [
AdminDashboardController,
AdminUsersController,
@@ -61,6 +63,8 @@ import { AdminHqPermissionsService } from './admin-hq-permissions.service';
AdminPartnersController,
AdminPartnerAccountsController,
AdminCitiesController,
AdminCityWarehousesController,
AdminCityWarehouseMutationsController,
AdminBenefitCouponsController,
AdminBenefitLedgersController,
AdminRedeemRecordsController,
@@ -104,5 +108,6 @@ import { AdminHqPermissionsService } from './admin-hq-permissions.service';
AdminHqPermissionsService,
SuperAdminGuard,
],
exports: [CityScopeModule],
})
export class OpsModule {}