feat(product): 实现产品需求管理模块(后端 + 前端)
后端:Product Module(CRUD)、Requirement Module(CRUD + 状态机流转)、PrismaService 前端:产品列表页、产品详情页(含需求池 Tab)、Zustand Store、API 封装 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
81
apps/server/src/modules/requirement/requirement.service.ts
Normal file
81
apps/server/src/modules/requirement/requirement.service.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
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]: [],
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class RequirementService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
create(productId: string, dto: CreateRequirementDto) {
|
||||
return this.prisma.requirement.create({
|
||||
data: {
|
||||
productId,
|
||||
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 },
|
||||
data: {
|
||||
...(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 },
|
||||
data: { status: newStatus },
|
||||
});
|
||||
}
|
||||
|
||||
async remove(productId: string, id: string) {
|
||||
await this.findOne(productId, id);
|
||||
return this.prisma.requirement.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user