import { ConflictException, Injectable, NotFoundException, } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { AuditLogService } from '../audit/audit-log.service'; import { Apiary, ApiaryStatus } from '../../generated/prisma'; import { CreateApiaryDto } from './dto/create-apiary.dto'; import { UpdateApiaryDto } from './dto/update-apiary.dto'; export interface ApiaryFilter { status?: ApiaryStatus; location?: string; } @Injectable() export class ApiariesService { constructor( private readonly prisma: PrismaService, private readonly auditLog: AuditLogService, ) {} async findAll(filter: ApiaryFilter = {}): Promise { return this.prisma.apiary.findMany({ where: { status: filter.status, location: filter.location ? { contains: filter.location, mode: 'insensitive' } : undefined, }, orderBy: { name: 'asc' }, }); } async findById(id: string): Promise { const apiary = await this.prisma.apiary.findUnique({ where: { id } }); if (!apiary) { throw new NotFoundException('Apiary not found'); } return apiary; } async create(dto: CreateApiaryDto, actingUserId: string): Promise { const apiary = await this.prisma.apiary.create({ data: { ...dto } }); await this.auditLog.record({ entityType: 'Apiary', entityId: apiary.id, action: 'CREATE', userId: actingUserId, afterData: apiary, }); return apiary; } async update( id: string, dto: UpdateApiaryDto, actingUserId: string, ): Promise { const before = await this.findById(id); const apiary = await this.prisma.apiary.update({ where: { id }, data: { ...dto }, }); await this.auditLog.record({ entityType: 'Apiary', entityId: id, action: 'UPDATE', userId: actingUserId, beforeData: before, afterData: apiary, }); return apiary; } async deactivate(id: string, actingUserId: string): Promise { const before = await this.findById(id); // Colony.apiaryId is a plain FK, not enforced against apiary status - // without this guard a colony can silently keep pointing at an // inactive apiary (still shows ACTIVE, still countable, but its // "location" has vanished from the active apiary list). Move the // colonies elsewhere or dissolve them first instead. const activeColonyCount = await this.prisma.colony.count({ where: { apiaryId: id, status: 'ACTIVE' }, }); if (activeColonyCount > 0) { throw new ConflictException( `Der Bienenstand hat noch ${activeColonyCount} aktive(s) Volk/Völker - zuerst verlegen oder auflösen`, ); } const apiary = await this.prisma.apiary.update({ where: { id }, data: { status: 'INACTIVE', deactivatedAt: new Date() }, }); await this.auditLog.record({ entityType: 'Apiary', entityId: id, action: 'DEACTIVATE', userId: actingUserId, beforeData: before, afterData: apiary, }); return apiary; } async reactivate(id: string, actingUserId: string): Promise { const before = await this.findById(id); if (before.status === 'ACTIVE') { throw new ConflictException('Apiary is already active'); } const apiary = await this.prisma.apiary.update({ where: { id }, data: { status: 'ACTIVE', deactivatedAt: null }, }); await this.auditLog.record({ entityType: 'Apiary', entityId: id, action: 'UPDATE', userId: actingUserId, beforeData: before, afterData: apiary, }); return apiary; } }