/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.services.accounts;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import nl.strohalm.cyclos.access.AdminMemberPermission;
import nl.strohalm.cyclos.access.AdminSystemPermission;
import nl.strohalm.cyclos.access.BrokerPermission;
import nl.strohalm.cyclos.access.MemberPermission;
import nl.strohalm.cyclos.access.OperatorPermission;
import nl.strohalm.cyclos.dao.accounts.AccountDAO;
import nl.strohalm.cyclos.dao.accounts.AccountLimitLogDAO;
import nl.strohalm.cyclos.dao.accounts.AmountReservationDAO;
import nl.strohalm.cyclos.dao.accounts.ClosedAccountBalanceDAO;
import nl.strohalm.cyclos.dao.accounts.transactions.TransferDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.accounts.Account;
import nl.strohalm.cyclos.entities.accounts.AccountLimitLog;
import nl.strohalm.cyclos.entities.accounts.AccountOwner;
import nl.strohalm.cyclos.entities.accounts.AccountQuery;
import nl.strohalm.cyclos.entities.accounts.AccountStatus;
import nl.strohalm.cyclos.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.AmountReservation;
import nl.strohalm.cyclos.entities.accounts.ClosedAccountBalance;
import nl.strohalm.cyclos.entities.accounts.InstallmentAmountReservation;
import nl.strohalm.cyclos.entities.accounts.MemberAccount;
import nl.strohalm.cyclos.entities.accounts.MemberAccountType;
import nl.strohalm.cyclos.entities.accounts.MemberGroupAccountSettings;
import nl.strohalm.cyclos.entities.accounts.PendingAuthorizationAmountReservation;
import nl.strohalm.cyclos.entities.accounts.ScheduledPaymentAmountReservation;
import nl.strohalm.cyclos.entities.accounts.SystemAccount;
import nl.strohalm.cyclos.entities.accounts.SystemAccountOwner;
import nl.strohalm.cyclos.entities.accounts.SystemAccountType;
import nl.strohalm.cyclos.entities.accounts.TransferAuthorizationAmountReservation;
import nl.strohalm.cyclos.entities.accounts.transactions.PaymentFilter;
import nl.strohalm.cyclos.entities.accounts.transactions.ScheduledPayment;
import nl.strohalm.cyclos.entities.accounts.transactions.Transfer;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferAuthorization;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.groups.AdminGroup;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.members.Administrator;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.members.MemberQuery;
import nl.strohalm.cyclos.entities.members.MemberTransactionDetailsReportData;
import nl.strohalm.cyclos.entities.members.MemberTransactionSummaryReportData;
import nl.strohalm.cyclos.entities.members.MemberTransactionSummaryVO;
import nl.strohalm.cyclos.entities.members.MembersTransactionsReportParameters;
import nl.strohalm.cyclos.entities.settings.LocalSettings.MemberResultDisplay;
import nl.strohalm.cyclos.services.accountfees.AccountFeeServiceLocal;
import nl.strohalm.cyclos.services.accounts.CreditLimitDTO.Entry;
import nl.strohalm.cyclos.services.accounts.rates.RateServiceLocal;
import nl.strohalm.cyclos.services.accounts.rates.RatesDTO;
import nl.strohalm.cyclos.services.accounts.rates.RatesResultDTO;
import nl.strohalm.cyclos.services.elements.ElementServiceLocal;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.groups.GroupServiceLocal;
import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal;
import nl.strohalm.cyclos.services.settings.SettingsServiceLocal;
import nl.strohalm.cyclos.services.transactions.TransactionSummaryVO;
import nl.strohalm.cyclos.utils.CacheCleaner;
import nl.strohalm.cyclos.utils.CombinedIterator;
import nl.strohalm.cyclos.utils.DataIteratorHelper;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.TransactionHelper;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.query.IteratorList;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import nl.strohalm.cyclos.webservices.model.AccountStatusVO;
import nl.strohalm.cyclos.webservices.model.MemberAccountVO;
import nl.strohalm.cyclos.webservices.utils.AccountHelper;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.lang.ObjectUtils;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
/**
* Account service implementation
* @author luis
*/
public class AccountServiceImpl implements AccountServiceLocal {
/**
* A combined iterator which iterates members and the combination of payment filters x debits x credits
*
* @author luis
*/
private class MembersTransactionsSummaryIterator extends CombinedIterator<MemberTransactionSummaryReportData, Member, MemberTransactionSummaryVO, TransactionSummaryReportKey> {
private final MembersTransactionsReportParameters params;
private final List<Boolean> creditOrDebitToQuery;
private MembersTransactionsSummaryIterator(final Iterator<Member> masterIterator, final MembersTransactionsReportParameters params) {
super(masterIterator);
this.params = params;
// Check whether to get credits / debits
creditOrDebitToQuery = new ArrayList<Boolean>();
if (params.isCredits()) {
creditOrDebitToQuery.add(true);
}
if (params.isDebits()) {
creditOrDebitToQuery.add(false);
}
}
@Override
protected boolean belongsToMasterElement(final Member member, final TransactionSummaryReportKey key, final MemberTransactionSummaryVO vo) {
return vo.getMemberId().equals(member.getId());
}
@Override
protected MemberTransactionSummaryReportData combine(final Member member, final Map<TransactionSummaryReportKey, MemberTransactionSummaryVO> elements) {
final MemberTransactionSummaryReportData data = new MemberTransactionSummaryReportData();
data.setMember(member);
for (final Map.Entry<TransactionSummaryReportKey, MemberTransactionSummaryVO> entry : elements.entrySet()) {
final TransactionSummaryReportKey key = entry.getKey();
final MemberTransactionSummaryVO transactions = entry.getValue();
if (key.credits) {
data.addCredits(key.paymentFilter, transactions);
} else {
data.addDebits(key.paymentFilter, transactions);
}
}
return data;
}
@Override
protected void registerInnerIterators() {
final Collection<PaymentFilter> paymentFilters = params.getPaymentFilters();
final MemberResultDisplay memberDisplay = settingsService.getLocalSettings().getMemberResultDisplay();
for (final PaymentFilter paymentFilter : paymentFilters) {
for (final Boolean isCredit : creditOrDebitToQuery) {
final Iterator<MemberTransactionSummaryVO> iterator = accountDao.membersTransactionSummaryReport(params.getMemberGroups(), paymentFilter, params.getPeriod(), isCredit, memberDisplay);
final TransactionSummaryReportKey key = new TransactionSummaryReportKey(paymentFilter, isCredit);
registerInnerIterator(key, iterator);
}
}
}
}
private static class TransactionSummaryReportKey {
private final PaymentFilter paymentFilter;
private final boolean credits;
public TransactionSummaryReportKey(final PaymentFilter paymentFilter, final boolean credits) {
this.paymentFilter = paymentFilter;
this.credits = credits;
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
final TransactionSummaryReportKey other = (TransactionSummaryReportKey) obj;
return ObjectUtils.equals(paymentFilter, other.paymentFilter) && credits == other.credits;
}
@Override
public int hashCode() {
return paymentFilter.hashCode() * (credits ? 1 : -1);
}
}
private static final float PRECISION_DELTA = 0.0001F;
private static final int CLOSE_BATCH_SIZE = 30;
private AccountDAO accountDao;
private ClosedAccountBalanceDAO closedAccountBalanceDao;
private TransferDAO transferDao;
private AmountReservationDAO amountReservationDao;
private AccountLimitLogDAO accountLimitLogDao;
private SettingsServiceLocal settingsService;
private FetchServiceLocal fetchService;
private AccountTypeServiceLocal accountTypeService;
private RateServiceLocal rateService;
private GroupServiceLocal groupService;
private ElementServiceLocal elementService;
private PermissionServiceLocal permissionService;
private AccountFeeServiceLocal accountFeeService;
private TransactionHelper transactionHelper;
private AccountHelper accountHelper;
@Override
public boolean canView(final Account account) {
if (LoggedUser.isSystem()) {
return true;
}
if (account instanceof SystemAccount) {
if (LoggedUser.isAdministrator()) {
AdminGroup adminGroup = LoggedUser.group();
Collection<SystemAccountType> visibleTypes = fetchService.fetch(adminGroup, AdminGroup.Relationships.VIEW_INFORMATION_OF).getViewInformationOf();
return visibleTypes.contains(account.getType());
}
return false;
} else {
// As there is currently no specific check for individual member accounts, just check by owner
return canViewAccountsOf(account.getOwner());
}
}
@Override
public boolean canViewAccountsOf(final AccountOwner owner) {
if (LoggedUser.isSystem()) {
return true;
}
if (owner instanceof SystemAccountOwner) {
// Not an specific account - just test the permission
return permissionService.hasPermission(AdminSystemPermission.ACCOUNTS_INFORMATION);
} else {
return permissionService.permission((Member) owner)
.admin(AdminMemberPermission.ACCOUNTS_INFORMATION)
.broker(BrokerPermission.ACCOUNTS_INFORMATION)
.member()
.operator(OperatorPermission.ACCOUNT_ACCOUNT_INFORMATION)
.hasPermission();
}
}
@Override
public boolean canViewAuthorizedInformation(final AccountOwner owner) {
if (owner instanceof SystemAccountOwner) {
return permissionService.permission()
.admin(AdminSystemPermission.ACCOUNTS_AUTHORIZED_INFORMATION)
.hasPermission();
} else {
return permissionService.permission((Member) owner)
.admin(AdminMemberPermission.ACCOUNTS_AUTHORIZED_INFORMATION)
.broker(BrokerPermission.ACCOUNTS_AUTHORIZED_INFORMATION)
.member(MemberPermission.ACCOUNT_AUTHORIZED_INFORMATION)
.operator(OperatorPermission.ACCOUNT_AUTHORIZED_INFORMATION)
.hasPermission();
}
}
@Override
public void closeBalances(final Calendar time) {
final Calendar day = DateHelper.truncate(time);
// Process each batch in a new transaction
final boolean[] hasMore = new boolean[1];
hasMore[0] = true;
while (hasMore[0]) {
transactionHelper.runInCurrentThread(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(final TransactionStatus txStatus) {
CacheCleaner cacheCleaner = new CacheCleaner(fetchService);
IteratorList<Account> accounts = accountDao.iterateUnclosedAccounts(day, CLOSE_BATCH_SIZE);
hasMore[0] = accounts.hasNext();
try {
for (Account account : accounts) {
closeBalance(account, day);
// Clear the cache to avoid having too much objects in memory
cacheCleaner.clearCache();
}
} finally {
DataIteratorHelper.close(accounts);
}
}
});
}
}
@Override
public int countPendingActivation(final MemberGroup group, final MemberAccountType accountType) {
return accountDao.countAccounts(group, accountType, MemberAccount.Action.ACTIVATE);
}
@Override
public Account getAccount(final AccountDTO params, final Relationship... fetch) {
// We might receive an account itself, or the owner / type parameters
Account account = params.getAccount();
AccountOwner owner;
AccountType type;
if (account != null && account.isPersistent()) {
account = accountDao.load(account.getId(), fetch);
owner = account.getOwner();
type = account.getType();
} else {
owner = params.getOwner();
type = params.getType();
}
// FIXME this is security logic, and no longer needs to be done here
if (LoggedUser.hasUser() && LoggedUser.isAdministrator() && (owner instanceof SystemAccountOwner)) {
// For administrator viewing system accounts, ensure return only the types he can view information about
AdminGroup group = LoggedUser.group();
group = fetchService.fetch(group, AdminGroup.Relationships.VIEW_INFORMATION_OF);
for (final SystemAccountType current : group.getViewInformationOf()) {
if (current.equals(type)) {
return fetchService.fetch(current.getAccount(), fetch);
}
}
throw new EntityNotFoundException(SystemAccount.class);
}
account = account == null ? accountDao.load(owner, type, fetch) : account;
// Update the account on the param, so on a next attempt to reuse the same param, the account is already set
params.setAccount(account);
return account;
}
@Override
public List<? extends Account> getAccounts(final AccountOwner owner, final Relationship... fetch) {
return getAccounts(owner, false, fetch);
}
/**
* gets a Set with accounts belonging to the allowedTTs AND to the member
* @param member the members whose accounts are checked on this
* @param allowedTTs the transfer types to be checked
* @param direction a TransferType.Direction enum.
* <ul>
* <li>If FROM, only accounts from which the checked transfer types come from are included.
* <li>If TO, only accounts to which the checked transfer types go are included.
* <li>If BOTH, both from and to accounts of the transfer types are included.
* @return a Set with accounts belonging to the member, and containing the transfer types in allowedTTs.
*/
@Override
@SuppressWarnings("unchecked")
public Set<? extends Account> getAccountsFromTTs(final Member member, final Collection<TransferType> allowedTTs, final TransferType.Direction direction) {
final Set<MemberAccount> allowedAccounts = new HashSet<MemberAccount>(allowedTTs.size());
final List<MemberAccount> accounts = (List<MemberAccount>) getAccounts(member);
for (final TransferType currentTT : allowedTTs) {
for (final MemberAccount currentAccount : accounts) {
if (direction.equals(TransferType.Direction.BOTH)) {
if (currentAccount.getType().equals(currentTT.getFrom()) || (currentAccount.getType().equals(currentTT.getTo()))) {
allowedAccounts.add(currentAccount);
}
} else if (direction.equals(TransferType.Direction.FROM) && currentAccount.getType().equals(currentTT.getFrom())) {
allowedAccounts.add(currentAccount);
} else if (direction.equals(TransferType.Direction.TO) && currentAccount.getType().equals(currentTT.getTo())) {
allowedAccounts.add(currentAccount);
}
}
}
return allowedAccounts;
}
@Override
public BigDecimal getBalance(final AccountDateDTO params) {
Account account = getAccount(params);
Calendar date = params.getDate();
if (date == null) {
date = Calendar.getInstance();
}
// Get the last closed balance before the given date
ClosedAccountBalance closedBalance = closedAccountBalanceDao.get(account, date);
BigDecimal balance = closedBalance == null ? BigDecimal.ZERO : closedBalance.getBalance();
Calendar beginDate = (closedBalance == null) ? null : closedBalance.getDate();
Period balanceDiffPeriod = Period.between(beginDate, date).useTime();
BigDecimal diff = transferDao.balanceDiff(account, balanceDiffPeriod);
balance = balance.add(diff);
return balance;
}
@Override
public BigDecimal getBalanceAtTimePoint(final Account account, final Calendar date, final boolean inclusive, final boolean compensateChargebacks) {
AccountDateDTO param = new AccountDateDTO(account, date);
BigDecimal balance = (inclusive) ? getBalance(param) : getExclusiveBalance(param);
if (compensateChargebacks) {
Period period = Period.endingAt(date).useTime();
period.setInclusiveEnd(inclusive);
BigDecimal chargebackBalance = transferDao.getChargebackBalance(account, period);
balance = balance.add(chargebackBalance);
}
return balance;
}
@Override
public BigDecimal getBalanceAtTransfer(final Account account, final Transfer transfer, final boolean compensateChargebacks, final boolean inclusive) {
if (transfer.getProcessDate() == null) {
throw new IllegalArgumentException("transfer must be processed.");
}
// Get the last closed balance before the given date
ClosedAccountBalance closedBalance = closedAccountBalanceDao.get(account, transfer.getProcessDate());
BigDecimal balance = closedBalance == null ? BigDecimal.ZERO : closedBalance.getBalance();
Period diffPeriod = Period.begginingAt((closedBalance == null) ? null : closedBalance.getDate());
diffPeriod.setInclusiveEnd(inclusive);
BigDecimal diff = transferDao.balanceDiff(account, diffPeriod, transfer);
balance = balance.add(diff);
if (compensateChargebacks) {
BigDecimal chargebackBalance = transferDao.getChargebackBalance(account, transfer, inclusive);
balance = balance.add(chargebackBalance);
}
return balance;
}
@Override
public TransactionSummaryVO getBrokerCommissions(final GetTransactionsDTO params) {
return accountDao.getBrokerCommissions(params);
}
@Override
public BigDecimal getCreditLimit(final AccountDTO params) {
final Account account = getAccount(params);
return account.getCreditLimit();
}
@Override
public CreditLimitDTO getCreditLimits(final Member owner) {
final Map<AccountType, BigDecimal> limits = new HashMap<AccountType, BigDecimal>();
final Map<AccountType, BigDecimal> upperLimits = new HashMap<AccountType, BigDecimal>();
final List<? extends Account> accts = getAccounts(owner, true);
for (final Account acct : accts) {
final AccountType type = acct.getType();
limits.put(type, acct.getCreditLimit());
upperLimits.put(type, acct.getUpperCreditLimit());
}
final CreditLimitDTO dto = new CreditLimitDTO();
dto.setLimitPerType(limits);
dto.setUpperLimitPerType(upperLimits);
return dto;
}
@Override
public TransactionSummaryVO getCredits(final GetTransactionsDTO params) {
return accountDao.getCredits(params);
}
@Override
public AccountStatusVO getCurrentAccountStatusVO(final AccountDTO accountDTO) {
AccountStatus accountStatus = getCurrentStatus(accountDTO);
return accountHelper.toVO(accountStatus);
}
@Override
public AccountStatus getCurrentStatus(final AccountDTO params) {
final Account account = getAccount(params);
AccountStatus status = getStatus(account, null);
// Member accounts could also have reserved amounts for volume account fees
if (account instanceof MemberAccount) {
BigDecimal diff = accountFeeService.calculateReservedAmountForVolumeFee((MemberAccount) account);
status.setReservedAmount(status.getReservedAmount().add(diff));
}
return status;
}
@Override
public TransactionSummaryVO getDebits(final GetTransactionsDTO params) {
return accountDao.getDebits(params);
}
@Override
public MemberAccount getDefaultAccount() {
MemberAccount account = null;
if (LoggedUser.hasUser()) {
MemberGroup group = LoggedUser.group();
MemberAccountType defaultType = accountTypeService.getDefault(group);
if (defaultType != null) {
account = (MemberAccount) getAccount(new AccountDTO(LoggedUser.accountOwner(), defaultType));
}
}
if (account == null) {
throw new EntityNotFoundException(Account.class);
}
return account;
}
@Override
public Account getDefaultAccountFromList(Member member, final List<Account> allowedAccounts) {
member = fetchService.fetch(member, Element.Relationships.GROUP);
// check if the default account is amongst this
final MemberAccountType defaultType = accountTypeService.getDefault(member.getMemberGroup());
for (final Account currentAccount : allowedAccounts) {
if (currentAccount.getType().equals(defaultType)) {
// Found the default account: return the DTO based on it
return currentAccount;
}
}
// if no default account, just take the first
if (allowedAccounts.size() > 0) {
return allowedAccounts.get(0);
}
// No accounts: return null
return null;
}
@Override
public BigDecimal getExclusiveBalance(final AccountDateDTO params) {
Account account = getAccount(params);
Calendar date = params.getDate();
// Get the last closed balance before the given date
ClosedAccountBalance closedBalance = closedAccountBalanceDao.get(account, date);
BigDecimal balance = closedBalance == null ? BigDecimal.ZERO : closedBalance.getBalance();
if (date == null || closedBalance == null || !date.equals(closedBalance.getDate())) {
Calendar beginDate = (closedBalance == null) ? null : closedBalance.getDate();
Period balanceDiffPeriod = Period.between(beginDate, date).useTime();
balanceDiffPeriod.setInclusiveEnd(false);
BigDecimal diff = transferDao.balanceDiff(account, balanceDiffPeriod);
balance = balance.add(diff);
}
return balance;
}
@Override
public MemberAccountVO getMemberAccountVO(final Long memberAccountId) {
if (memberAccountId == null) {
return null;
}
MemberAccount memberAccount = load(memberAccountId);
return accountHelper.toVO(memberAccount);
}
@Override
public AccountStatus getRatedStatus(final Account account, final Calendar date) {
AccountStatus status = getStatusAt(account, date, false);
// status may not be null here, so we skip a null check
RatesDTO dto = new RatesDTO();
dto.setDate(date);
dto.setAccount(account);
dto.setAmount(status.getBalance());
RatesResultDTO rates = rateService.getRates(dto);
status.setRates(rates);
return status;
}
@Override
public AccountStatus getStatus(final Account account, final Calendar date) {
return getStatusAt(account, date, false);
}
@Override
public Map<PaymentFilter, TransactionSummaryVO> getTransactionsSummary(final Member member, final AccountType accountType, final Period period, final Collection<PaymentFilter> paymentFilters, final boolean credits) {
final Map<PaymentFilter, TransactionSummaryVO> summary = new HashMap<PaymentFilter, TransactionSummaryVO>();
final GetTransactionsDTO params = new GetTransactionsDTO();
params.setOwner(member);
params.setType(accountType);
params.setPeriod(period);
for (final PaymentFilter paymentFilter : paymentFilters) {
params.setPaymentFilter(paymentFilter);
TransactionSummaryVO vo;
if (credits) {
vo = accountDao.getCredits(params);
} else {
vo = accountDao.getDebits(params);
}
summary.put(paymentFilter, vo);
}
return summary;
}
@Override
public boolean hasAccounts(final Member member) {
return !getAccounts(member).isEmpty();
}
@Override
public <T extends Account> T load(final Long id, final Relationship... fetch) {
return accountDao.<T> load(id, fetch);
}
@Override
@SuppressWarnings("unchecked")
public Iterator<MemberTransactionDetailsReportData> membersTransactionsDetailsReport(final MembersTransactionsReportParameters params) {
// Ensure the parameters are valid
if (!isValid(params)) {
return IteratorUtils.EMPTY_ITERATOR;
}
return accountDao.membersTransactionsDetailsReport(params);
}
@Override
@SuppressWarnings("unchecked")
public Iterator<MemberTransactionSummaryReportData> membersTransactionsSummaryReport(final MembersTransactionsReportParameters params) {
// Ensure the parameters are valid
if (!isValid(params)) {
return IteratorUtils.EMPTY_ITERATOR;
}
// Retrieve the members
final Iterator<Member> membersIterator = resolveMembersForTransactionsReport(params);
return new MembersTransactionsSummaryIterator(membersIterator, params);
}
@Override
public void removeClosedBalancesAfter(final Account account, final Calendar date) {
closedAccountBalanceDao.removeClosedBalancesAfter(account, date);
}
@Override
public ScheduledPaymentAmountReservation reserve(final ScheduledPayment scheduledPayment) {
ScheduledPaymentAmountReservation reservation = new ScheduledPaymentAmountReservation();
reservation.setDate(Calendar.getInstance());
reservation.setAccount(scheduledPayment.getFrom());
reservation.setAmount(scheduledPayment.getAmount());
reservation.setScheduledPayment(scheduledPayment);
return insertReservation(reservation);
}
@Override
public PendingAuthorizationAmountReservation reservePending(final Transfer transfer) {
PendingAuthorizationAmountReservation reservation = new PendingAuthorizationAmountReservation();
reservation.setDate(Calendar.getInstance());
reservation.setAccount(transfer.getFrom());
reservation.setAmount(transfer.getAmount());
reservation.setTransfer(transfer);
return insertReservation(reservation);
}
@Override
public TransferAuthorizationAmountReservation returnReservation(final TransferAuthorization authorization, final Transfer transfer) {
TransferAuthorizationAmountReservation reservation = new TransferAuthorizationAmountReservation();
reservation.setDate(Calendar.getInstance());
reservation.setAccount(transfer.getFrom());
reservation.setAmount(transfer.getAmount().negate());
reservation.setTransferAuthorization(authorization);
reservation.setTransfer(transfer);
return insertReservation(reservation);
}
@Override
public InstallmentAmountReservation returnReservationForInstallment(final Transfer transfer) {
InstallmentAmountReservation reservation = new InstallmentAmountReservation();
reservation.setDate(Calendar.getInstance());
reservation.setAccount(transfer.getFrom());
reservation.setAmount(transfer.getAmount().negate());
reservation.setTransfer(transfer);
return insertReservation(reservation);
}
public void setAccountDao(final AccountDAO accountDao) {
this.accountDao = accountDao;
}
public void setAccountFeeServiceLocal(final AccountFeeServiceLocal accountFeeService) {
this.accountFeeService = accountFeeService;
}
public void setAccountHelper(final AccountHelper accountHelper) {
this.accountHelper = accountHelper;
}
public void setAccountLimitLogDao(final AccountLimitLogDAO accountLimitLogDao) {
this.accountLimitLogDao = accountLimitLogDao;
}
public void setAccountTypeServiceLocal(final AccountTypeServiceLocal accountTypeService) {
this.accountTypeService = accountTypeService;
}
public void setAmountReservationDao(final AmountReservationDAO amountReservationDao) {
this.amountReservationDao = amountReservationDao;
}
public void setClosedAccountBalanceDao(final ClosedAccountBalanceDAO closedAccountBalanceDao) {
this.closedAccountBalanceDao = closedAccountBalanceDao;
}
@Override
public void setCreditLimit(final Member owner, final CreditLimitDTO limits) {
validate(owner, limits);
Map<? extends AccountType, BigDecimal> limitPerType = limits.getLimitPerType();
final Map<AccountType, BigDecimal> newLimitPerType = new HashMap<AccountType, BigDecimal>();
if (limitPerType != null) {
for (AccountType accountType : limitPerType.keySet()) {
final BigDecimal limit = limitPerType.get(accountType);
accountType = fetchService.fetch(accountType);
newLimitPerType.put(accountType, limit);
}
}
limitPerType = newLimitPerType;
limits.setLimitPerType(limitPerType);
Map<? extends AccountType, BigDecimal> upperLimitPerType = limits.getUpperLimitPerType();
final Map<AccountType, BigDecimal> newUpperLimitPerType = new HashMap<AccountType, BigDecimal>();
if (upperLimitPerType != null) {
for (AccountType accountType : upperLimitPerType.keySet()) {
final BigDecimal limit = upperLimitPerType.get(accountType);
accountType = fetchService.fetch(accountType);
newUpperLimitPerType.put(accountType, limit);
}
}
upperLimitPerType = newUpperLimitPerType;
limits.setUpperLimitPerType(upperLimitPerType);
final List<Entry> entries = limits.getEntries();
for (final Entry entry : entries) {
final AccountType type = entry.getAccountType();
final BigDecimal limit = entry.getCreditLimit();
final BigDecimal upperLimit = entry.getUpperCreditLimit();
if (limit == null && upperLimit == null) {
continue;
}
List<? extends Account> accts;
if (owner == null) {
accts = getAccounts(type);
} else {
accts = Arrays.asList(getAccount(new AccountDTO(owner, type)));
}
for (Account account : accts) {
boolean limitHasChanged = false;
if (limit != null && !account.getCreditLimit().equals(limit.abs())) {
account.setCreditLimit(limit.abs());
limitHasChanged = true;
}
if (upperLimit != null && (account.getUpperCreditLimit() == null || !account.getUpperCreditLimit().equals(upperLimit.abs()))) {
account.setUpperCreditLimit(upperLimit.abs());
limitHasChanged = true;
}
if (limitHasChanged) {
// Update the account
account = accountDao.update(account);
// Generate the log
AccountLimitLog log = new AccountLimitLog();
log.setAccount(account);
log.setBy((Administrator) LoggedUser.element());
log.setDate(Calendar.getInstance());
log.setCreditLimit(limit);
log.setUpperCreditLimit(upperLimit);
accountLimitLogDao.insert(log);
}
}
}
}
public void setElementServiceLocal(final ElementServiceLocal elementService) {
this.elementService = elementService;
}
public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
this.fetchService = fetchService;
}
public void setGroupServiceLocal(final GroupServiceLocal groupService) {
this.groupService = groupService;
}
public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) {
this.permissionService = permissionService;
}
public void setRateServiceLocal(final RateServiceLocal rateService) {
this.rateService = rateService;
}
public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) {
this.settingsService = settingsService;
}
public void setTransactionHelper(final TransactionHelper transactionHelper) {
this.transactionHelper = transactionHelper;
}
public void setTransferDao(final TransferDAO transferDao) {
this.transferDao = transferDao;
}
@Override
public void validate(Member member, final CreditLimitDTO creditLimit) {
// Fetch the member
try {
member = fetchService.fetch(member);
} catch (final Exception e) {
throw new ValidationException();
}
// Retrieve all given account types
final Map<? extends AccountType, BigDecimal> limitPerType = creditLimit.getLimitPerType();
final Map<? extends AccountType, BigDecimal> upperLimitPerType = creditLimit.getUpperLimitPerType();
final Set<AccountType> accountTypes = new HashSet<AccountType>();
if (limitPerType != null) {
for (final AccountType at : limitPerType.keySet()) {
accountTypes.add(at);
}
}
if (upperLimitPerType != null) {
for (final AccountType at : upperLimitPerType.keySet()) {
accountTypes.add(at);
}
}
// Check if the member has all account types
for (final AccountType type : accountTypes) {
try {
getAccount(new AccountDTO(member, type));
} catch (final EntityNotFoundException e) {
throw new ValidationException();
}
}
}
private void closeBalance(final Account account, final Calendar day) {
AccountStatus status = getStatusAt(account, day, true);
if (status != null) {
// Insert a new closed balance
ClosedAccountBalance closedBalance = new ClosedAccountBalance();
closedBalance.setDate(day);
closedBalance.setAccount(account);
closedBalance.setBalance(status.getBalance());
closedBalance.setReserved(status.getReservedAmount());
closedAccountBalanceDao.insert(closedBalance);
}
// Update the last closing date
account.setLastClosingDate(day);
}
private List<? extends Account> getAccounts(final AccountOwner owner, final boolean forceAllAccounts, final Relationship... fetch) {
final AccountQuery query = new AccountQuery();
query.setOwner(owner);
query.fetch(fetch);
List<? extends Account> accounts = accountDao.search(query);
if (forceAllAccounts) {
return accounts;
} else if (owner instanceof Member) {
accounts = new ArrayList<Account>(accounts);
final Member member = fetchService.fetch((Member) owner);
for (final Iterator<? extends Account> iterator = accounts.iterator(); iterator.hasNext();) {
final Account account = iterator.next();
MemberGroupAccountSettings accountSettings;
boolean remove = false;
final Group group = member.getGroup();
if (group.getStatus() == Group.Status.NORMAL) {
try {
accountSettings = groupService.loadAccountSettings(group.getId(), account.getType().getId());
} catch (final EntityNotFoundException e) {
accountSettings = null;
remove = true;
}
} else {
// Removed group
accountSettings = null;
}
// Check whether the account is hidden
if (accountSettings != null && accountSettings.isHideWhenNoCreditLimit()) {
// Hide the account: it should be visible only when has credit limit, and credit limit is zero or there are at least one transfer
final boolean hasCreditLimit = Math.abs(account.getCreditLimit().floatValue()) > PRECISION_DELTA;
if (!hasCreditLimit && !transferDao.hasTransfers(account)) {
remove = true;
}
}
if (remove) {
iterator.remove();
}
}
}
return accounts;
}
private List<? extends Account> getAccounts(final AccountType type) {
final AccountQuery query = new AccountQuery();
query.setType(type);
return accountDao.search(query);
}
private AccountStatus getStatusAt(final Account account, final Calendar date, final boolean onlyIfThereAreDiffs) {
// Fill the status with basic data
AccountStatus status = new AccountStatus();
status.setAccount(account);
status.setCreditLimit(account.getCreditLimit());
status.setUpperCreditLimit(account.getUpperCreditLimit());
status.setDate(date);
// Get the last closed balance
ClosedAccountBalance closedBalance = closedAccountBalanceDao.get(account, date);
Calendar closedDate = closedBalance == null ? null : closedBalance.getDate();
Calendar endDate = (Calendar) (date == null ? null : date.clone());
if (endDate != null) {
endDate.add(Calendar.SECOND, -1);
}
Period period = Period.between(closedDate, endDate);
// Get the balance diff
BigDecimal balanceDiff = transferDao.balanceDiff(account, period);
status.setBalance(closedBalance == null ? balanceDiff : closedBalance.getBalance().add(balanceDiff));
// Get the reserved amount diff
BigDecimal reservationDiff = amountReservationDao.reservationDiff(account, period);
status.setReservedAmount(closedBalance == null ? reservationDiff : closedBalance.getReserved().add(reservationDiff));
if (onlyIfThereAreDiffs && balanceDiff.equals(BigDecimal.ZERO) && reservationDiff.equals(BigDecimal.ZERO)) {
// If should return only if there are diffs, and there were none, return null
return null;
}
return status;
}
private <R extends AmountReservation> R insertReservation(R reservation) {
reservation = amountReservationDao.insert(reservation);
// Make sure there are no closed balances on the future
removeClosedBalancesAfter(reservation.getAccount(), reservation.getDate());
return reservation;
}
private boolean isValid(final MembersTransactionsReportParameters params) {
final Collection<MemberGroup> memberGroups = params.getMemberGroups();
if (CollectionUtils.isEmpty(memberGroups)) {
return false;
}
if (!params.isDebits() && !params.isCredits()) {
return false;
}
return true;
}
@SuppressWarnings("unchecked")
private Iterator<Member> resolveMembersForTransactionsReport(final MembersTransactionsReportParameters params) {
final Collection<MemberGroup> groups = params.getMemberGroups();
final Period period = params.getPeriod();
final MemberQuery query = new MemberQuery();
query.setPageParameters(params.getPageParameters());
if (params.isFetchBroker()) {
query.fetch(Member.Relationships.BROKER);
}
query.setGroups(groups);
if (period != null && period.getEnd() != null) {
query.setActivationPeriod(Period.endingAt(period.getEnd()));
}
query.setResultType(ResultType.ITERATOR);
final List<Member> members = (List<Member>) elementService.search(query);
final Iterator<Member> membersIterator = members.iterator();
return membersIterator;
}
}