Files
ftb-project-management/apps/server/src/modules/requirement/requirement.service.ts
2026-07-03 10:23:18 +08:00

91 lines
3.0 KiB
TypeScript

import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { RequirementStatus } from '@ftb/shared';
import { CreateRequirementDto } from './dto/create-requirement.dto';
import { UpdateRequirementDto } from './dto/update-requirement.dto';
const VALID_TRANSITIONS: Record<string, string[]> = {
[RequirementStatus.DRAFT]: [RequirementStatus.REVIEWING],
[RequirementStatus.REVIEWING]: [RequirementStatus.APPROVED, RequirementStatus.REJECTED],
[RequirementStatus.APPROVED]: [RequirementStatus.DELIVERED],
[RequirementStatus.REJECTED]: [RequirementStatus.DRAFT],
[RequirementStatus.DELIVERED]: [],
};
function createFallbackRequirementCode() {
return `REQ-${Date.now().toString(36).toUpperCase()}-${Math.random()
.toString(36)
.slice(2, 6)
.toUpperCase()}`;
}
@Injectable()
export class RequirementService {
constructor(private prisma: PrismaService) {}
create(productId: string, dto: CreateRequirementDto) {
return this.prisma.requirement.create({
data: {
productId,
code: dto.code?.trim() || createFallbackRequirementCode(),
title: dto.title,
description: dto.description || '',
priority: dto.priority ?? 0,
creatorId: dto.creatorId,
},
});
}
findAll(productId: string, status?: string) {
return this.prisma.requirement.findMany({
where: {
productId,
...(status ? { status } : {}),
},
orderBy: { createdAt: 'desc' },
include: { creator: { select: { id: true, name: true } } },
});
}
async findOne(productId: string, id: string) {
const req = await this.prisma.requirement.findFirst({
where: { id, productId },
include: { creator: { select: { id: true, name: true } } },
});
if (!req) throw new NotFoundException('需求不存在');
return req;
}
async update(productId: string, id: string, dto: UpdateRequirementDto) {
await this.findOne(productId, id);
return this.prisma.requirement.update({
where: { id_productId: { id, productId } },
data: {
...(dto.code !== undefined && { code: dto.code }),
...(dto.title !== undefined && { title: dto.title }),
...(dto.description !== undefined && { description: dto.description }),
...(dto.priority !== undefined && { priority: dto.priority }),
},
});
}
async updateStatus(productId: string, id: string, newStatus: string) {
const req = await this.findOne(productId, id);
const allowed = VALID_TRANSITIONS[req.status] || [];
if (!allowed.includes(newStatus)) {
throw new BadRequestException(
`无法从 "${req.status}" 转换到 "${newStatus}"`,
);
}
return this.prisma.requirement.update({
where: { id_productId: { id, productId } },
data: { status: newStatus },
});
}
async remove(productId: string, id: string) {
await this.findOne(productId, id);
return this.prisma.requirement.delete({ where: { id_productId: { id, productId } } });
}
}