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 }; } }