kindergarten/reading-platform-backend/src/modules/course-package/course-package.service.ts

373 lines
8.4 KiB
TypeScript
Raw Normal View History

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