import { Injectable } from '@nestjs/common'; import { PrismaService } from '../../database/prisma.service'; @Injectable() export class CoursePackageService { constructor(private prisma: PrismaService) {} // ==================== 套餐管理 ==================== async findAllPackages(params: { status?: string; page?: number; pageSize?: number; }) { const { status, page = 1, pageSize = 20 } = params; const skip = (page - 1) * pageSize; const where: any = {}; if (status) { where.status = status; } const [items, total] = await Promise.all([ this.prisma.coursePackage.findMany({ where, include: { _count: { select: { courses: true, tenantPackages: true }, }, }, orderBy: { createdAt: 'desc' }, skip, take: pageSize, }), this.prisma.coursePackage.count({ where }), ]); return { items: items.map((pkg) => ({ ...pkg, courseCount: pkg._count.courses, tenantCount: pkg._count.tenantPackages, })), total, page, pageSize, }; } async findOnePackage(id: number) { const pkg = await this.prisma.coursePackage.findUnique({ where: { id }, include: { courses: { include: { course: { select: { id: true, name: true, coverImagePath: true, duration: true, gradeTags: true, }, }, }, orderBy: { sortOrder: 'asc' }, }, }, }); if (!pkg) { throw new Error('套餐不存在'); } return pkg; } async createPackage(data: { name: string; description?: string; price: number; discountPrice?: number; discountType?: string; gradeLevels: string[]; }) { return this.prisma.coursePackage.create({ data: { name: data.name, description: data.description, price: data.price, discountPrice: data.discountPrice, discountType: data.discountType, gradeLevels: JSON.stringify(data.gradeLevels), status: 'DRAFT', }, }); } async updatePackage( id: number, data: { name?: string; description?: string; price?: number; discountPrice?: number; discountType?: string; gradeLevels?: string[]; }, ) { const updateData: any = { ...data }; if (data.gradeLevels) { updateData.gradeLevels = JSON.stringify(data.gradeLevels); } return this.prisma.coursePackage.update({ where: { id }, data: updateData, }); } async deletePackage(id: number) { // 检查是否有租户正在使用 const tenantCount = await this.prisma.tenantPackage.count({ where: { packageId: id, status: 'ACTIVE' }, }); if (tenantCount > 0) { throw new Error(`有 ${tenantCount} 个租户正在使用该套餐,无法删除`); } return this.prisma.coursePackage.delete({ where: { id }, }); } // ==================== 套餐课程管理 ==================== async setPackageCourses( packageId: number, courses: { courseId: number; gradeLevel: string; sortOrder?: number }[], ) { // 删除现有关联 await this.prisma.coursePackageCourse.deleteMany({ where: { packageId }, }); // 创建新关联 if (courses.length > 0) { await this.prisma.coursePackageCourse.createMany({ data: courses.map((c, index) => ({ packageId, courseId: c.courseId, gradeLevel: c.gradeLevel, sortOrder: c.sortOrder ?? index, })), }); } // 更新套餐课程数 await this.prisma.coursePackage.update({ where: { id: packageId }, data: { courseCount: courses.length }, }); return this.findOnePackage(packageId); } async addCourseToPackage( packageId: number, courseId: number, gradeLevel: string, sortOrder?: number, ) { // 检查是否已存在 const existing = await this.prisma.coursePackageCourse.findUnique({ where: { packageId_courseId: { packageId, courseId }, }, }); if (existing) { throw new Error('该课程已在套餐中'); } await this.prisma.coursePackageCourse.create({ data: { packageId, courseId, gradeLevel, sortOrder: sortOrder ?? 0, }, }); // 更新套餐课程数 const count = await this.prisma.coursePackageCourse.count({ where: { packageId }, }); await this.prisma.coursePackage.update({ where: { id: packageId }, data: { courseCount: count }, }); return this.findOnePackage(packageId); } async removeCourseFromPackage(packageId: number, courseId: number) { await this.prisma.coursePackageCourse.delete({ where: { packageId_courseId: { packageId, courseId }, }, }); // 更新套餐课程数 const count = await this.prisma.coursePackageCourse.count({ where: { packageId }, }); await this.prisma.coursePackage.update({ where: { id: packageId }, data: { courseCount: count }, }); return this.findOnePackage(packageId); } // ==================== 套餐状态管理 ==================== async submitPackage(id: number, userId: number) { const pkg = await this.prisma.coursePackage.findUnique({ where: { id }, include: { _count: { select: { courses: true } } }, }); if (!pkg) { throw new Error('套餐不存在'); } if (pkg._count.courses === 0) { throw new Error('套餐必须包含至少一个课程包'); } return this.prisma.coursePackage.update({ where: { id }, data: { status: 'PENDING_REVIEW', submittedAt: new Date(), submittedBy: userId, }, }); } async reviewPackage( id: number, userId: number, approved: boolean, comment?: string, ) { const pkg = await this.prisma.coursePackage.findUnique({ where: { id }, }); if (!pkg) { throw new Error('套餐不存在'); } if (pkg.status !== 'PENDING_REVIEW') { throw new Error('只有待审核状态的套餐可以审核'); } return this.prisma.coursePackage.update({ where: { id }, data: { status: approved ? 'APPROVED' : 'REJECTED', reviewedAt: new Date(), reviewedBy: userId, reviewComment: comment, }, }); } async publishPackage(id: number) { const pkg = await this.prisma.coursePackage.findUnique({ where: { id }, }); if (!pkg) { throw new Error('套餐不存在'); } if (pkg.status !== 'APPROVED') { throw new Error('只有已审核通过的套餐可以发布'); } return this.prisma.coursePackage.update({ where: { id }, data: { status: 'PUBLISHED', publishedAt: new Date(), }, }); } async offlinePackage(id: number) { return this.prisma.coursePackage.update({ where: { id }, data: { status: 'OFFLINE', }, }); } // ==================== 学校端查询 ==================== async findTenantPackages(tenantId: number) { return this.prisma.tenantPackage.findMany({ where: { tenantId, status: 'ACTIVE', }, include: { package: { include: { courses: { include: { course: { select: { id: true, name: true, coverImagePath: true, }, }, }, }, }, }, }, orderBy: { createdAt: 'desc' }, }); } async renewTenantPackage( tenantId: number, packageId: number, endDate: string, pricePaid?: number, ) { const existing = await this.prisma.tenantPackage.findFirst({ where: { tenantId, packageId }, }); if (existing) { return this.prisma.tenantPackage.update({ where: { id: existing.id }, data: { endDate, status: 'ACTIVE', pricePaid: pricePaid ?? existing.pricePaid, }, }); } return this.prisma.tenantPackage.create({ data: { tenantId, packageId, startDate: new Date().toISOString().split('T')[0], endDate, status: 'ACTIVE', pricePaid: pricePaid ?? 0, }, }); } }