feat(assoc): v4.0.1 合伙人关联码、分佣账单与 H5 用户管理
订单佣金只认关联用户;合伙人备注写入独立表;H5 增加用户管理与首页统计。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -40,6 +40,7 @@ type OrderFilterInput = Pick<
|
||||
| 'excludeTest'
|
||||
| 'deliveryType'
|
||||
| 'promoCodeId'
|
||||
| 'assocPartnerAccountId'
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
@@ -217,6 +218,12 @@ export class AdminOrdersService {
|
||||
if (promoCodeId && /^\d+$/.test(promoCodeId)) {
|
||||
where.promoCodeId = BigInt(promoCodeId);
|
||||
}
|
||||
const assocPartnerAccountId = query.assocPartnerAccountId?.trim();
|
||||
if (assocPartnerAccountId === 'none') {
|
||||
where.partnerAccountIdAtPay = null;
|
||||
} else if (assocPartnerAccountId && /^\d+$/.test(assocPartnerAccountId)) {
|
||||
where.partnerAccountIdAtPay = BigInt(assocPartnerAccountId);
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
@@ -282,6 +289,9 @@ export class AdminOrdersService {
|
||||
hqRemark: true,
|
||||
deviceKey: true,
|
||||
phoneVerifiedAt: true,
|
||||
assocPartnerAccountId: true,
|
||||
assocBoundAt: true,
|
||||
assocPartner: { select: { id: true, name: true, companyName: true, phone: true } },
|
||||
},
|
||||
},
|
||||
delivery: {
|
||||
@@ -328,11 +338,54 @@ export class AdminOrdersService {
|
||||
: { redeemSummary: null, redeemRecords: [] };
|
||||
|
||||
const withFee = this.withDeliveryLogisticsFee(order);
|
||||
const { benefitCoupon: _coupon, ...orderRest } = withFee;
|
||||
const { benefitCoupon: _coupon, user, ...orderRest } = withFee;
|
||||
|
||||
let assocPartnerAtPay: {
|
||||
id: string;
|
||||
name: string;
|
||||
companyName: string | null;
|
||||
phone: string | null;
|
||||
orderCommissionRate: number | null;
|
||||
} | null = null;
|
||||
if (order.partnerAccountIdAtPay) {
|
||||
const partner = await this.prisma.partnerAccount.findUnique({
|
||||
where: { id: order.partnerAccountIdAtPay },
|
||||
select: { id: true, name: true, companyName: true, phone: true },
|
||||
});
|
||||
if (partner) {
|
||||
assocPartnerAtPay = {
|
||||
id: partner.id.toString(),
|
||||
name: partner.name,
|
||||
companyName: partner.companyName,
|
||||
phone: partner.phone,
|
||||
orderCommissionRate:
|
||||
order.orderCommissionRateAtPay != null ? Number(order.orderCommissionRateAtPay) : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const userAssoc = user?.assocPartner
|
||||
? {
|
||||
id: user.assocPartner.id.toString(),
|
||||
name: user.assocPartner.name,
|
||||
companyName: user.assocPartner.companyName,
|
||||
phone: user.assocPartner.phone,
|
||||
}
|
||||
: null;
|
||||
|
||||
return serializeBigInt(
|
||||
mapOrderCompat({
|
||||
...orderRest,
|
||||
user: user
|
||||
? {
|
||||
...user,
|
||||
assocPartner: userAssoc,
|
||||
assocBoundAt: user.assocBoundAt?.toISOString() ?? null,
|
||||
}
|
||||
: user,
|
||||
assocPartnerAtPay,
|
||||
orderCommissionRateAtPay:
|
||||
order.orderCommissionRateAtPay != null ? Number(order.orderCommissionRateAtPay) : null,
|
||||
statusLogs: mapStatusLogCompat(statusLogs),
|
||||
benefitCoupons: coupon
|
||||
? [
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { Body, Controller, Delete, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
|
||||
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||
import {
|
||||
HqPermissionGuard,
|
||||
RequireHqPermissions,
|
||||
} from '../../common/guards/hq-permission.guard';
|
||||
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
|
||||
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
|
||||
import { AdminPartnersService } from './admin-partners.service';
|
||||
import { PartnerAssocService } from '../store/partner-assoc.service';
|
||||
import { AdminPartnerAccountsQueryDto, AdminPartnersQueryDto } from './dto/admin-query.dto';
|
||||
import {
|
||||
CreatePartnerAccountDto,
|
||||
@@ -14,7 +19,10 @@ import {
|
||||
@Controller('admin/partners')
|
||||
@UseGuards(HqAuthGuard)
|
||||
export class AdminPartnersController {
|
||||
constructor(private readonly service: AdminPartnersService) {}
|
||||
constructor(
|
||||
private readonly service: AdminPartnersService,
|
||||
private readonly assoc: PartnerAssocService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
list(@Query() query: AdminPartnersQueryDto) {
|
||||
@@ -47,6 +55,38 @@ export class AdminPartnersController {
|
||||
update(@Param('id') id: string, @Body() dto: UpdatePartnerDto) {
|
||||
return this.service.updatePartner(BigInt(id), dto);
|
||||
}
|
||||
|
||||
@Get(':id/assoc')
|
||||
assocSummary(@Param('id') id: string) {
|
||||
return this.assoc.getSummary(BigInt(id));
|
||||
}
|
||||
|
||||
@Get(':id/assoc/users')
|
||||
assocUsers(
|
||||
@Param('id') id: string,
|
||||
@Query('page') page = '1',
|
||||
@Query('pageSize') pageSize = '20',
|
||||
) {
|
||||
return this.assoc.listUsers(BigInt(id), Number(page) || 1, Number(pageSize) || 20);
|
||||
}
|
||||
|
||||
@Post(':id/assoc/qrcode')
|
||||
regenQrcode(@Param('id') id: string) {
|
||||
return this.assoc.ensureQrcode(BigInt(id), true);
|
||||
}
|
||||
|
||||
@Post(':id/assoc/users/:userId/unbind')
|
||||
@UseGuards(HqPermissionGuard)
|
||||
@RequireHqPermissions('users_partner_assoc')
|
||||
@HqOperation({
|
||||
action: HqOperationAction.USER_ASSOC_UPDATE,
|
||||
refType: 'USER',
|
||||
refIdParam: 'userId',
|
||||
includeBody: true,
|
||||
})
|
||||
unbindUser(@Param('userId') userId: string) {
|
||||
return this.assoc.unbindUser(BigInt(userId));
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('admin/partner-accounts')
|
||||
|
||||
@@ -4,6 +4,7 @@ import { resolveMaxPartnerCommissionRate, validatePartnerCommissionRates } from
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
||||
import { PartnerCityService } from '../city-scope/partner-city.service';
|
||||
import { PartnerAssocService } from '../store/partner-assoc.service';
|
||||
import type { AdminPartnerAccountsQueryDto, AdminPartnersQueryDto } from './dto/admin-query.dto';
|
||||
import type {
|
||||
CreatePartnerAccountDto,
|
||||
@@ -26,11 +27,24 @@ function assertPartnerCommissionRates(
|
||||
if (!check.ok) throw new BadRequestException(check.message);
|
||||
}
|
||||
|
||||
function toCommissionDecimal(rate: number): Prisma.Decimal {
|
||||
return new Prisma.Decimal(Number(rate).toFixed(4));
|
||||
}
|
||||
|
||||
/** 选填字符串:undefined 不改;null / 空白写成 null */
|
||||
function trimOptionalText(value: string | null | undefined): string | null | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
if (value == null) return null;
|
||||
const t = String(value).trim();
|
||||
return t.length ? t : null;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminPartnersService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly partnerCityService: PartnerCityService,
|
||||
private readonly partnerAssocService: PartnerAssocService,
|
||||
) {}
|
||||
|
||||
async listPartners(query: AdminPartnersQueryDto) {
|
||||
@@ -64,7 +78,7 @@ export class AdminPartnersService {
|
||||
},
|
||||
orderBy: { createdAt: 'asc' },
|
||||
},
|
||||
_count: { select: { stores: true, children: true } },
|
||||
_count: { select: { stores: true, children: true, assocUsers: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.partnerAccount.count({ where }),
|
||||
@@ -88,6 +102,7 @@ export class AdminPartnersService {
|
||||
managedWarehouseId: p.managedWarehouseId?.toString() ?? null,
|
||||
managedWarehouseName: p.managedWarehouse?.name ?? null,
|
||||
storeCount: p._count.stores,
|
||||
assocUserCount: p._count.assocUsers,
|
||||
accountCount: p._count.children + 1,
|
||||
children: p.children.map((c) => ({
|
||||
id: c.id.toString(),
|
||||
@@ -125,7 +140,7 @@ export class AdminPartnersService {
|
||||
orderBy: { createdAt: 'asc' },
|
||||
},
|
||||
stores: { select: { id: true, name: true, status: true }, take: 10, orderBy: { createdAt: 'desc' } },
|
||||
_count: { select: { stores: true, children: true } },
|
||||
_count: { select: { stores: true, children: true, assocUsers: true } },
|
||||
},
|
||||
});
|
||||
if (!account) throw new NotFoundException('开城合伙人不存在');
|
||||
@@ -140,6 +155,8 @@ export class AdminPartnersService {
|
||||
bankAccountNo: account.bankAccountNo,
|
||||
bankBranch: account.bankBranch,
|
||||
managedWarehouseName: account.managedWarehouse?.name ?? null,
|
||||
storeCount: account._count.stores,
|
||||
assocUserCount: account._count.assocUsers,
|
||||
accountCount: account._count.children + 1,
|
||||
maxPartnerCommissionRate:
|
||||
account.city?.maxPartnerCommissionRate != null
|
||||
@@ -189,8 +206,8 @@ export class AdminPartnersService {
|
||||
scopeType: dto.scopeType as CityPartnerScopeType,
|
||||
districtCodes:
|
||||
dto.scopeType === 'DISTRICT' ? (dto.districtCodes ?? []) : Prisma.JsonNull,
|
||||
orderCommissionRate: orderCommissionRate,
|
||||
redeemCommissionRate: redeemCommissionRate,
|
||||
orderCommissionRate: toCommissionDecimal(orderCommissionRate),
|
||||
redeemCommissionRate: toCommissionDecimal(redeemCommissionRate),
|
||||
bindingStatus: (dto.bindingStatus ?? 'ACTIVE') as CityPartnerStatus,
|
||||
companyName: dto.companyName?.trim() || null,
|
||||
address: dto.address?.trim() || null,
|
||||
@@ -204,7 +221,13 @@ export class AdminPartnersService {
|
||||
include: { city: { select: { id: true, code: true, name: true } } },
|
||||
});
|
||||
|
||||
return serializeBigInt(this.partnerCityService.toDto(account));
|
||||
const dtoOut = serializeBigInt(this.partnerCityService.toDto(account));
|
||||
try {
|
||||
await this.partnerAssocService.ensureQrcode(account.id);
|
||||
} catch {
|
||||
/* 补码失败不挡创建 */
|
||||
}
|
||||
return dtoOut;
|
||||
}
|
||||
|
||||
async updatePartner(id: bigint, dto: UpdatePartnerDto) {
|
||||
@@ -221,17 +244,26 @@ export class AdminPartnersService {
|
||||
? dto.districtCodes ?? this.partnerCityService.parseDistrictCodes(existing.districtCodes)
|
||||
: null;
|
||||
|
||||
await this.partnerCityService.validatePrimaryBinding(
|
||||
cityId,
|
||||
{
|
||||
partnerAccountId: id.toString(),
|
||||
scopeType,
|
||||
districtCodes: districtCodes ?? undefined,
|
||||
},
|
||||
id,
|
||||
);
|
||||
const existingDistricts = this.partnerCityService.parseDistrictCodes(existing.districtCodes);
|
||||
const nextDistrictsSorted = [...(districtCodes ?? [])].sort();
|
||||
const existingDistrictsSorted = [...(existingDistricts ?? [])].sort();
|
||||
const scopeActuallyChanged =
|
||||
(dto.scopeType !== undefined && dto.scopeType !== existing.scopeType) ||
|
||||
(dto.districtCodes !== undefined &&
|
||||
JSON.stringify(nextDistrictsSorted) !== JSON.stringify(existingDistrictsSorted));
|
||||
if (scopeActuallyChanged) {
|
||||
await this.partnerCityService.validatePrimaryBinding(
|
||||
cityId,
|
||||
{
|
||||
partnerAccountId: id.toString(),
|
||||
scopeType,
|
||||
districtCodes: districtCodes ?? undefined,
|
||||
},
|
||||
id,
|
||||
);
|
||||
}
|
||||
|
||||
if (dto.phone !== undefined) {
|
||||
if (dto.phone !== undefined && dto.phone != null) {
|
||||
const phone = dto.phone.trim();
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||||
throw new BadRequestException('请输入正确的登录手机号');
|
||||
@@ -254,18 +286,22 @@ export class AdminPartnersService {
|
||||
assertPartnerCommissionRates(city, orderCommissionRate, redeemCommissionRate);
|
||||
}
|
||||
|
||||
const phoneChanged =
|
||||
dto.phone !== undefined && dto.phone.trim() !== existing.phone;
|
||||
const nextPhone = dto.phone != null ? dto.phone.trim() : undefined;
|
||||
const phoneChanged = nextPhone !== undefined && nextPhone !== existing.phone;
|
||||
const nextName = dto.name != null ? dto.name.trim() : undefined;
|
||||
const nextCompanyName = trimOptionalText(dto.companyName);
|
||||
const nextAddress = trimOptionalText(dto.address);
|
||||
const nextContactPhone = trimOptionalText(dto.contactPhone);
|
||||
|
||||
const account = await this.prisma.partnerAccount.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...(dto.name !== undefined ? { name: dto.name.trim() } : {}),
|
||||
...(dto.phone !== undefined ? { phone: dto.phone.trim() } : {}),
|
||||
...(nextName !== undefined ? { name: nextName } : {}),
|
||||
...(nextPhone !== undefined ? { phone: nextPhone } : {}),
|
||||
...(phoneChanged ? { wxOpenId: null, wxUnionId: null } : {}),
|
||||
...(dto.companyName !== undefined ? { companyName: dto.companyName.trim() } : {}),
|
||||
...(dto.address !== undefined ? { address: dto.address.trim() } : {}),
|
||||
...(dto.contactPhone !== undefined ? { contactPhone: dto.contactPhone.trim() } : {}),
|
||||
...(nextCompanyName !== undefined ? { companyName: nextCompanyName } : {}),
|
||||
...(nextAddress !== undefined ? { address: nextAddress } : {}),
|
||||
...(nextContactPhone !== undefined ? { contactPhone: nextContactPhone } : {}),
|
||||
...(dto.scopeType !== undefined ? { scopeType: dto.scopeType as CityPartnerScopeType } : {}),
|
||||
...(dto.scopeType !== undefined || dto.districtCodes !== undefined
|
||||
? {
|
||||
@@ -275,8 +311,12 @@ export class AdminPartnersService {
|
||||
: Prisma.JsonNull,
|
||||
}
|
||||
: {}),
|
||||
...(dto.orderCommissionRate !== undefined ? { orderCommissionRate: dto.orderCommissionRate } : {}),
|
||||
...(dto.redeemCommissionRate !== undefined ? { redeemCommissionRate: dto.redeemCommissionRate } : {}),
|
||||
...(dto.orderCommissionRate !== undefined
|
||||
? { orderCommissionRate: toCommissionDecimal(dto.orderCommissionRate) }
|
||||
: {}),
|
||||
...(dto.redeemCommissionRate !== undefined
|
||||
? { redeemCommissionRate: toCommissionDecimal(dto.redeemCommissionRate) }
|
||||
: {}),
|
||||
...(dto.bindingStatus !== undefined ? { bindingStatus: dto.bindingStatus as CityPartnerStatus } : {}),
|
||||
...(dto.contractNo !== undefined ? { contractNo: dto.contractNo } : {}),
|
||||
...(dto.bankAccountName !== undefined ? { bankAccountName: dto.bankAccountName } : {}),
|
||||
|
||||
@@ -54,6 +54,9 @@ export class AdminStoreRatingsService {
|
||||
id: r.id,
|
||||
serviceScore: r.serviceScore,
|
||||
envScore: r.envScore,
|
||||
comment: r.comment,
|
||||
tags: r.tags,
|
||||
imageUrls: r.imageUrls,
|
||||
createdAt: r.createdAt,
|
||||
store: r.store,
|
||||
redeemRecordId: r.redeemRecordId,
|
||||
|
||||
@@ -8,7 +8,12 @@ import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
|
||||
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
|
||||
import { AdminUsersService } from './admin-users.service';
|
||||
import { AdminUsersQueryDto } from './dto/admin-query.dto';
|
||||
import { BatchDeleteUsersConfirmDto, BatchDeleteUsersDto, UpdateAdminUserDto } from './dto/admin-mutate.dto';
|
||||
import {
|
||||
BatchDeleteUsersConfirmDto,
|
||||
BatchDeleteUsersDto,
|
||||
UpdateAdminUserDto,
|
||||
UpdateUserAssocDto,
|
||||
} from './dto/admin-mutate.dto';
|
||||
|
||||
@Controller('admin/users')
|
||||
@UseGuards(HqAuthGuard)
|
||||
@@ -48,6 +53,19 @@ export class AdminUsersController {
|
||||
return this.usersService.detail(BigInt(id));
|
||||
}
|
||||
|
||||
@Put(':id/assoc')
|
||||
@UseGuards(HqPermissionGuard)
|
||||
@RequireHqPermissions('users_partner_assoc')
|
||||
@HqOperation({
|
||||
action: HqOperationAction.USER_ASSOC_UPDATE,
|
||||
refType: 'USER',
|
||||
refIdParam: 'id',
|
||||
includeBody: true,
|
||||
})
|
||||
setAssoc(@Param('id') id: string, @Body() dto: UpdateUserAssocDto) {
|
||||
return this.usersService.setAssoc(BigInt(id), dto.partnerAccountId);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@HqOperation({
|
||||
action: HqOperationAction.USER_UPDATE,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
||||
import { PartnerAssocService } from '../store/partner-assoc.service';
|
||||
import type { AdminUsersQueryDto } from './dto/admin-query.dto';
|
||||
|
||||
const FINISHED_ORDER_STATUSES = ['COMPLETED', 'CANCELLED', 'REFUNDED'] as const;
|
||||
@@ -28,6 +29,21 @@ function toAmount(v: Prisma.Decimal | number | null | undefined) {
|
||||
return v == null ? 0 : Number(v);
|
||||
}
|
||||
|
||||
function mapAssocPartner(p?: {
|
||||
id: bigint;
|
||||
name: string;
|
||||
companyName: string | null;
|
||||
phone: string | null;
|
||||
} | null) {
|
||||
if (!p) return null;
|
||||
return {
|
||||
id: p.id.toString(),
|
||||
name: p.name,
|
||||
companyName: p.companyName,
|
||||
phone: p.phone,
|
||||
};
|
||||
}
|
||||
|
||||
function mapAdminUserRow(u: {
|
||||
id: bigint;
|
||||
userNo: string;
|
||||
@@ -46,6 +62,12 @@ function mapAdminUserRow(u: {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
_count: { orders: number };
|
||||
assocPartner?: {
|
||||
id: bigint;
|
||||
name: string;
|
||||
companyName: string | null;
|
||||
phone: string | null;
|
||||
} | null;
|
||||
}, benefit: UserBenefitStat = EMPTY_BENEFIT_STAT) {
|
||||
return {
|
||||
id: u.id,
|
||||
@@ -66,13 +88,17 @@ function mapAdminUserRow(u: {
|
||||
createdAt: u.createdAt,
|
||||
updatedAt: u.updatedAt,
|
||||
orderCount: u._count.orders,
|
||||
assocPartner: mapAssocPartner(u.assocPartner),
|
||||
...benefit,
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminUsersService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly partnerAssocService: PartnerAssocService,
|
||||
) {}
|
||||
|
||||
async list(query: AdminUsersQueryDto) {
|
||||
const page = query.page ?? 1;
|
||||
@@ -88,6 +114,30 @@ export class AdminUsersService {
|
||||
}
|
||||
where.id = BigInt(userId);
|
||||
}
|
||||
const keyword = query.keyword?.trim();
|
||||
if (keyword) {
|
||||
const keywordOr: Prisma.UserWhereInput[] = [
|
||||
{ userNo: { contains: keyword } },
|
||||
{ nickname: { contains: keyword } },
|
||||
{ hqRemark: { contains: keyword } },
|
||||
{ phone: { contains: keyword } },
|
||||
];
|
||||
if (/^\d+$/.test(keyword)) {
|
||||
keywordOr.push({ id: BigInt(keyword) });
|
||||
}
|
||||
where.OR = keywordOr;
|
||||
}
|
||||
const assocPartnerAccountId = query.assocPartnerAccountId?.trim();
|
||||
if (assocPartnerAccountId === 'none') {
|
||||
where.assocPartnerAccountId = null;
|
||||
} else if (assocPartnerAccountId === 'any') {
|
||||
where.assocPartnerAccountId = { not: null };
|
||||
} else if (assocPartnerAccountId) {
|
||||
if (!/^\d+$/.test(assocPartnerAccountId)) {
|
||||
return serializeBigInt({ items: [], total: 0, page, pageSize });
|
||||
}
|
||||
where.assocPartnerAccountId = BigInt(assocPartnerAccountId);
|
||||
}
|
||||
if (query.deviceKey) where.deviceKey = query.deviceKey;
|
||||
if (query.status !== undefined) where.status = query.status;
|
||||
if (query.phoneVerified === '1') where.phoneVerifiedAt = { not: null };
|
||||
@@ -117,6 +167,7 @@ export class AdminUsersService {
|
||||
isTest: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
assocPartner: { select: { id: true, name: true, companyName: true, phone: true } },
|
||||
_count: { select: { orders: true } },
|
||||
},
|
||||
}),
|
||||
@@ -159,6 +210,7 @@ export class AdminUsersService {
|
||||
where: { id },
|
||||
include: {
|
||||
cityPreference: true,
|
||||
assocPartner: { select: { id: true, name: true, companyName: true, phone: true } },
|
||||
mergedInto: { select: { id: true, userNo: true, phone: true, nickname: true } },
|
||||
orders: {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
@@ -186,18 +238,33 @@ export class AdminUsersService {
|
||||
|
||||
const benefitMap = await this.loadBenefitStats([user.id]);
|
||||
|
||||
const { assocPartner, ...userRest } = user;
|
||||
return serializeBigInt({
|
||||
...user,
|
||||
...userRest,
|
||||
...(benefitMap.get(user.id.toString()) ?? EMPTY_BENEFIT_STAT),
|
||||
wechatVerified: !!user.wxOpenId,
|
||||
mergedFromCount: user._count.mergedFrom,
|
||||
orderCount: user._count.orders,
|
||||
addressCount: user._count.addresses,
|
||||
sourcePromo,
|
||||
assocPartner: assocPartner
|
||||
? {
|
||||
id: assocPartner.id.toString(),
|
||||
name: assocPartner.name,
|
||||
companyName: assocPartner.companyName,
|
||||
phone: assocPartner.phone,
|
||||
}
|
||||
: null,
|
||||
assocBoundAt: user.assocBoundAt?.toISOString() ?? null,
|
||||
_count: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
async setAssoc(id: bigint, partnerAccountId?: string | null) {
|
||||
const raw = partnerAccountId?.trim() || null;
|
||||
return this.partnerAssocService.setUserAssoc(id, raw ? BigInt(raw) : null);
|
||||
}
|
||||
|
||||
async updateUser(id: bigint, dto: { hqRemark: string }) {
|
||||
const user = await this.prisma.user.findUnique({ where: { id }, select: { id: true } });
|
||||
if (!user) throw new NotFoundException('用户不存在');
|
||||
|
||||
@@ -364,11 +364,13 @@ export class CreatePartnerDto {
|
||||
districtCodes?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
orderCommissionRate?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
redeemCommissionRate?: number;
|
||||
@@ -412,16 +414,19 @@ export class UpdatePartnerDto {
|
||||
phone?: string;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateIf((_, v) => v != null)
|
||||
@IsString()
|
||||
companyName?: string;
|
||||
companyName?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateIf((_, v) => v != null)
|
||||
@IsString()
|
||||
address?: string;
|
||||
address?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateIf((_, v) => v != null)
|
||||
@IsString()
|
||||
contactPhone?: string;
|
||||
contactPhone?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['CITY_WIDE', 'DISTRICT'])
|
||||
@@ -433,11 +438,13 @@ export class UpdatePartnerDto {
|
||||
districtCodes?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
orderCommissionRate?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
redeemCommissionRate?: number;
|
||||
@@ -590,11 +597,13 @@ export class BindCityPartnerDto {
|
||||
districtCodes?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
orderCommissionRate?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
redeemCommissionRate?: number;
|
||||
@@ -615,11 +624,13 @@ export class UpdateCityPartnerDto {
|
||||
districtCodes?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
orderCommissionRate?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
redeemCommissionRate?: number;
|
||||
@@ -974,6 +985,13 @@ export class UpdateAdminUserDto {
|
||||
hqRemark: string;
|
||||
}
|
||||
|
||||
export class UpdateUserAssocDto {
|
||||
@IsOptional()
|
||||
@ValidateIf((_, v) => v != null && v !== '')
|
||||
@IsString()
|
||||
partnerAccountId?: string | null;
|
||||
}
|
||||
|
||||
/** HQ 订单发货(目前仅小飞侠 XFX) */
|
||||
export class AdminShipOrderDto {
|
||||
@IsIn(['XFX'])
|
||||
|
||||
@@ -40,6 +40,16 @@ export class PaginationQueryDto {
|
||||
}
|
||||
|
||||
export class AdminUsersQueryDto extends PaginationQueryDto {
|
||||
/** 综合搜索:编号 / 昵称 / 备注 / 手机 / 数字 ID */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
keyword?: string;
|
||||
|
||||
/** 关联合伙人:主账号 ID;`none` 未关联;`any` 已关联(全部) */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
assocPartnerAccountId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phone?: string;
|
||||
@@ -136,6 +146,11 @@ export class AdminOrdersQueryDto extends PaginationQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
promoCodeId?: string;
|
||||
|
||||
/** 本单佣金关联合伙人:主账号 ID,或 `none` 表示无快照 */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
assocPartnerAccountId?: string;
|
||||
}
|
||||
|
||||
/** HQ 订单导出(筛选 + 勾选范围) */
|
||||
@@ -206,6 +221,10 @@ export class AdminOrdersExportDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
promoCodeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
assocPartnerAccountId?: string;
|
||||
}
|
||||
|
||||
/** 概览页用户/订单 ECharts 聚合筛选 */
|
||||
|
||||
@@ -92,6 +92,10 @@ export class HqProxyOrderCreateDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
promoCodeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
assocPartnerAccountId?: string;
|
||||
}
|
||||
|
||||
export class HqProxyOrderPayDto {
|
||||
|
||||
Reference in New Issue
Block a user