import { BadRequestException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { TeamRolesEnum } from 'src/team-roles/team-roles.enum'; import { TeamAccessService } from 'src/teams/team-access.service'; import { Repository } from 'typeorm'; import { RecurringTransactionIntervalEnum } from 'src/recurring-transactions/recurring-transaction-interval.enum'; import { CashboxExportSubscriptionResponseDTO } from './dto/cashbox-export-subscription-response.dto'; import { UpsertCashboxExportSubscriptionDTO } from './dto/upsert-cashbox-export-subscription.dto'; import { CashboxExportSubscription } from './entities/cashbox-export-subscription.entity'; const INTERVAL_MONTHS: Record = { [RecurringTransactionIntervalEnum.monthly]: 1, [RecurringTransactionIntervalEnum.quarterly]: 3, [RecurringTransactionIntervalEnum.yearly]: 12, }; @Injectable() export class CashboxExportSubscriptionService { constructor( @InjectRepository(CashboxExportSubscription) private readonly repository: Repository, private readonly access: TeamAccessService, ) {} async getSubscription( teamId: number, userId: number, ): Promise { await this.assertAccess(userId, teamId); const existing = await this.repository.findOne({ where: { team: { id: teamId } } }); if (!existing) { return { recipients: [], interval: RecurringTransactionIntervalEnum.monthly, active: false, nextRunDate: null, }; } return this.toResponse(existing); } async upsertSubscription( teamId: number, userId: number, dto: UpsertCashboxExportSubscriptionDTO, ): Promise { await this.assertAccess(userId, teamId); if (dto.active && dto.recipients.length === 0) { throw new BadRequestException( 'Ein aktivierter automatischer Versand benötigt mindestens eine Empfängeradresse.', ); } const existing = await this.repository.findOne({ where: { team: { id: teamId } } }); const needsNewSchedule = !existing || (dto.active && (!existing.active || existing.interval !== dto.interval)); const entity = existing ?? this.repository.create({ team: { id: teamId } as any, nextRunDate: null, active: false }); entity.recipients = dto.recipients; entity.interval = dto.interval; entity.active = dto.active; if (needsNewSchedule) { entity.nextRunDate = this.nextBoundary(dto.interval); } const saved = await this.repository.save(entity); return this.toResponse(saved); } private async assertAccess(userId: number, teamId: number): Promise { await this.access.assertAtLeast( userId, teamId, 'transaction_create_min_role', TeamRolesEnum.scnd_treasurer, ); } private nextBoundary(interval: RecurringTransactionIntervalEnum): string { const now = new Date(); const date = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)); date.setUTCMonth(date.getUTCMonth() + INTERVAL_MONTHS[interval]); return date.toISOString(); } private toResponse( entity: CashboxExportSubscription, ): CashboxExportSubscriptionResponseDTO { return { recipients: entity.recipients, interval: entity.interval, active: entity.active, nextRunDate: entity.nextRunDate, }; } }