/*
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.dao.accounts.transactions;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import nl.strohalm.cyclos.dao.BaseDAOImpl;
import nl.strohalm.cyclos.dao.accounts.AccountDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.accounts.Account;
import nl.strohalm.cyclos.entities.accounts.AccountQuery;
import nl.strohalm.cyclos.entities.accounts.Currency;
import nl.strohalm.cyclos.entities.accounts.Rated;
import nl.strohalm.cyclos.entities.accounts.external.ExternalTransfer;
import nl.strohalm.cyclos.entities.accounts.transactions.AuthorizationLevel;
import nl.strohalm.cyclos.entities.accounts.transactions.AuthorizationLevel.Authorizer;
import nl.strohalm.cyclos.entities.accounts.transactions.Payment;
import nl.strohalm.cyclos.entities.accounts.transactions.PaymentFilter;
import nl.strohalm.cyclos.entities.accounts.transactions.Transfer;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferQuery;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.accounts.transactions.TransfersAwaitingAuthorizationQuery;
import nl.strohalm.cyclos.entities.exceptions.DaoException;
import nl.strohalm.cyclos.entities.groups.GroupFilter;
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.Operator;
import nl.strohalm.cyclos.entities.reports.StatisticalDTO;
import nl.strohalm.cyclos.services.stats.general.KeyDevelopmentsStatsPerMonthVO;
import nl.strohalm.cyclos.utils.BigDecimalHelper;
import nl.strohalm.cyclos.utils.Pair;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.hibernate.HibernateCustomFieldHandler;
import nl.strohalm.cyclos.utils.hibernate.HibernateHelper;
import nl.strohalm.cyclos.utils.query.PageParameters;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;
import nl.strohalm.cyclos.utils.statistics.ListOperations;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
/**
* Implementation class for transfer DAO
* @author rafael, Jefferson Magno, rinke
*/
public class TransferDAOImpl extends BaseDAOImpl<Transfer> implements TransferDAO {
private AccountDAO accountDao;
private HibernateCustomFieldHandler hibernateCustomFieldHandler;
public TransferDAOImpl() {
super(Transfer.class);
}
@Override
public BigDecimal balanceDiff(final Account account, final Period period) {
if (account == null) {
return BigDecimal.ZERO;
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("account", account.getId());
StringBuilder hql = new StringBuilder();
hql.append(" select sum( ");
hql.append(" case when t.chargebackOf.id is null then ");
hql.append(" case when t.from.id = :account then -t.amount else t.amount end ");
hql.append(" else ");
hql.append(" case when t.to.id = :account then t.amount else -t.amount end ");
hql.append(" end)");
hql.append(" from Transfer t ");
hql.append(" where (t.from.id = :account or t.to.id = :account) ");
hql.append(" and t.processDate is not null ");
HibernateHelper.addPeriodParameterToQuery(hql, params, "t.processDate", period);
BigDecimal diff = (BigDecimal) uniqueResult(hql.toString(), params);
return BigDecimalHelper.nvl(diff);
}
@Override
public BigDecimal balanceDiff(final Account account, Period period, final Transfer transfer) {
if (account == null) {
return BigDecimal.ZERO;
}
period = period.clone();
period.setEnd(null);
Map<String, Object> params = new HashMap<String, Object>();
params.put("account", account.getId());
StringBuilder hql = new StringBuilder();
hql.append(" select sum( ");
hql.append(" case when t.chargebackOf.id is null then ");
hql.append(" case when t.from.id = :account then -t.amount else t.amount end ");
hql.append(" else ");
hql.append(" case when t.to.id = :account then t.amount else -t.amount end ");
hql.append(" end)");
hql.append(" from Transfer t ");
hql.append(" where (t.from.id = :account or t.to.id = :account) ");
hql.append(" and t.processDate is not null ");
if (period != null && period.getBegin() != null) {
hql.append(" and (t.processDate >");
if (period.isInclusiveBegin()) {
hql.append("=");
}
hql.append(" :beginDate ) ");
params.put("beginDate", period.getBegin());
}
params.put("endDate", transfer.getProcessDate());
params.put("transferId", transfer.getId());
hql.append(" and (t.processDate < :endDate or (t.processDate = :endDate and t.id <");
if (period != null && period.isInclusiveEnd()) {
hql.append("=");
}
hql.append(" :transferId) ) ");
BigDecimal diff = (BigDecimal) uniqueResult(hql.toString().trim(), params);
return BigDecimalHelper.nvl(diff);
}
@Override
public BigDecimal getChargebackBalance(final Account account, final Period period) {
if (account == null) {
return BigDecimal.ZERO;
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("account", account.getId());
StringBuilder hql = new StringBuilder();
hql.append(" select sum(case when t.from.id = :account then t.amount else -t.amount end) ");
hql.append(" from Transfer t ");
hql.append(" where (t.from.id = :account or t.to.id = :account) ");
hql.append(" and (t.chargedBackBy is not null or t.chargebackOf is not null) ");
hql.append(" and t.processDate is not null ");
HibernateHelper.addPeriodParameterToQuery(hql, params, "t.processDate", period);
BigDecimal diff = (BigDecimal) uniqueResult(hql.toString(), params);
return BigDecimalHelper.nvl(diff);
}
@Override
public BigDecimal getChargebackBalance(final Account account, final Transfer transfer, final boolean inclusive) {
if (account == null) {
return BigDecimal.ZERO;
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("account", account.getId());
StringBuilder hql = new StringBuilder();
hql.append(" select sum(case when t.from.id = :account then t.amount else -t.amount end) ");
hql.append(" from Transfer t ");
hql.append(" where (t.from.id = :account or t.to.id = :account) ");
hql.append(" and (t.chargedBackBy is not null or t.chargebackOf is not null) ");
hql.append(" and t.processDate is not null ");
params.put("date", transfer.getProcessDate());
params.put("transferId", transfer.getId());
hql.append(" and (t.processDate < :date or (t.processDate = :date and t.id <");
if (inclusive) {
hql.append("=");
}
hql.append(" :transferId) ) ");
BigDecimal diff = (BigDecimal) uniqueResult(hql.toString(), params);
return BigDecimalHelper.nvl(diff);
}
// Used by Activity: all using gross product.
// TODO statistics: all statistics queries must included processDate is not null
@Override
public List<Pair<Member, BigDecimal>> getGrossProductPerMember(final StatisticalDTO dto) {
final Map<String, Object> namedParameters = new HashMap<String, Object>();
final StringBuilder hql = new StringBuilder("select new " + Pair.class.getName());
hql.append("(m, sum(t.amount)) from Transfer t, Member m where 1=1 ");
hql.append(" and t.chargedBackBy is null and t.chargebackOf is null ");
hql.append(" and exists (select ma.id from MemberAccount ma where t.to = ma and m = ma.member) ");
appendGroupAndPaymentFilterAndPeriod(hql, namedParameters, dto);
// Group by the member that received the transfer
hql.append(" group by m ");
// Order by the sum of amounts
hql.append(" order by sum(t.amount) desc ");
return list(hql.toString(), namedParameters);
}
// performance tested 2010: 24 secs
@Override
public List<KeyDevelopmentsStatsPerMonthVO> getGrossProductPerMonth(final StatisticalDTO dto) {
final Map<String, Object> namedParameters = new HashMap<String, Object>();
final StringBuilder hql = new StringBuilder();
hql.append(" select new " + KeyDevelopmentsStatsPerMonthVO.class.getName());
hql.append(" (sum(t.amount), month(t.processDate), year(t.processDate)) ");
hql.append(" from Transfer t, Member m where 1=1 ");
hql.append(" and t.chargedBackBy is null and t.chargebackOf is null ");
hql.append(" and exists (select ma.id from MemberAccount ma where t.to = ma and m = ma.member) ");
appendGroupAndPaymentFilterAndPeriod(hql, namedParameters, dto);
hql.append(" group by month(t.processDate), year(t.processDate) ");
hql.append(" order by year(t.processDate), month(t.processDate) ");
final List<KeyDevelopmentsStatsPerMonthVO> list = list(hql.toString(), namedParameters);
return list;
}
// Used by Activity Stats > all using number of trans, % not trading
@Override
public List<Pair<Member, Integer>> getNumberOfTransactionsPerMember(final StatisticalDTO dto) {
final Map<String, Object> namedParameters = new HashMap<String, Object>();
final StringBuilder hql = new StringBuilder();
hql.append("select new " + Pair.class.getName() + "(m, count(t.id))");
hql.append(" from Transfer t, Member m where 1=1 ");
hql.append(" and t.chargedBackBy is null and t.chargebackOf is null ");
// transaction to
hql.append(" and (exists (select ma.id from MemberAccount ma where t.to = ma and m = ma.member) ");
// transaction from (added by Rinke, because eventually this seemed more logical)
hql.append(" or exists (select ma.id from MemberAccount ma where t.from = ma and m = ma.member)) ");
appendGroupAndPaymentFilterAndPeriod(hql, namedParameters, dto);
// Group by the member that received the transfer
hql.append(" group by m ");
// Order by the sum of amounts
hql.append(" order by count(t.id) desc ");
return list(hql.toString(), namedParameters);
}
@Override
public List<KeyDevelopmentsStatsPerMonthVO> getNumberOfTransactionsPerMonth(final StatisticalDTO dto) {
final Map<String, Object> namedParameters = new HashMap<String, Object>();
final StringBuilder hql = new StringBuilder();
hql.append(" select new " + KeyDevelopmentsStatsPerMonthVO.class.getName());
hql.append(" (count(distinct t.id), month(t.processDate), year(t.processDate)) ");
hql.append(" from Transfer t, Member m where 1=1 ");
hql.append(" and t.chargedBackBy is null and t.chargebackOf is null ");
hql.append(" and (exists (select ma.id from MemberAccount ma where t.to = ma and m = ma.member) ");
hql.append(" or exists (select ma.id from MemberAccount ma where t.from = ma and m = ma.member)) ");
appendGroupAndPaymentFilterAndPeriod(hql, namedParameters, dto);
hql.append(" group by month(t.processDate), year(t.processDate) ");
hql.append(" order by year(t.processDate), month(t.processDate) ");
final List<KeyDevelopmentsStatsPerMonthVO> list = list(hql.toString(), namedParameters);
return list;
}
@Override
public Calendar getOldestTransfer(final Currency currency, final Account account, final Period period, final boolean excludeChargebacks) {
final StringBuilder hql = new StringBuilder();
final Map<String, Object> namedParameters = new HashMap<String, Object>();
hql.append(" select min(t.processDate) from Transfer t ");
hql.append(" where 1=1 ");
if (currency != null) {
namedParameters.put("currency", currency);
hql.append(" and t.to.type.currency = :currency ");
}
if (account != null) {
namedParameters.put("account", account);
hql.append(" and (t.from = :account or t.to = :account) ");
}
if (excludeChargebacks) {
hql.append(" and t.chargedBackBy is null and t.chargebackOf is null ");
}
HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "t.processDate", period);
hql.append(" order by t.processDate, t.id");
return (Calendar) uniqueResult(hql.toString(), namedParameters);
}
@Override
public List<Pair<Member, BigDecimal>> getPaymentsPerMember(final StatisticalDTO dto) throws DaoException {
// change later on to its own implementation (it should sum outgoing transfers, not incoming as in gross product)
return getGrossProductPerMember(dto);
}
@Override
public BigDecimal getSumOfTransactions(final StatisticalDTO dto) {
final Map<String, Object> namedParameters = new HashMap<String, Object>();
// the query uses the same as getGrossProductPerMember, because mysql appears to be much faster with this query than with a simple select
// sum(t.amount) without "group by".
final StringBuilder hql = new StringBuilder("select new " + Pair.class.getName());
hql.append("(m, sum(t.amount)) from Transfer t, Member m where 1=1 ");
hql.append(" and t.chargedBackBy is null and t.chargebackOf is null ");
hql.append(" and exists (select ma.id from MemberAccount ma where t.to = ma and m = ma.member) ");
appendGroupAndPaymentFilterAndPeriod(hql, namedParameters, dto);
// TransferType
if (dto.getTransferType() != null) {
hql.append(" and t.type = :transferType ");
namedParameters.put("transferType", dto.getTransferType());
}
hql.append(" group by m ");
final List<Pair<Member, BigDecimal>> sums = list(hql.toString(), namedParameters);
BigDecimal sumOfTransactions = BigDecimal.ZERO;
for (final Pair<Member, BigDecimal> item : sums) {
sumOfTransactions = sumOfTransactions.add(item.getSecond());
}
return sumOfTransactions;
}
// TEST PERFORMANCE
@Override
public BigDecimal getSumOfTransactionsRest(final TransferQuery query) {
final StringBuilder hql = new StringBuilder("select sum(t.amount) from Transfer t where 1=1 ");
hql.append(" and t.chargedBackBy is null and t.chargebackOf is null ");
final Map<String, Object> namedParameters = new HashMap<String, Object>();
// Period
HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "t.processDate", query.getPeriod());
// From AccountType
if (query.getFromAccountType() != null) {
hql.append(" and t.from.type = :fromAccountType");
namedParameters.put("fromAccountType", query.getFromAccountType());
}
// To AccountType
if (query.getToAccountType() != null) {
hql.append(" and t.to.type = :toAccountType");
namedParameters.put("toAccountType", query.getToAccountType());
}
// TransferType not in the collection of payment filters
// (that is the meaning of the word 'rest' on the name of the method)
final Collection<PaymentFilter> paymentFilters = query.getPaymentFilters();
if (paymentFilters != null && !CollectionUtils.isEmpty(paymentFilters)) {
final Set<TransferType> transferTypesSet = new HashSet<TransferType>();
for (final PaymentFilter paymentFilter : paymentFilters) {
transferTypesSet.addAll(paymentFilter.getTransferTypes());
}
hql.append(" and t.type not in (:transferTypes) ");
namedParameters.put("transferTypes", transferTypesSet);
}
final BigDecimal sumOfTransactions = uniqueResult(hql.toString(), namedParameters);
if (sumOfTransactions == null) {
return BigDecimal.ZERO;
} else {
return sumOfTransactions;
}
}
// used by key dev: transaction amounts and number of transactions.
@Override
public List<Number> getTransactionAmounts(final StatisticalDTO dto) {
final Map<String, Object> namedParameters = new HashMap<String, Object>();
final StringBuilder hql = new StringBuilder();
hql.append("select new " + Pair.class.getName() + "(t.id, t.amount)");
hql.append(" from Transfer t, Member m where 1=1 ");
hql.append(" and t.chargedBackBy is null and t.chargebackOf is null ");
hql.append(" and (exists (select ma.id from MemberAccount ma where t.to = ma and m = ma.member) ");
hql.append(" or exists (select ma.id from MemberAccount ma where t.from = ma and m = ma.member)) ");
appendGroupAndPaymentFilterAndPeriod(hql, namedParameters, dto);
final List<Pair<Long, BigDecimal>> pairList = list(hql.toString(), namedParameters);
// because using to and from's, duplicate id's may appear. Transferring to Set solves this.
final Set<Pair<Long, BigDecimal>> pairSet = new HashSet<Pair<Long, BigDecimal>>(pairList);
return ListOperations.getSecondNumberFromPairCollection(pairSet);
}
@Override
public BigDecimal getTransactionedAmountAt(final Calendar date, final Account account, final TransferType transferType) {
return getTransactionedAmountAt(date, null, account, transferType);
}
@Override
public BigDecimal getTransactionedAmountAt(Calendar date, final Operator operator, final Account account, final TransferType transferType) {
if (date == null) {
date = Calendar.getInstance();
}
final Map<String, Object> namedParameters = new HashMap<String, Object>();
StringBuilder hql = new StringBuilder("select sum(t.amount) from Transfer t where 1=1 ");
HibernateHelper.addInParameterToQuery(hql, namedParameters, "t.status", Payment.Status.PROCESSED, Payment.Status.PENDING, Payment.Status.SCHEDULED);
HibernateHelper.addParameterToQuery(hql, namedParameters, "t.from", account);
HibernateHelper.addParameterToQuery(hql, namedParameters, "t.type", transferType);
HibernateHelper.addParameterToQuery(hql, namedParameters, "t.by", operator);
HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "ifnull(t.processDate, t.date)", Period.day(date));
BigDecimal sum = uniqueResult(hql.toString(), namedParameters);
return BigDecimalHelper.nvl(sum);
}
@Override
public List<Transfer> getTransfers(final Currency currency, final Account account, final Period period, final Transfer sinceTransfer, final boolean includeChargebacks, final Integer maxResults) {
final StringBuilder hql = new StringBuilder();
if (sinceTransfer != null && sinceTransfer.getProcessDate() == null) {
throw new IllegalArgumentException("transfer must be processed");
}
final Map<String, Object> namedParameters = new HashMap<String, Object>();
hql.append(" from Transfer t ");
hql.append(" where 1=1 ");
hql.append(" and t.processDate is not null ");
if (currency != null) {
namedParameters.put("currency", currency);
hql.append(" and t.to.type.currency = :currency ");
}
if (account != null) {
namedParameters.put("account", account);
hql.append(" and (t.from = :account or t.to = :account) ");
}
HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "t.processDate", period);
if (sinceTransfer != null) {
namedParameters.put("id", sinceTransfer.getId());
namedParameters.put("startDate", sinceTransfer.getProcessDate());
hql.append(" and ( (t.processDate > :startDate) or ( (t.processDate = :startDate) and (t.id > :id) ) ) ");
}
if (!includeChargebacks) {
hql.append(" and t.chargedBackBy is null and t.chargebackOf is null ");
}
hql.append(" order by t.processDate, t.id");
List<Transfer> transfers;
if (maxResults != null) {
transfers = list(ResultType.LIST, hql.toString(), namedParameters, PageParameters.max(maxResults));
} else {
transfers = list(hql.toString(), namedParameters);
}
return transfers;
}
@Override
public boolean hasTransfers(final Account account) {
Map<String, Account> params = Collections.singletonMap("account", account);
PageParameters pageParameters = PageParameters.max(1);
String hql = "select t.id from Transfer t where t.from = :account or t.to = :account";
List<?> list = list(ResultType.LIST, hql, params, pageParameters);
return !list.isEmpty();
}
@Override
public Transfer loadTransferByTraceNumber(final String traceNumber, final Long clientId, final Relationship... fetch) {
final Map<String, Object> namedParameters = new HashMap<String, Object>();
List<Relationship> toFetch = ArrayUtils.isEmpty(fetch) ? null : Arrays.asList(fetch);
final StringBuilder hql = HibernateHelper.getInitialQuery(getEntityType(), "t", toFetch);
HibernateHelper.addParameterToQuery(hql, namedParameters, "traceNumber", traceNumber);
HibernateHelper.addParameterToQuery(hql, namedParameters, "clientId", clientId);
return uniqueResult(hql.toString(), namedParameters);
}
@Override
public List<SimpleTransferVO> paymentVOs(final Account account, final Period period) throws DaoException {
final StringBuilder hql = new StringBuilder();
final Map<String, Object> namedParameters = new HashMap<String, Object>();
namedParameters.put("account", account);
hql.append("select new ").append(SimpleTransferVO.class.getName()).append("(t.date, case t.from when :account then -t.amount else t.amount end)");
hql.append(" from ").append(getEntityType().getName()).append(" t");
hql.append(" where (t.from = :account or t.to = :account) ");
HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "t.date", period);
hql.append(" order by t.date");
return list(hql.toString(), namedParameters);
}
@Override
public List<Transfer> search(final TransferQuery query) {
final Map<String, Object> namedParameters = new HashMap<String, Object>();
final StringBuilder hql = new StringBuilder();
hql.append(" select t");
hql.append(" from Loan l right join l.transfer t ");
hibernateCustomFieldHandler.appendJoins(hql, "t.customValues", query.getCustomValues());
HibernateHelper.appendJoinFetch(hql, Transfer.class, "t", query.getFetch());
hql.append(" where 1=1");
if (!buildSearchQuery(query, hql, namedParameters)) {
return Collections.emptyList();
}
return list(query, hql.toString(), namedParameters);
}
@Override
public List<Transfer> searchTransfersAwaitingAuthorization(final TransfersAwaitingAuthorizationQuery query) {
final Map<String, Object> namedParameters = new HashMap<String, Object>();
final StringBuilder hql = new StringBuilder();
hql.append(" select t from Transfer t join t.nextAuthorizationLevel l ");
HibernateHelper.appendJoinFetch(hql, getEntityType(), "t", query.getFetch());
hql.append(" where 1=1");
Element authorizer = query.getAuthorizer();
if (authorizer == null) {
return Collections.emptyList();
}
// Set common parameters
authorizer = getFetchDao().fetch(authorizer, Element.Relationships.GROUP);
for (final Authorizer auth : Authorizer.values()) {
namedParameters.put(auth.name(), auth);
}
namedParameters.put("authorizer", authorizer);
// The payment must be top-level
hql.append(" and t.parent is null");
// Status is PENDING and process date is null
hql.append(" and t.processDate is null and t.status = :status ");
namedParameters.put("status", Payment.Status.PENDING);
// Filter by authorizer
if (authorizer instanceof Administrator) {
final Administrator administrator = (Administrator) authorizer;
hql.append(" and l.authorizer in (:ADMIN, :BROKER)");
hql.append(" and :adminGroup in elements(l.adminGroups)");
namedParameters.put("adminGroup", administrator.getAdminGroup());
} else if (authorizer instanceof Operator) {
hql.append(" and ((l.authorizer = :RECEIVER and exists (");
hql.append(" select ma.id from MemberAccount ma, Operator o where ma = t.to and o.member = ma.member and o = :authorizer");
hql.append(" )) or (l.authorizer = :PAYER and exists (");
hql.append(" select ma.id from MemberAccount ma, Operator o where ma = t.from and o.member = ma.member and o = :authorizer");
hql.append(" ))) ");
} else {
hql.append(" and ((l.authorizer = :BROKER and exists(");
hql.append(" select ma.id from MemberAccount ma where ma = t.from and ma.member.broker = :authorizer");
hql.append(" )) or (l.authorizer = :RECEIVER and exists (");
hql.append(" select ma.id from MemberAccount ma where ma = t.to and ma.member = :authorizer");
hql.append(" )) or (l.authorizer = :PAYER and exists (");
hql.append(" select ma.id from MemberAccount ma where ma = t.from and ma.member = :authorizer");
hql.append(")))");
}
// Ensures that when the authorizer has already authorized once, the same transfer is not returned
hql.append(" and not exists (select a.id from TransferAuthorization a where a.transfer = t and a.by = :authorizer)");
// Add the from member
final Member member = query.getMember();
if (member != null) {
hql.append(" and exists (select ma.id from MemberAccount ma where ma.member = :member and (ma = t.from or ma = t.to))");
namedParameters.put("member", member);
}
// Add the payment filter
PaymentFilter paymentFilter = query.getPaymentFilter();
if (paymentFilter != null) {
paymentFilter = getFetchDao().fetch(paymentFilter, PaymentFilter.Relationships.TRANSFER_TYPES);
HibernateHelper.addInParameterToQuery(hql, namedParameters, "t.type", paymentFilter.getTransferTypes());
}
// Add the other filters
HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "t.date", query.getPeriod());
HibernateHelper.addParameterToQuery(hql, namedParameters, "t.type", query.getTransferType());
HibernateHelper.addLikeParameterToQuery(hql, namedParameters, "t.transactionNumber", query.getTransactionNumber());
HibernateHelper.appendOrder(hql, "t.date desc");
return list(query, hql.toString(), namedParameters);
}
public void setAccountDao(final AccountDAO accountDao) {
this.accountDao = accountDao;
}
public void setHibernateCustomFieldHandler(final HibernateCustomFieldHandler hibernateCustomFieldHandler) {
this.hibernateCustomFieldHandler = hibernateCustomFieldHandler;
}
@Override
public Transfer updateAuthorizationData(final Long id, final Transfer.Status status, final AuthorizationLevel nextAuthorizationLevel, final Calendar processDate, final Rated rates) {
final Transfer transfer = load(id);
transfer.setStatus(status);
transfer.setNextAuthorizationLevel(nextAuthorizationLevel);
transfer.setProcessDate(processDate);
if (rates != null) {
// if rates are set, the processDate may not be null
if ((rates.getEmissionDate() != null || rates.getExpirationDate() != null) && transfer.getProcessDate() == null) {
throw new IllegalArgumentException("rates can only be set if processDate on the transfer is NOT null. ");
}
transfer.setEmissionDate(rates.getEmissionDate());
transfer.setExpirationDate(rates.getExpirationDate());
transfer.setiRate(rates.getiRate());
}
return update(transfer);
}
@Override
public Transfer updateChargeBack(final Transfer transfer, final Transfer chargeback) {
transfer.setChargedBackBy(chargeback);
return update(transfer);
}
@Override
public Transfer updateExternalTransfer(final Long id, final ExternalTransfer externalTransfer) {
final Transfer transfer = load(id);
transfer.setExternalTransfer(externalTransfer);
return update(transfer);
}
@Override
public Transfer updateStatus(final Long id, final Payment.Status status) {
final Transfer transfer = load(id);
transfer.setStatus(status);
if (status != Payment.Status.PROCESSED) {
transfer.setProcessDate(null);
}
return update(transfer);
}
@Override
public Transfer updateTransactionNumber(final Long id, final String transactionNumber) {
final Transfer transfer = load(id);
transfer.setTransactionNumber(transactionNumber);
return update(transfer);
}
/**
* convenience method for a very often repeated block of hql query append statements. It appends the group filter, payment filter and period to a
* hql StringBuilder object for a query.
*
* @param hql
* @param namedParameters
* @param dto
*/
private void appendGroupAndPaymentFilterAndPeriod(final StringBuilder hql, final Map<String, Object> namedParameters, final StatisticalDTO dto) {
// Period
HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "t.processDate", dto.getPeriod());
// PaymentFilter
if (dto.getPaymentFilter() != null) {
hql.append(" and exists (select 1 from " + PaymentFilter.class.getName() + " pf where pf = :filter and t.type in elements(pf.transferTypes)) ");
namedParameters.put("filter", dto.getPaymentFilter());
}
// Members groups
if (!CollectionUtils.isEmpty(dto.getGroups())) {
hql.append(" and exists ");
hql.append(" ( select ghl.id ");
hql.append(" from GroupHistoryLog ghl ");
hql.append(" where ghl.element = m ");
hql.append(" and ghl.group in (:groups) ");
hql.append(" and ghl.period.begin < :end ");
hql.append(" and (ghl.period.end is null or ghl.period.end >= :begin) ");
hql.append(" and t.processDate between ghl.period.begin and ifnull(ghl.period.end, t.processDate) ");
hql.append(" ) ");
namedParameters.put("groups", dto.getGroups());
namedParameters.put("begin", dto.getPeriod().getBegin());
namedParameters.put("end", dto.getPeriod().getEnd());
}
}
@SuppressWarnings("unchecked")
private boolean buildSearchQuery(final TransferQuery query, final StringBuilder hql, final Map<String, Object> namedParameters) {
// hql.append(" and not exists (select pas.id from PendingAccountStatus pas where pas.transfer = t)");
HibernateHelper.addParameterToQuery(hql, namedParameters, "t.type.requiresAuthorization", query.getRequiresAuthorization());
HibernateHelper.addParameterToQuery(hql, namedParameters, "t.status", query.getStatus());
HibernateHelper.addLikeParameterToQuery(hql, namedParameters, "t.description", query.getDescription());
HibernateHelper.addLikeParameterToQuery(hql, namedParameters, "t.transactionNumber", query.getTransactionNumber());
HibernateHelper.addParameterToQuery(hql, namedParameters, "t.loanPayment", query.getLoanPayment());
HibernateHelper.addParameterToQuery(hql, namedParameters, "t.parent", query.getParent());
HibernateHelper.addParameterToQuery(hql, namedParameters, "t.type", query.getTransferType());
HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "ifnull(t.processDate, t.date)", query.getPeriod());
if (query.isRootOnly()) {
hql.append(" and t.parent is null");
}
if (query.getLoanTransfer() != null) {
if (query.getLoanTransfer()) {
hql.append(" and l is not null");
} else {
hql.append(" and l is null");
}
}
// By conciliation status
if (query.getConciliated() != null) {
hql.append(" and t.externalTransfer is " + (query.getConciliated() ? "not" : "") + " null");
}
// By owner
if (query.getOwner() != null) {
// Load the account
Collection<Account> accounts;
if (query.getType() == null) {
AccountQuery aq = new AccountQuery();
aq.setOwner(query.getOwner());
accounts = (Collection<Account>) accountDao.search(aq);
} else {
final Account account = accountDao.load(query.getOwner(), query.getType());
accounts = Collections.singleton(account);
}
namedParameters.put("accounts", accounts);
if (query.getMember() != null) {
// Load the related member accounts
final AccountQuery otherAccountsQuery = new AccountQuery();
otherAccountsQuery.setOwner(query.getMember());
final List<? extends Account> otherAccounts = accountDao.search(otherAccountsQuery);
if (otherAccounts.isEmpty()) {
// No accounts - ensure nothing will be returned
return false;
} else {
hql.append(" and ((t.from in (:accounts) and t.to in (:relatedAccounts)) or (t.to in (:accounts) and t.from in (:relatedAccounts)))");
namedParameters.put("relatedAccounts", otherAccounts);
}
} else {
hql.append(" and (t.from in (:accounts) or t.to in (:accounts))");
}
// Use the groups / group filters
Collection<MemberGroup> groups = new HashSet<MemberGroup>();
if (CollectionUtils.isNotEmpty(query.getGroupFilters())) {
// Get the groups from group filters
for (GroupFilter groupFilter : query.getGroupFilters()) {
if (groupFilter != null && groupFilter.isPersistent()) {
groupFilter = getFetchDao().fetch(groupFilter, GroupFilter.Relationships.GROUPS);
groups.addAll(groupFilter.getGroups());
}
}
}
if (CollectionUtils.isNotEmpty(query.getGroups())) {
// Specific groups
if (!groups.isEmpty()) {
// No group filters: use group alone
groups.retainAll(query.getGroups());
} else {
// Filter the groups from group filters with the specified groups
groups = query.getGroups();
}
}
if (!groups.isEmpty()) {
hql.append(" and ((t.to in (:accounts) and exists (select ma.id from MemberAccount ma where ma = t.from and ma.member.group in (:groups)))");
hql.append(" or (t.from in (:accounts) and exists (select ma.id from MemberAccount ma where ma = t.to and ma.member.group in (:groups))))");
namedParameters.put("groups", groups);
}
}
// From account owner
if (query.getFromAccountOwner() != null) {
final AccountQuery accountQuery = new AccountQuery();
accountQuery.setOwner(query.getFromAccountOwner());
final List<? extends Account> fromAccounts = accountDao.search(accountQuery);
hql.append(" and t.from in (:fromAccounts) ");
namedParameters.put("fromAccounts", fromAccounts);
}
// To account owner
if (query.getToAccountOwner() != null) {
final AccountQuery accountQuery = new AccountQuery();
accountQuery.setOwner(query.getToAccountOwner());
final List<? extends Account> toAccounts = accountDao.search(accountQuery);
hql.append(" and t.to in (:toAccounts) ");
namedParameters.put("toAccounts", toAccounts);
}
// PaymentFilter
final Collection<PaymentFilter> paymentFilters = query.getPaymentFilters();
if (CollectionUtils.isNotEmpty(paymentFilters)) {
// Get all TTs from all those payment filters
final String ttHql = "from TransferType tt where exists ("
+ " select 1"
+ " from PaymentFilter pf"
+ " where pf in (:pfs)"
+ " and tt in elements(pf.transferTypes))";
final List<TransferType> transferTypes = list(ttHql, Collections.singletonMap("pfs", paymentFilters));
HibernateHelper.addInParameterToQuery(hql, namedParameters, "t.type", transferTypes);
}
if (query.getExcludeTransferType() != null) {
hql.append(" and t.type != :excludeTransferType ");
namedParameters.put("excludeTransferType", query.getExcludeTransferType());
}
// Operated by
if (query.getBy() != null) {
hql.append(" and (t.by = :by or t.receiver = :by)");
namedParameters.put("by", query.getBy());
}
// Custom fields
hibernateCustomFieldHandler.appendConditions(hql, namedParameters, query.getCustomValues());
// Set the order
if (!query.isUnordered()) {
final List<String> orders = new ArrayList<String>();
// Order by date ...
String order = "ifnull(t.processDate, t.date)";
if (query.isReverseOrder()) {
order += " desc";
}
orders.add(order);
// ... then by id, to ensure that payments in the same second are ordered correctly
order = "t.id";
if (query.isReverseOrder()) {
order += " desc";
}
orders.add(order);
HibernateHelper.appendOrder(hql, orders);
}
return true;
}
}