airpredict/apps/api/src/spaces/spaces.service.ts

118 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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