84 lines
3.6 KiB
Java
84 lines
3.6 KiB
Java
package eu.ztsh.wymiana.service;
|
|
|
|
import eu.ztsh.wymiana.exception.ExchangeFailedException;
|
|
import eu.ztsh.wymiana.exception.InsufficientFundsException;
|
|
import eu.ztsh.wymiana.exception.UserNotFoundException;
|
|
import eu.ztsh.wymiana.model.Currency;
|
|
import eu.ztsh.wymiana.model.Symbol;
|
|
import eu.ztsh.wymiana.model.User;
|
|
import eu.ztsh.wymiana.validation.InstanceValidator;
|
|
import eu.ztsh.wymiana.web.model.CurrencyExchangeRequest;
|
|
import lombok.RequiredArgsConstructor;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
@RequiredArgsConstructor
|
|
@Service
|
|
public class CurrencyService {
|
|
|
|
private final UserService userService;
|
|
private final NbpService nbpService;
|
|
private final InstanceValidator validator;
|
|
|
|
public User exchange(CurrencyExchangeRequest request) {
|
|
validator.validate(request);
|
|
return userService.get(request.pesel()).map(user -> {
|
|
if (!request.from().equals(Symbol.PLN) && !request.to().equals(Symbol.PLN)) {
|
|
throw new ExchangeFailedException("Either 'from' or 'to' has to be PLN");
|
|
}
|
|
|
|
var from = user.currencies().get(request.from());
|
|
if (from == null) {
|
|
// There is no currency 'from' opened so no need to check if user has funds to exchange
|
|
throw new InsufficientFundsException();
|
|
}
|
|
var exchanged = performExchange(from,
|
|
Optional.ofNullable(user.currencies().get(request.to())).orElse(create(request.to())),
|
|
Optional.ofNullable(request.toSell()).orElse(BigDecimal.ZERO),
|
|
Optional.ofNullable(request.toBuy()).orElse(BigDecimal.ZERO));
|
|
user.currencies().putAll(exchanged);
|
|
return userService.update(user);
|
|
})
|
|
.orElseThrow(() -> new UserNotFoundException(request));
|
|
}
|
|
|
|
private Currency create(Symbol symbol) {
|
|
return new Currency(symbol, BigDecimal.ZERO);
|
|
}
|
|
|
|
private Map<Symbol, Currency> performExchange(Currency from, Currency to, BigDecimal toSell, BigDecimal toBuy) {
|
|
BigDecimal exchangeRate;
|
|
BigDecimal neededFromAmount;
|
|
BigDecimal requestedToAmount;
|
|
if (from.symbol().equals(Symbol.PLN)) {
|
|
exchangeRate = nbpService.getSellRate(to.symbol());
|
|
neededFromAmount = round(toBuy.signum() != 0 ? toBuy.multiply(exchangeRate) : toSell);
|
|
requestedToAmount = round(toBuy.signum() != 0 ? toBuy : divide(toSell, exchangeRate));
|
|
} else {
|
|
exchangeRate = nbpService.getBuyRate(from.symbol());
|
|
neededFromAmount = round(toBuy.signum() != 0 ? divide(toBuy, exchangeRate) : toSell);
|
|
requestedToAmount = round(toBuy.signum() != 0 ? toBuy : toSell.multiply(exchangeRate));
|
|
}
|
|
if (neededFromAmount.compareTo(from.amount()) > 0) {
|
|
throw new InsufficientFundsException();
|
|
}
|
|
var newFrom = new Currency(from.symbol(), from.amount().subtract(neededFromAmount));
|
|
var newTo = new Currency(to.symbol(), to.amount().add(requestedToAmount));
|
|
return Stream.of(newFrom, newTo).collect(Collectors.toMap(Currency::symbol, currency -> currency));
|
|
}
|
|
|
|
private BigDecimal round(BigDecimal input) {
|
|
return input.setScale(2, RoundingMode.HALF_UP);
|
|
}
|
|
|
|
private BigDecimal divide(BigDecimal input, BigDecimal division) {
|
|
return input.setScale(2, RoundingMode.HALF_UP).divide(division, RoundingMode.HALF_UP);
|
|
}
|
|
|
|
}
|