first commit
This commit is contained in:
188
myteamwallet_backend/src/transactions/transactions.service.ts
Normal file
188
myteamwallet_backend/src/transactions/transactions.service.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { LoggingService } from 'src/database/logging/logging.service';
|
||||
import { Player } from 'src/players/entities/player.entity';
|
||||
import { RoleEnum } from 'src/roles/roles.enum';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
import { CreateTransactionDto } from './dto/create-transaction.dto';
|
||||
import { TransactionType } from './entitites/transaction-type.entity';
|
||||
import { Transaction } from './entitites/transaction.entity';
|
||||
import { TransactionTypeEnum } from './transaction-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class TransactionsService {
|
||||
constructor(
|
||||
@InjectRepository(Transaction)
|
||||
private transactionsRepository: Repository<Transaction>,
|
||||
@InjectRepository(Player)
|
||||
private playersRepository: Repository<Player>,
|
||||
@InjectRepository(TransactionType)
|
||||
private transactionTypesRepository: Repository<TransactionType>,
|
||||
@InjectRepository(User)
|
||||
private usersRepository: Repository<User>,
|
||||
private logger: LoggingService,
|
||||
) {}
|
||||
|
||||
async createTransactions(data: CreateTransactionDto[], userId: string) {
|
||||
const res = [];
|
||||
for (const d of data) {
|
||||
const r = await this.create(d, userId);
|
||||
res.push(r);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
async create(data: CreateTransactionDto, userId: string) {
|
||||
const player = await this.playersRepository.findOne({
|
||||
where: { id: data.playerId },
|
||||
});
|
||||
|
||||
const creatingUser = await this.usersRepository.findOne({
|
||||
where: {
|
||||
id: Number(userId),
|
||||
},
|
||||
relations: ['players'],
|
||||
});
|
||||
|
||||
const transactionType = await this.transactionTypesRepository.findOne({
|
||||
where: { id: TransactionTypeEnum[TransactionTypeEnum[data.type]] },
|
||||
});
|
||||
|
||||
if (creatingUser.role.id != RoleEnum.admin) {
|
||||
if (
|
||||
!player ||
|
||||
!transactionType ||
|
||||
!creatingUser ||
|
||||
!creatingUser.players ||
|
||||
creatingUser.players.length == 0
|
||||
) {
|
||||
await this.logger.warn({
|
||||
event: 'transaction_create_fail',
|
||||
details: `Player: ${data.playerId}, amount: ${data.amount}, typeEnum: ${data.type}`,
|
||||
userId: Number(userId),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const teamPlayer = creatingUser.players.find(
|
||||
(p) =>
|
||||
p.team.id == player.team.id &&
|
||||
p.teamRole.id >=
|
||||
Number(
|
||||
player.team.settings.find(
|
||||
(s) => s.key == 'transaction_create_min_role',
|
||||
)['value'],
|
||||
),
|
||||
);
|
||||
|
||||
if (!teamPlayer) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const transaction = this.transactionsRepository.create({
|
||||
amount: data.amount,
|
||||
date: data.date,
|
||||
player: player,
|
||||
note: data.note || '',
|
||||
type: transactionType,
|
||||
});
|
||||
|
||||
await this.logger.info({
|
||||
event: 'transaction_create',
|
||||
details: `Player: ${transaction.player.id}, amount: ${transaction.amount}, type: ${transaction.type?.name}`,
|
||||
userId: Number(userId),
|
||||
});
|
||||
|
||||
return this.transactionsRepository.save(transaction);
|
||||
}
|
||||
|
||||
async reverse(transactionId: number, userId: string) {
|
||||
const original = await this.transactionsRepository.findOne({
|
||||
where: { id: transactionId },
|
||||
relations: ['player', 'type'],
|
||||
});
|
||||
|
||||
if (!original) {
|
||||
throw new NotFoundException('Buchung nicht gefunden');
|
||||
}
|
||||
|
||||
if (original.note?.startsWith('Stornierung von Buchung #')) {
|
||||
throw new BadRequestException(
|
||||
'Eine Stornobuchung kann nicht erneut storniert werden',
|
||||
);
|
||||
}
|
||||
|
||||
const alreadyReversed = await this.transactionsRepository.findOne({
|
||||
where: {
|
||||
player: { id: original.player.id },
|
||||
note: `Stornierung von Buchung #${original.id}`,
|
||||
},
|
||||
});
|
||||
if (alreadyReversed) {
|
||||
throw new BadRequestException('Diese Buchung wurde bereits storniert');
|
||||
}
|
||||
|
||||
const creatingUser = await this.usersRepository.findOne({
|
||||
where: { id: Number(userId) },
|
||||
relations: ['players'],
|
||||
});
|
||||
|
||||
if (creatingUser.role.id != RoleEnum.admin) {
|
||||
const teamPlayer = creatingUser.players?.find(
|
||||
(p) =>
|
||||
p.team.id == original.player.team.id &&
|
||||
p.teamRole.id >=
|
||||
Number(
|
||||
original.player.team.settings.find(
|
||||
(s) => s.key == 'transaction_create_min_role',
|
||||
)?.['value'] ?? 0,
|
||||
),
|
||||
);
|
||||
|
||||
if (!teamPlayer) {
|
||||
throw new ForbiddenException(
|
||||
'Keine Berechtigung, diese Buchung zu stornieren',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const originalAmount = Math.abs(Number(original.amount));
|
||||
let reversalType = original.type;
|
||||
let reversalAmount = -originalAmount;
|
||||
|
||||
// Buchungstypen > 10 (z.B. Strafe/Umlage/Gebühr) ziehen im Entity-Hook
|
||||
// immer vom Guthaben ab, egal welches Vorzeichen der Betrag hat. Um sie
|
||||
// auszugleichen, wird die Gegenbuchung stattdessen als "credit" gebucht.
|
||||
if (original.type.id > 10) {
|
||||
reversalType = await this.transactionTypesRepository.findOne({
|
||||
where: { id: TransactionTypeEnum.credit },
|
||||
});
|
||||
reversalAmount = originalAmount;
|
||||
}
|
||||
|
||||
const reversal = this.transactionsRepository.create({
|
||||
amount: reversalAmount,
|
||||
date: new Date().toISOString(),
|
||||
player: original.player,
|
||||
note: `Stornierung von Buchung #${original.id}`,
|
||||
type: reversalType,
|
||||
});
|
||||
|
||||
const saved = await this.transactionsRepository.save(reversal);
|
||||
|
||||
await this.logger.info({
|
||||
event: 'transaction_reverse',
|
||||
details: `Buchung #${original.id} storniert durch Buchung #${saved.id}, Player: ${original.player.id}, amount: ${original.amount}`,
|
||||
userId: Number(userId),
|
||||
});
|
||||
|
||||
return saved;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user