代下单功能

This commit is contained in:
2026-07-12 11:40:04 +08:00
parent d949301bc1
commit de01d36cdb
46 changed files with 2657 additions and 253 deletions
@@ -1,36 +0,0 @@
import { Body, Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { AdminPromoCodesService } from './admin-promo-codes.service';
import { AdminPromoCodesQueryDto } from './dto/admin-query.dto';
import { CreatePromoCodeDto, UpdatePromoCodeStatusDto } from './dto/admin-mutate.dto';
@Controller('admin/promo-codes')
@UseGuards(HqAuthGuard)
export class AdminPromoCodesController {
constructor(private readonly service: AdminPromoCodesService) {}
@Get()
list(@Query() query: AdminPromoCodesQueryDto) {
return this.service.list(query);
}
@Get(':id/stats')
stats(@Param('id') id: string) {
return this.service.stats(BigInt(id));
}
@Get(':id')
detail(@Param('id') id: string) {
return this.service.detail(BigInt(id));
}
@Post()
create(@Body() dto: CreatePromoCodeDto) {
return this.service.create(dto);
}
@Put(':id/status')
updateStatus(@Param('id') id: string, @Body() dto: UpdatePromoCodeStatusDto) {
return this.service.updateStatus(BigInt(id), dto.status);
}
}
@@ -1,133 +0,0 @@
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { CreatePromoCodeDto } from './dto/admin-mutate.dto';
import { AdminPromoCodesQueryDto } from './dto/admin-query.dto';
function userH5Base(): string {
return (process.env.USER_H5_URL || 'http://localhost:5173/user').replace(/\/$/, '');
}
function buildLandingUrl(code: string): string {
return `${userH5Base()}/?promo=${encodeURIComponent(code)}`;
}
function randomCode(): string {
const n = Math.random().toString(36).slice(2, 8).toUpperCase();
return `DK${n}`;
}
@Injectable()
export class AdminPromoCodesService {
constructor(private readonly prisma: PrismaService) {}
private mapRow(row: {
id: bigint;
code: string;
name: string;
status: string;
scanCount: number;
orderCount: number;
createdAt: Date;
}) {
return serializeBigInt({
id: row.id,
code: row.code,
name: row.name,
status: row.status,
scanCount: row.scanCount,
orderCount: row.orderCount,
landingUrl: buildLandingUrl(row.code),
createdAt: row.createdAt,
});
}
async list(query: AdminPromoCodesQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where: {
status?: 'ACTIVE' | 'DISABLED';
name?: { contains: string };
code?: { contains: string };
} = {};
if (query.status) where.status = query.status as 'ACTIVE' | 'DISABLED';
if (query.name) where.name = { contains: query.name };
if (query.code) where.code = { contains: query.code };
const [items, total] = await Promise.all([
this.prisma.commonPromoCode.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.commonPromoCode.count({ where }),
]);
return serializeBigInt({
items: items.map((r) => this.mapRow(r)),
total,
page,
pageSize,
});
}
async detail(id: bigint) {
const row = await this.prisma.commonPromoCode.findUnique({ where: { id } });
if (!row) throw new NotFoundException('推广码不存在');
const stats = this.statsFromRow(row);
return serializeBigInt({ ...this.mapRow(row), stats });
}
async create(dto: CreatePromoCodeDto) {
let code = dto.code?.trim().toUpperCase();
if (code) {
const exists = await this.prisma.commonPromoCode.findUnique({ where: { code } });
if (exists) throw new BadRequestException('推广码已存在');
} else {
for (let i = 0; i < 5; i++) {
const candidate = randomCode();
const exists = await this.prisma.commonPromoCode.findUnique({ where: { code: candidate } });
if (!exists) {
code = candidate;
break;
}
}
if (!code) throw new BadRequestException('生成推广码失败,请重试');
}
const row = await this.prisma.commonPromoCode.create({
data: {
code,
name: dto.name.trim(),
status: 'ACTIVE',
},
});
return this.mapRow(row);
}
async updateStatus(id: bigint, status: 'ACTIVE' | 'DISABLED') {
const row = await this.prisma.commonPromoCode.update({
where: { id },
data: { status },
});
return this.mapRow(row);
}
async stats(id: bigint) {
const row = await this.prisma.commonPromoCode.findUnique({ where: { id } });
if (!row) throw new NotFoundException('推广码不存在');
return serializeBigInt(this.statsFromRow(row));
}
private statsFromRow(row: { scanCount: number; orderCount: number }) {
const scanCount = row.scanCount;
const orderCount = row.orderCount;
const conversionRate =
scanCount > 0 ? Math.round((orderCount / scanCount) * 1000) / 10 : 0;
return { scanCount, orderCount, conversionRate };
}
}
@@ -16,6 +16,9 @@ function mapAdminUserRow(u: {
wxOpenId: string | null;
nickname: string | null;
status: number;
sourceType: string;
sourceRefId: bigint | null;
sourceLabel: string | null;
createdAt: Date;
updatedAt: Date;
_count: { orders: number };
@@ -31,6 +34,9 @@ function mapAdminUserRow(u: {
wechatVerified: !!u.wxOpenId,
nickname: u.nickname,
status: u.status,
sourceType: u.sourceType,
sourceRefId: u.sourceRefId,
sourceLabel: u.sourceLabel,
createdAt: u.createdAt,
updatedAt: u.updatedAt,
orderCount: u._count.orders,
@@ -69,6 +75,9 @@ export class AdminUsersService {
wxOpenId: true,
nickname: true,
status: true,
sourceType: true,
sourceRefId: true,
sourceLabel: true,
createdAt: true,
updatedAt: true,
_count: { select: { orders: true } },
@@ -107,12 +116,21 @@ export class AdminUsersService {
});
if (!user) throw new NotFoundException('用户不存在');
let sourcePromo: { id: bigint; code: string; name: string } | null = null;
if (user.sourceType === 'PROMO_CODE' && user.sourceRefId) {
sourcePromo = await this.prisma.commonPromoCode.findUnique({
where: { id: user.sourceRefId },
select: { id: true, code: true, name: true },
});
}
return serializeBigInt({
...user,
wechatVerified: !!user.wxOpenId,
mergedFromCount: user._count.mergedFrom,
orderCount: user._count.orders,
addressCount: user._count.addresses,
sourcePromo,
_count: undefined,
});
}
@@ -33,8 +33,6 @@ import { AdminHqLogsController } from './admin-hq-logs.controller';
import { AdminHqLogsService } from './admin-hq-logs.service';
import { AdminTicketsController } from './admin-tickets.controller';
import { AdminTicketsService } from './admin-tickets.service';
import { AdminPromoCodesController } from './admin-promo-codes.controller';
import { AdminPromoCodesService } from './admin-promo-codes.service';
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
import { BenefitModule } from '../benefit/benefit.module';
import { CommonModule } from '../common/common.module';
@@ -79,7 +77,6 @@ import { AdminHqPermissionsService } from './admin-hq-permissions.service';
AdminXiaofeixiaController,
AdminProductDetailTemplatesController,
AdminRedeemDebugController,
AdminPromoCodesController,
AdminWechatBindingsController,
AdminHqPermissionsController,
],
@@ -103,7 +100,6 @@ import { AdminHqPermissionsService } from './admin-hq-permissions.service';
AdminXiaofeixiaService,
AdminProductDetailTemplatesService,
AdminRedeemDebugService,
AdminPromoCodesService,
AdminWechatBindingsService,
AdminHqPermissionsService,
SuperAdminGuard,