/*
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.external;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import nl.strohalm.cyclos.dao.accounts.external.ExternalTransferDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.access.User;
import nl.strohalm.cyclos.entities.accounts.MemberAccount;
import nl.strohalm.cyclos.entities.accounts.SystemAccountOwner;
import nl.strohalm.cyclos.entities.accounts.external.ExternalTransfer;
import nl.strohalm.cyclos.entities.accounts.external.ExternalTransferAction;
import nl.strohalm.cyclos.entities.accounts.external.ExternalTransferImport;
import nl.strohalm.cyclos.entities.accounts.external.ExternalTransferQuery;
import nl.strohalm.cyclos.entities.accounts.external.ExternalTransferType;
import nl.strohalm.cyclos.entities.accounts.loans.Loan;
import nl.strohalm.cyclos.entities.accounts.transactions.Payment;
import nl.strohalm.cyclos.entities.customization.fields.MemberCustomField;
import nl.strohalm.cyclos.entities.customization.fields.MemberCustomFieldValue;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException;
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.services.accounts.external.exceptions.CannotDeleteExternalTransferException;
import nl.strohalm.cyclos.services.accounts.external.exceptions.CannotMarkExternalTransferAsCheckedException;
import nl.strohalm.cyclos.services.accounts.external.exceptions.CannotMarkExternalTransferAsUncheckedException;
import nl.strohalm.cyclos.services.customization.MemberCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.elements.ElementServiceLocal;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal;
import nl.strohalm.cyclos.services.transactions.LoanServiceLocal;
import nl.strohalm.cyclos.services.transactions.PaymentServiceLocal;
import nl.strohalm.cyclos.services.transactions.TransferDTO;
import nl.strohalm.cyclos.utils.CustomFieldHelper;
import nl.strohalm.cyclos.utils.MessageResolver;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.query.PageParameters;
import nl.strohalm.cyclos.utils.transactionimport.TransactionImportDTO;
import nl.strohalm.cyclos.utils.validation.PropertyValidation;
import nl.strohalm.cyclos.utils.validation.RequiredValidation;
import nl.strohalm.cyclos.utils.validation.ValidationError;
import nl.strohalm.cyclos.utils.validation.Validator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
/**
* Implementation for ExternalTransferService
*
* @author luis
*/
public class ExternalTransferServiceImpl implements ExternalTransferServiceLocal {
/**
* Validates the member as required when the action is not ignored
*
* @author luis
*/
private class RequiredWhenNotIgnoreValidation implements PropertyValidation {
private static final long serialVersionUID = -2629175142234917511L;
@Override
public ValidationError validate(final Object object, final Object property, final Object value) {
final ExternalTransfer transfer = (ExternalTransfer) object;
final ExternalTransferType type = fetchService.fetch(transfer.getType());
if (type == null) {
return null;
}
if (type.getAction() == ExternalTransferType.Action.IGNORE) {
return null;
} else {
return RequiredValidation.instance().validate(object, property, value);
}
}
}
private static final Relationship[] EXTERNAL_TRANSFER_FETCH = { RelationshipHelper.nested(ExternalTransfer.Relationships.TYPE, ExternalTransferType.Relationships.ACCOUNT), RelationshipHelper.nested(ExternalTransfer.Relationships.TYPE, ExternalTransferType.Relationships.TRANSFER_TYPE) };
private static final Relationship[] LOAN_FETCH = { RelationshipHelper.nested(Loan.Relationships.TRANSFER, Payment.Relationships.TO, MemberAccount.Relationships.MEMBER), RelationshipHelper.nested(ExternalTransfer.Relationships.TYPE, ExternalTransferType.Relationships.TRANSFER_TYPE) };
private static final Relationship[] TRANSFER_FETCH = { RelationshipHelper.nested(Payment.Relationships.FROM, MemberAccount.Relationships.MEMBER), RelationshipHelper.nested(Payment.Relationships.TO) };
private LoanServiceLocal loanService;
private FetchServiceLocal fetchService;
private PaymentServiceLocal paymentService;
private ElementServiceLocal elementService;
private MemberCustomFieldServiceLocal memberCustomFieldService;
private ExternalTransferTypeServiceLocal externalTransferTypeService;
private ExternalTransferDAO externalTransferDao;
private CustomFieldHelper customFieldHelper;
private PermissionServiceLocal permissionService;
private MessageResolver messageResolver;
@Override
public ExternalTransfer importNew(final ExternalTransferImport transferImport, final TransactionImportDTO dto) {
// Check if the logged user manages the member involved in the transaction.
Member member = resolveMember(dto);
if (member != null && !permissionService.manages(member)) {
// The imported member is not managed by the current admin. Assume empty
member = null;
}
final ExternalTransfer transfer = new ExternalTransfer();
transfer.setLineNumber(dto.getLineNumber());
transfer.setAccount(transferImport.getAccount());
transfer.setMember(resolveMember(dto));
transfer.setDate(dto.getDate());
transfer.setAmount(dto.getAmount());
transfer.setStatus(ExternalTransfer.Status.PENDING);
transfer.setTransferImport(transferImport);
transfer.setType(resolveType(transferImport, dto));
transfer.setDescription(dto.getDescription());
String comments = dto.getComments();
// If the member was null and there is data about him, create import comments
if (member == null) {
Long id = dto.getMemberId();
String username = dto.getMemberUsername();
Map.Entry<String, String> fieldValue = MapUtils.isEmpty(dto.getMemberFieldValues()) ? null : dto.getMemberFieldValues().entrySet().iterator().next();
String newComments = null;
if (id != null) {
newComments = messageResolver.message("externalTransferImport.error.importing.invalidMemberId", id);
} else if (StringUtils.isNotEmpty(username)) {
newComments = messageResolver.message("externalTransferImport.error.importing.invalidMemberUsername", username);
} else if (fieldValue != null) {
newComments = messageResolver.message("externalTransferImport.error.importing.invalidMemberField", fieldValue.getKey(), fieldValue.getValue());
}
if (comments != null) {
comments = newComments + "\n" + comments;
} else {
comments = newComments;
}
}
// If the payment type was null and there is a payment type code, create import comments
if (transfer.getType() == null && StringUtils.isNotEmpty(dto.getTypeCode())) {
String newComments = messageResolver.message("externalTransferImport.error.importing.invalidTypeCode", dto.getTypeCode());
if (comments != null) {
comments = newComments + "\n" + comments;
} else {
comments = newComments;
}
}
transfer.setComments(comments);
return externalTransferDao.insert(transfer);
}
@Override
public ExternalTransfer load(final Long id, final Relationship... fetch) {
return externalTransferDao.load(id, fetch);
}
@Override
public void performAction(final ExternalTransferAction action, final Long... ids) {
for (final Long id : ids) {
final ExternalTransfer externalTransfer = externalTransferDao.load(id, ExternalTransfer.Relationships.TYPE);
final ExternalTransfer.Status status = externalTransfer.getStatus();
if (action == ExternalTransferAction.DELETE) {
if (status != ExternalTransfer.Status.PENDING) {
throw new CannotDeleteExternalTransferException();
}
externalTransferDao.delete(id);
} else if (action == ExternalTransferAction.MARK_AS_CHECKED) {
if (status != ExternalTransfer.Status.PENDING || !externalTransfer.isComplete()) {
throw new CannotMarkExternalTransferAsCheckedException();
}
final ExternalTransferType type = externalTransfer.getType();
if (type != null && type.getAction() == ExternalTransferType.Action.IGNORE) {
// Ignored types are processed as soon as it's checked
externalTransfer.setStatus(ExternalTransfer.Status.PROCESSED);
} else {
externalTransfer.setStatus(ExternalTransfer.Status.CHECKED);
}
externalTransferDao.update(externalTransfer);
} else { // action == ExternalTransferAction.MARK_AS_UNCHECKED
if (status != ExternalTransfer.Status.CHECKED) {
throw new CannotMarkExternalTransferAsUncheckedException();
}
externalTransfer.setStatus(ExternalTransfer.Status.PENDING);
externalTransferDao.update(externalTransfer);
}
}
}
@Override
public int process(final Collection<ProcessExternalTransferDTO> dtos) throws UnexpectedEntityException {
if (CollectionUtils.isEmpty(dtos)) {
return 0;
}
int count = 0;
for (final ProcessExternalTransferDTO dto : dtos) {
final ExternalTransfer externalTransfer = fetchService.fetch(dto.getExternalTransfer(), EXTERNAL_TRANSFER_FETCH);
if (externalTransfer.getStatus() != ExternalTransfer.Status.CHECKED) {
throw new UnexpectedEntityException();
}
dto.setExternalTransfer(externalTransfer);
dto.setLoan(fetchService.fetch(dto.getLoan(), LOAN_FETCH));
dto.setTransfer(fetchService.fetch(dto.getTransfer(), TRANSFER_FETCH));
process(dto);
count++;
}
return count;
}
/**
* Resolve the member of the given DTO
*/
@Override
@SuppressWarnings("unchecked")
public Member resolveMember(final TransactionImportDTO dto) {
final Long id = dto.getMemberId();
final String username = dto.getMemberUsername();
final Map<String, String> fieldValues = dto.getMemberFieldValues();
Member member = null;
// Try by id
if (id != null) {
try {
member = (Member) elementService.load(id, Element.Relationships.GROUP);
} catch (final EntityNotFoundException e) {
// Id was not of a valid member - ignore it
}
}
// Try by username
if (member == null && StringUtils.isNotEmpty(username)) {
try {
member = (Member) elementService.loadUser(username, RelationshipHelper.nested(User.Relationships.ELEMENT, Element.Relationships.GROUP)).getElement();
} catch (final EntityNotFoundException e) {
// Username was not of a valid member - ignore it
}
}
// Try by custom fields
if (member == null && MapUtils.isNotEmpty(fieldValues)) {
final List<MemberCustomField> allMemberFields = memberCustomFieldService.list();
final Collection<MemberCustomFieldValue> values = customFieldHelper.buildValues(MemberCustomFieldValue.class, allMemberFields, fieldValues);
final MemberQuery query = new MemberQuery();
// Fetch a maximum of 2 members - we need a single one. If more than one found, none should be used
query.setPageParameters(new PageParameters(2, 0));
query.setCustomValues(values);
final Iterator<Member> members = ((List<Member>) elementService.search(query)).iterator();
if (members.hasNext()) {
// Get the first one...
member = members.next();
// ... but, if there's more than one, ignore it
if (members.hasNext()) {
member = null;
}
}
}
if (member != null && !member.getGroup().getStatus().isEnabled()) {
// Cannot be a removed member
member = null;
}
return member;
}
@Override
public ExternalTransfer save(final ExternalTransfer externalTransfer) {
validate(externalTransfer);
if (externalTransfer.isTransient()) {
externalTransfer.setStatus(ExternalTransfer.Status.PENDING);
return externalTransferDao.insert(externalTransfer);
} else {
return externalTransferDao.update(externalTransfer);
}
}
@Override
public List<ExternalTransfer> search(final ExternalTransferQuery query) {
return externalTransferDao.search(query);
}
public void setCustomFieldHelper(final CustomFieldHelper customFieldHelper) {
this.customFieldHelper = customFieldHelper;
}
public void setElementServiceLocal(final ElementServiceLocal elementService) {
this.elementService = elementService;
}
public void setExternalTransferDao(final ExternalTransferDAO externalTransferDao) {
this.externalTransferDao = externalTransferDao;
}
public void setExternalTransferTypeServiceLocal(final ExternalTransferTypeServiceLocal externalTransferTypeService) {
this.externalTransferTypeService = externalTransferTypeService;
}
public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
this.fetchService = fetchService;
}
public void setLoanServiceLocal(final LoanServiceLocal loanService) {
this.loanService = loanService;
}
public void setMemberCustomFieldServiceLocal(final MemberCustomFieldServiceLocal memberCustomFieldService) {
this.memberCustomFieldService = memberCustomFieldService;
}
public void setMessageResolver(final MessageResolver messageResolver) {
this.messageResolver = messageResolver;
}
public void setPaymentServiceLocal(final PaymentServiceLocal paymentService) {
this.paymentService = paymentService;
}
public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) {
this.permissionService = permissionService;
}
@Override
public void validate(final ExternalTransfer externalTransfer) {
getValidator().validate(externalTransfer);
}
private Validator getValidator() {
final Validator validator = new Validator("externalTransfer");
validator.property("account").required();
validator.property("type").required();
validator.property("date").required();
validator.property("amount").required();
validator.property("member").add(new RequiredWhenNotIgnoreValidation());
return validator;
}
private void process(final ProcessExternalTransferDTO dto) {
final ExternalTransfer externalTransfer = dto.getExternalTransfer();
final ExternalTransferType type = externalTransfer.getType();
switch (type.getAction()) {
case GENERATE_SYSTEM_PAYMENT:
case GENERATE_MEMBER_PAYMENT:
final boolean toMember = type.getAction() == ExternalTransferType.Action.GENERATE_MEMBER_PAYMENT;
final TransferDTO payment = new TransferDTO();
payment.setAutomatic(true);
payment.setDate(dto.getDate());
payment.setFromOwner(toMember ? SystemAccountOwner.instance() : externalTransfer.getMember());
payment.setToOwner(toMember ? externalTransfer.getMember() : SystemAccountOwner.instance());
payment.setTransferType(type.getTransferType());
payment.setAmount(dto.getAmount());
payment.setDescription(externalTransfer.getDescription() != null ? externalTransfer.getDescription() : type.getName());
payment.setExternalTransfer(externalTransfer);
paymentService.insertWithNotification(payment);
break;
case DISCARD_LOAN:
loanService.discardByExternalTransfer(dto.getLoan(), externalTransfer);
break;
case CONCILIATE_PAYMENT:
paymentService.conciliate(dto.getTransfer(), externalTransfer);
break;
}
externalTransfer.setStatus(ExternalTransfer.Status.PROCESSED);
externalTransferDao.update(externalTransfer);
}
/**
* Resolve the type of the given DTO on the given account
*/
private ExternalTransferType resolveType(final ExternalTransferImport transferImport, final TransactionImportDTO dto) {
try {
return externalTransferTypeService.load(transferImport.getAccount(), dto.getTypeCode());
} catch (final Exception e) {
return null;
}
}
}