import { BadRequestException, Injectable, NotFoundException, } from '@nestjs/common'; import { METER_KINDS, type MeterReadingCreate, type MetersResponse } from '@siop/shared'; import type { AuthenticatedUser } from '../auth/current-user.decorator'; import { PrismaService } from '../prisma/prisma.service'; @Injectable() export class MetersService { constructor(private readonly prisma: PrismaService) {} async list(assetId: string): Promise { await this.assertAsset(assetId); const meters = await this.prisma.meter.findMany({ where: { assetId }, include: { readings: { include: { readBy: true }, orderBy: { createdAt: 'desc' }, take: 10, }, }, }); // Les deux compteurs existent toujours dans la réponse, même vides return { meters: METER_KINDS.map((kind) => { const meter = meters.find((m) => m.kind === kind); return { kind, readings: meter?.readings.map((r) => ({ id: r.id, value: r.value, readBy: r.readBy ? { id: r.readBy.id, displayName: r.readBy.displayName } : null, createdAt: r.createdAt.toISOString(), })) ?? [], }; }), }; } /** Invariant : un compteur ne redescend jamais. */ async addReading( assetId: string, dto: MeterReadingCreate, user: AuthenticatedUser, ): Promise { await this.assertAsset(assetId); const meter = await this.prisma.meter.upsert({ where: { assetId_kind: { assetId, kind: dto.kind } }, update: {}, create: { assetId, kind: dto.kind }, }); const dernier = await this.prisma.meterReading.findFirst({ where: { meterId: meter.id }, orderBy: { createdAt: 'desc' }, }); if (dernier && dto.value <= dernier.value) { throw new BadRequestException( `Relevé refusé : ${dto.value} est inférieur ou égal au précédent (${dernier.value}) — un compteur ne redescend pas`, ); } await this.prisma.meterReading.create({ data: { meterId: meter.id, value: dto.value, readById: user.userId }, }); return this.list(assetId); } private async assertAsset(assetId: string): Promise { const asset = await this.prisma.asset.findUnique({ where: { id: assetId } }); if (!asset) throw new NotFoundException('Appareil inconnu'); } }