feat: add CashboxExportService.exportForUser
This commit is contained in:
@@ -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-');
|
||||
});
|
||||
});
|
||||
@@ -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<Team>,
|
||||
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`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
Reference in New Issue
Block a user