/* 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; } }