118 lines
4.1 KiB
TypeScript
118 lines
4.1 KiB
TypeScript
import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
|
||
import { Prisma } from '@prisma/client';
|
||
import { type StandardCode } from '@airpredict/shared';
|
||
import { PrismaService } from '../prisma/prisma.service';
|
||
import { PredictionService } from '../prediction/prediction.service';
|
||
import { CreateSpaceDto, UpdateSpaceDto, PrecalcDto } from './dto/space.dto';
|
||
|
||
@Injectable()
|
||
export class SpacesService {
|
||
constructor(
|
||
private prisma: PrismaService,
|
||
private prediction: PredictionService,
|
||
) {}
|
||
|
||
/** 预计算:不落库,直接返回浓度+贡献率 */
|
||
async precalc(dto: PrecalcDto) {
|
||
return this.prediction.computeSpace({
|
||
volume: dto.volume,
|
||
temperature: dto.temperature,
|
||
humidity: dto.humidity,
|
||
ventilationRate: dto.ventilationRate,
|
||
standard: dto.standard as StandardCode,
|
||
materials: dto.materials.map((m) => ({ materialId: m.materialId, usageAmount: m.usageAmount })),
|
||
});
|
||
}
|
||
|
||
async create(orgId: string, dto: CreateSpaceDto) {
|
||
await this.assertProjectOwned(orgId, dto.projectId);
|
||
const space = await this.prisma.space.create({
|
||
data: {
|
||
id: this.genId('S'),
|
||
projectId: dto.projectId,
|
||
name: dto.name,
|
||
type: dto.type,
|
||
layout: dto.layout ?? 'uniform',
|
||
height: dto.height,
|
||
area: dto.area,
|
||
volume: dto.volume,
|
||
temperature: dto.temperature,
|
||
humidity: dto.humidity,
|
||
ventilationRate: dto.ventilationRate,
|
||
standard: dto.standard,
|
||
materials: {
|
||
create: dto.materials.map((m) => ({
|
||
materialId: m.materialId,
|
||
usageUnit: m.usageUnit ?? 'm²',
|
||
usageAmount: m.usageAmount,
|
||
})),
|
||
},
|
||
},
|
||
include: { materials: { include: { material: true } } },
|
||
});
|
||
await this.touchProject(dto.projectId);
|
||
return space;
|
||
}
|
||
|
||
async update(orgId: string, id: string, dto: UpdateSpaceDto) {
|
||
const space = await this.prisma.space.findUnique({ where: { id }, include: { project: true } });
|
||
if (!space) throw new NotFoundException('空间不存在');
|
||
if (space.project.ownerOrgId !== orgId) throw new ForbiddenException('无权操作');
|
||
|
||
const { materials, ...rest } = dto;
|
||
const updated = await this.prisma.space.update({
|
||
where: { id },
|
||
data: {
|
||
...rest,
|
||
// 配置已改,predictedConc 失效
|
||
predictedConc: Prisma.JsonNull,
|
||
...(materials
|
||
? {
|
||
materials: {
|
||
deleteMany: {},
|
||
create: materials.map((m) => ({
|
||
materialId: m.materialId,
|
||
usageUnit: m.usageUnit ?? 'm²',
|
||
usageAmount: m.usageAmount,
|
||
})),
|
||
},
|
||
}
|
||
: {}),
|
||
},
|
||
include: { materials: { include: { material: true } } },
|
||
});
|
||
await this.touchProject(space.projectId);
|
||
return updated;
|
||
}
|
||
|
||
async remove(orgId: string, id: string) {
|
||
const space = await this.prisma.space.findUnique({ where: { id }, include: { project: true } });
|
||
if (!space) throw new NotFoundException('空间不存在');
|
||
if (space.project.ownerOrgId !== orgId) throw new ForbiddenException('无权操作');
|
||
await this.prisma.space.delete({ where: { id } });
|
||
await this.touchProject(space.projectId);
|
||
return { success: true };
|
||
}
|
||
|
||
private async assertProjectOwned(orgId: string, projectId: string) {
|
||
const p = await this.prisma.project.findUnique({ where: { id: projectId } });
|
||
if (!p) throw new NotFoundException('项目不存在');
|
||
if (p.ownerOrgId !== orgId) throw new ForbiddenException('无权操作');
|
||
}
|
||
|
||
/** 配置变更后,项目回到未生成报告状态 */
|
||
private async touchProject(projectId: string) {
|
||
await this.prisma.project.update({
|
||
where: { id: projectId },
|
||
data: { status: 'configuring', rating: null, reportGeneratedAt: null },
|
||
});
|
||
}
|
||
|
||
private genId(prefix: string): string {
|
||
let s = '';
|
||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||
for (let i = 0; i < 8; i++) s += chars[Math.floor(Math.random() * chars.length)];
|
||
return prefix + s;
|
||
}
|
||
}
|