package org.internna.ossmoney.services.impl;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Currency;
import java.math.BigDecimal;
import org.internna.ossmoney.model.Bill;
import org.internna.ossmoney.model.Payee;
import org.internna.ossmoney.model.Account;
import org.internna.ossmoney.util.DateUtils;
import org.internna.ossmoney.model.AccountType;
import org.internna.ossmoney.model.Subcategory;
import org.internna.ossmoney.model.support.Interval;
import org.internna.ossmoney.model.AccountTransaction;
import org.internna.ossmoney.model.FinancialInstitution;
import org.internna.ossmoney.model.security.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.transaction.annotation.Transactional;
@Component
@Transactional
public final class AccountService implements org.internna.ossmoney.services.AccountService {
@Override public void transferMoney(Long origin, Long target, Date operationDate, BigDecimal amount, BigDecimal chargeAmount, double rate, String memo) {
UserDetails user = UserDetails.findCurrentUser();
Account originAccount = Account.findAccount(origin);
Account targetAccount = Account.findAccount(target);
transferMoney(user, originAccount, targetAccount, operationDate, amount, chargeAmount, rate, memo);
}
protected void transferMoney(UserDetails user, Account origin, Account target, Date operationDate, BigDecimal amount, BigDecimal chargeAmount, double rate, String memo) {
if (isValidTransfer(user, origin, target, operationDate, amount)) {
Payee payee = Payee.findMySelf(user);
Subcategory transferCategory = Subcategory.findBySubcategory("category.transfer.out", user);
Subcategory transferInCategory = Subcategory.findBySubcategory("category.transfer.in", user);
AccountTransaction send = AccountTransaction.createInstance(origin, payee, transferCategory, calculateAmount(amount), operationDate, memo);
AccountTransaction receive = AccountTransaction.createInstance(target, payee, transferInCategory, calculateTargetAmount(amount, rate), operationDate, memo);
receive.setOriginOfTheFunds(origin.getName());
send.persist();
receive.persist();
if ((chargeAmount != null) && (!BigDecimal.ZERO.equals(chargeAmount))) {
Subcategory chargeCategory = Subcategory.findBySubcategory("category.bankcharges", user);
AccountTransaction charge = AccountTransaction.createInstance(origin, Payee.findByFinancialInstitution(origin.getHeldAt()), chargeCategory, calculateAmount(chargeAmount), DateUtils.nextDate(operationDate), memo);
charge.persist();
}
Date now = new Date();
origin.setLastModified(now);
target.setLastModified(now);
origin.merge();
target.merge();
}
}
protected boolean isValidTransfer(UserDetails user, Account origin, Account target, Date operationDate, BigDecimal amount) {
return isValidTransaction(user, origin, operationDate, amount) && isValidTransaction(user, target, operationDate, amount);
}
@Override public long addTransaction(AccountTransaction transaction) {
long accountId = -1;
UserDetails user = UserDetails.findCurrentUser();
if (transaction.getAccount() != null) {
accountId = transaction.getAccount().getId();
Account account = Account.findAccount(accountId);
if (isValidTransaction(user, account, transaction.getOperationDate(), transaction.getAmount())) {
transaction.setAccount(account);
if (transaction.getPayee() != null) {
transaction.setPayee(Payee.entityManager().getReference(Payee.class, transaction.getPayee().getId()));
}
if (transaction.getSubcategory() != null) {
transaction.setSubcategory(Subcategory.entityManager().getReference(Subcategory.class, transaction.getSubcategory().getId()));
}
transaction.persist();
account.setLastModified(new Date());
account.merge();
}
}
return accountId;
}
protected boolean isValidTransaction(UserDetails user, Account account, Date operationDate, BigDecimal amount) {
boolean valid = (account != null) && (user != null);
if (valid) {
valid = account.belongsTo(user);
valid &= (account.getClosed() != null) && !account.getClosed();
valid &= (operationDate != null) && operationDate.after(account.getOpened());
valid &= (amount != null) && !BigDecimal.ZERO.equals(amount);
}
return valid;
}
@Override public void createAccount(Account account) {
if (account != null) {
Date now = new Date();
account.setCreated(now);
account.setLastModified(now);
account.setClosed(Boolean.FALSE);
UserDetails user = UserDetails.findCurrentUser();
user.addAccount(account);
if (account.getFavorite() == null) {
account.setFavorite(Boolean.FALSE);
}
if (account.getAccountType() != null) {
account.setAccountType(AccountType.entityManager().getReference(AccountType.class, account.getAccountType().getId()));
}
if (account.getHeldAt() != null) {
FinancialInstitution institution = FinancialInstitution.findFinancialInstitution(account.getHeldAt().getId());
institution.addAccount(account);
}
if ((account.getInitialBalance() == null) || (account.getInitialBalance().doubleValue() < 0)) {
account.setInitialBalance(BigDecimal.ZERO);
}
account.persist();
user.merge();
}
}
protected BigDecimal calculateAmount(BigDecimal amount) {
BigDecimal calculated = amount.abs();
return calculated.multiply(new BigDecimal(-1));
}
protected BigDecimal calculateTargetAmount(BigDecimal amount, double exchangeRate) {
BigDecimal calculated = amount.abs();
return calculated.multiply(new BigDecimal(Math.abs(exchangeRate)));
}
@Override public BigDecimal getExpenses(UserDetails user, Locale locale, Subcategory category, Interval interval) {
BigDecimal expenses = BigDecimal.ZERO;
Currency currency = Currency.getInstance(locale);
for (Account account : user.getAccounts()) {
Currency accountCurrency = Currency.getInstance(account.getLocale());
if (currency.equals(accountCurrency)) {
List<AccountTransaction> transactions = account.getTransactionsInPeriod(interval, null);
if (!CollectionUtils.isEmpty(transactions)) {
for (AccountTransaction transaction : transactions) {
if (transaction.getSubcategory().equals(category)) {
expenses = expenses.add(transaction.getAmount().abs());
}
}
}
}
}
return expenses;
}
@Override public void balance(UserDetails user, Account account, Account origin, Date date, String[] transactions) {
BigDecimal amount = BigDecimal.ZERO;
for (String id : transactions) {
AccountTransaction transaction = AccountTransaction.findAccountTransaction(Long.parseLong(id));
transaction.setReconciled(Boolean.TRUE);
amount = amount.add(transaction.getAmount().abs());
transaction.merge();
}
Subcategory transferCategory = Subcategory.findBySubcategory("category.transfer.out", user);
AccountTransaction transfer = AccountTransaction.createInstance(origin, Payee.findByName(account.getName()), transferCategory, amount, date, null);
transfer.persist();
Subcategory transferInCategory = Subcategory.findBySubcategory("category.transfer.in", user);
AccountTransaction incomeTransfer = AccountTransaction.createInstance(account, Payee.findMySelf(user), transferInCategory, amount, date, null);
incomeTransfer.setOriginOfTheFunds(origin.getName());
incomeTransfer.persist();
}
@Override public void payBill(UserDetails user, Bill bill, Long origin, Double amount, Date operationDate) {
Account account = Account.findAccount(origin);
BigDecimal total = new BigDecimal(amount).abs().negate();
AccountTransaction transaction = AccountTransaction.createInstance(account, bill.getPayee(), bill.getCategory(), total, operationDate, "");
bill.updatePayment(operationDate);
bill.merge();
transaction.persist();
}
}