diff --git a/myteamwallet_backend/src/cashbox-export/cashbox-export.service.spec.ts b/myteamwallet_backend/src/cashbox-export/cashbox-export.service.spec.ts new file mode 100644 index 0000000..26f736e --- /dev/null +++ b/myteamwallet_backend/src/cashbox-export/cashbox-export.service.spec.ts @@ -0,0 +1,60 @@ +import { NotFoundException } from '@nestjs/common'; +import { TeamRolesEnum } from '../team-roles/team-roles.enum'; +import { CashboxExportService } from './cashbox-export.service'; + +describe('CashboxExportService', () => { + const teamRepository = { findOne: jest.fn() }; + const access = { assertAtLeast: jest.fn() }; + let service: CashboxExportService; + + const team = { + id: 5, + name: 'Team A', + alias: 'team-a', + transactions: [ + { date: '2026-08-05T00:00:00.000Z', amount: 10, note: 'Sponsoring', type: { name: 'credit' } }, + ], + players: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + teamRepository.findOne.mockResolvedValue(team); + service = new CashboxExportService(teamRepository as any, access as any); + }); + + it('checks permission before loading data', async () => { + await service.exportForUser(5, 42, '2026-08-01', '2026-08-31', 'csv'); + + expect(access.assertAtLeast).toHaveBeenCalledWith( + 42, + 5, + 'transaction_create_min_role', + TeamRolesEnum.scnd_treasurer, + ); + }); + + it('throws NotFoundException for an unknown team', async () => { + teamRepository.findOne.mockResolvedValue(null); + + await expect( + service.exportForUser(999, 42, '2026-08-01', '2026-08-31', 'csv'), + ).rejects.toBeInstanceOf(NotFoundException); + }); + + it('builds a CSV buffer with the correct content type and filename', async () => { + const result = await service.exportForUser(5, 42, '2026-08-01', '2026-08-31', 'csv'); + + expect(result.contentType).toBe('text/csv; charset=utf-8'); + expect(result.filename).toBe('kassenbuch_team-a_2026-08-01_2026-08-31.csv'); + expect(result.buffer.toString('utf-8')).toContain('Sponsoring'); + }); + + it('builds a PDF buffer with the correct content type and filename', async () => { + const result = await service.exportForUser(5, 42, '2026-08-01', '2026-08-31', 'pdf'); + + expect(result.contentType).toBe('application/pdf'); + expect(result.filename).toBe('kassenbuch_team-a_2026-08-01_2026-08-31.pdf'); + expect(result.buffer.subarray(0, 5).toString('utf-8')).toBe('%PDF-'); + }); +}); diff --git a/myteamwallet_backend/src/cashbox-export/cashbox-export.service.ts b/myteamwallet_backend/src/cashbox-export/cashbox-export.service.ts new file mode 100644 index 0000000..d377acb --- /dev/null +++ b/myteamwallet_backend/src/cashbox-export/cashbox-export.service.ts @@ -0,0 +1,51 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { TeamRolesEnum } from 'src/team-roles/team-roles.enum'; +import { Team } from 'src/teams/entities/team.entity'; +import { TeamAccessService } from 'src/teams/team-access.service'; +import { Repository } from 'typeorm'; +import { buildCsv, buildPdf, buildRows } from './cashbox-export.utils'; + +@Injectable() +export class CashboxExportService { + constructor( + @InjectRepository(Team) + private readonly teamRepository: Repository, + private readonly access: TeamAccessService, + ) {} + + async exportForUser( + teamId: number, + userId: number, + from: string, + to: string, + format: 'csv' | 'pdf', + ): Promise<{ buffer: Buffer; contentType: string; filename: string }> { + await this.access.assertAtLeast( + userId, + teamId, + 'transaction_create_min_role', + TeamRolesEnum.scnd_treasurer, + ); + const team = await this.teamRepository.findOne({ + where: { id: teamId }, + relations: ['players', 'players.transactions', 'transactions'], + }); + if (!team) throw new NotFoundException('Team nicht gefunden.'); + + const rows = buildRows(team, from, to); + + if (format === 'csv') { + return { + buffer: Buffer.from(buildCsv(rows), 'utf-8'), + contentType: 'text/csv; charset=utf-8', + filename: `kassenbuch_${team.alias}_${from}_${to}.csv`, + }; + } + return { + buffer: await buildPdf(team, rows, from, to), + contentType: 'application/pdf', + filename: `kassenbuch_${team.alias}_${from}_${to}.pdf`, + }; + } +} diff --git a/myteamwallet_backend/src/cashbox-export/dto/cashbox-export-query.dto.ts b/myteamwallet_backend/src/cashbox-export/dto/cashbox-export-query.dto.ts new file mode 100644 index 0000000..4fadbd6 --- /dev/null +++ b/myteamwallet_backend/src/cashbox-export/dto/cashbox-export-query.dto.ts @@ -0,0 +1,12 @@ +import { IsDateString, IsIn } from 'class-validator'; + +export class CashboxExportQueryDto { + @IsDateString() + from: string; + + @IsDateString() + to: string; + + @IsIn(['csv', 'pdf']) + format: 'csv' | 'pdf'; +}