/* 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.Calendar; import nl.strohalm.cyclos.dao.accounts.AccountDAO; import nl.strohalm.cyclos.dao.accounts.MemberGroupAccountSettingsDAO; import nl.strohalm.cyclos.dao.accounts.transactions.TransferDAO; import nl.strohalm.cyclos.dao.members.ElementDAO; 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.SystemAccountOwner; import nl.strohalm.cyclos.entities.accounts.transactions.TransferType; import nl.strohalm.cyclos.entities.alerts.MemberAlert; 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.services.alerts.AlertServiceLocal; import nl.strohalm.cyclos.services.fetch.FetchServiceLocal; import nl.strohalm.cyclos.services.transactions.GrantSinglePaymentLoanDTO; import nl.strohalm.cyclos.services.transactions.LoanServiceLocal; import nl.strohalm.cyclos.services.transactions.PaymentServiceLocal; import nl.strohalm.cyclos.services.transactions.TransactionContext; import nl.strohalm.cyclos.services.transactions.TransferDTO; import nl.strohalm.cyclos.utils.RelationshipHelper; import nl.strohalm.cyclos.utils.TimePeriod; import nl.strohalm.cyclos.utils.TransactionHelper; import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData; import nl.strohalm.cyclos.utils.transaction.TransactionCommitListener; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; /** * Handles member account creation / removal * @author luis */ public class MemberAccountHandler { private static final float PRECISION_DELTA = 0.0001F; private ElementDAO elementDao; private AccountDAO accountDao; private TransferDAO transferDao; private AccountServiceLocal accountService; private PaymentServiceLocal paymentService; private AlertServiceLocal alertService; private LoanServiceLocal loanService; private MemberGroupAccountSettingsDAO memberGroupAccountSettingsDao; private FetchServiceLocal fetchService; private TransactionHelper transactionHelper; /** * Activate the member account if it exists, or create if it doesn't */ public MemberAccount activate(final Member member, final MemberAccountType type) { if (member == null || member.isTransient() || type == null || type.isTransient()) { throw new UnexpectedEntityException(); } MemberAccount account; try { account = (MemberAccount) accountDao.load(member, type); account = activate(account); } catch (final EntityNotFoundException e) { account = create(member, type); } return account; } /** * Activate the member account */ public MemberAccount activate(final MemberAccount account) { // The account exists, mark as active account.setStatus(MemberAccount.Status.ACTIVE); account.setAction(null); accountDao.update(account); // Ensure the member is activated activateMember(account.getMember()); return account; } /** * Deactivate a member account, or remove it if it has no transactions */ public void deactivate(final Member member, final MemberAccountType type, final boolean enforceZeroBalance) { try { final MemberAccount account = (MemberAccount) accountDao.load(member, type); deactivate(account, enforceZeroBalance); } catch (final EntityNotFoundException e) { // Ok, the account already didn't exist } } /** * When there are no transfers, remove the account. When there are transfers and the balance is zero, set the status to inactive. Otherwise, when * balance is not zero, throws UnexpectedEntityException */ public void deactivate(final MemberAccount account, final boolean enforceZeroBalance) { boolean hasTransfers = transferDao.hasTransfers(account); final boolean hasCreditLimit = Math.abs(account.getCreditLimit().floatValue()) > PRECISION_DELTA; if (hasTransfers || hasCreditLimit) { BigDecimal balance = accountService.getBalance(new AccountDateDTO(account)); if (enforceZeroBalance && Math.abs(balance.floatValue()) > PRECISION_DELTA) { // When there is non zero balance in the account, throw an error throw new UnexpectedEntityException(); } account.setStatus(MemberAccount.Status.INACTIVE); account.setAction(null); accountDao.update(account); } else { accountDao.delete(account.getId()); } } public void setAccountDao(final AccountDAO accountDao) { this.accountDao = accountDao; } public void setAccountServiceLocal(final AccountServiceLocal accountService) { this.accountService = accountService; } public void setAccountSettingsDao(final MemberGroupAccountSettingsDAO accountSettingsDao) { memberGroupAccountSettingsDao = accountSettingsDao; } public void setAlertServiceLocal(final AlertServiceLocal alertService) { this.alertService = alertService; } public void setElementDao(final ElementDAO elementDao) { this.elementDao = elementDao; } public void setFetchServiceLocal(final FetchServiceLocal fetchService) { this.fetchService = fetchService; } public void setLoanServiceLocal(final LoanServiceLocal loanService) { this.loanService = loanService; } public void setMemberGroupAccountSettingsDao(final MemberGroupAccountSettingsDAO memberGroupAccountSettingsDao) { this.memberGroupAccountSettingsDao = memberGroupAccountSettingsDao; } public void setPaymentServiceLocal(final PaymentServiceLocal paymentService) { this.paymentService = paymentService; } public void setTransactionHelper(final TransactionHelper transactionHelper) { this.transactionHelper = transactionHelper; } public void setTransferDao(final TransferDAO transferDao) { this.transferDao = transferDao; } private void activateMember(final Member member) { if (member.getActivationDate() == null) { member.setActivationDate(Calendar.getInstance()); elementDao.update(member); } } /** * Create a member account */ private MemberAccount create(final Member m, final MemberAccountType at) { final Member member = fetchService.fetch(m, Element.Relationships.USER, Element.Relationships.GROUP); final MemberAccountType accountType = fetchService.fetch(at); final MemberGroupAccountSettings accountSettings; try { accountSettings = memberGroupAccountSettingsDao.load(member.getMemberGroup().getId(), accountType.getId()); } catch (EntityNotFoundException e) { // The member has been moved to another group before the account is activated. Do nothing return null; } // Create the account final MemberAccount account = new MemberAccount(); account.setCreationDate(Calendar.getInstance()); account.setCreditLimit(accountSettings.getDefaultCreditLimit()); account.setUpperCreditLimit(accountSettings.getDefaultUpperCreditLimit()); account.setType(accountType); account.setMember(member); account.setOwnerName(member.getUsername()); accountDao.insert(account); // Ensure the member is activated activateMember(member); // Check for initial credit final BigDecimal initialCredit = accountSettings.getInitialCredit(); final TransferType initialCreditTransferType = accountSettings.getInitialCreditTransferType(); final BigDecimal minimumPayment = paymentService.getMinimumPayment(); if (initialCredit != null && (initialCredit.compareTo(minimumPayment) > 0) && initialCreditTransferType != null) { // The initial credit is granted in a new transaction. So, when it fails, an alert is created instead of failing the account creation CurrentTransactionData.addTransactionCommitListener(new TransactionCommitListener() { @Override public void onTransactionCommit() { try { // Grant the initial credit transactionHelper.runInCurrentThread(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(final TransactionStatus status) { grantInitialCredit(account, initialCredit, initialCreditTransferType); } }); } catch (Exception e) { transactionHelper.runInCurrentThread(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(final TransactionStatus status) { alertService.create(member, MemberAlert.Alerts.INITIAL_CREDIT_FAILED, account.getType().getName()); } }); } } }); } return account; } private void grantInitialCredit(MemberAccount account, final BigDecimal amount, TransferType transferType) { account = fetchService.fetch(account, RelationshipHelper.nested(MemberAccount.Relationships.MEMBER, Element.Relationships.USER)); transferType = fetchService.fetch(transferType); if (transferType.isLoanType()) { Integer repaymentDays = transferType.getLoan().getRepaymentDays(); if (repaymentDays == null) { repaymentDays = 30; } final GrantSinglePaymentLoanDTO dto = new GrantSinglePaymentLoanDTO(); dto.setAutomatic(true); dto.setRepaymentDate(new TimePeriod(repaymentDays, TimePeriod.Field.DAYS).add(Calendar.getInstance())); dto.setMember(account.getMember()); dto.setAmount(amount); dto.setTransferType(transferType); dto.setDescription(transferType.getDescription()); loanService.insert(dto); } else { final TransferDTO dto = new TransferDTO(); dto.setContext(TransactionContext.AUTOMATIC); dto.setAmount(amount); dto.setTransferType(transferType); dto.setFromOwner(SystemAccountOwner.instance()); dto.setTo(account); dto.setDescription(transferType.getDescription()); paymentService.insertWithoutNotification(dto); } } }