/* 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.Calendar; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import nl.strohalm.cyclos.access.AdminSystemPermission; import nl.strohalm.cyclos.dao.accounts.AccountDAO; import nl.strohalm.cyclos.dao.accounts.AccountLimitLogDAO; import nl.strohalm.cyclos.dao.accounts.AccountTypeDAO; import nl.strohalm.cyclos.dao.groups.GroupDAO; import nl.strohalm.cyclos.entities.Relationship; import nl.strohalm.cyclos.entities.access.Channel; import nl.strohalm.cyclos.entities.accounts.AccountLimitLog; import nl.strohalm.cyclos.entities.accounts.AccountOwner; import nl.strohalm.cyclos.entities.accounts.AccountType; import nl.strohalm.cyclos.entities.accounts.AccountTypeQuery; import nl.strohalm.cyclos.entities.accounts.MemberAccountType; import nl.strohalm.cyclos.entities.accounts.MemberGroupAccountSettings; 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.transactions.TransferType; import nl.strohalm.cyclos.entities.accounts.transactions.TransferTypeQuery; 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.groups.OperatorGroup; 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.services.fetch.FetchServiceLocal; import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal; import nl.strohalm.cyclos.services.transactions.TransactionContext; import nl.strohalm.cyclos.services.transfertypes.TransferTypeServiceLocal; import nl.strohalm.cyclos.utils.access.LoggedUser; import nl.strohalm.cyclos.utils.cache.Cache; import nl.strohalm.cyclos.utils.cache.CacheCallback; import nl.strohalm.cyclos.utils.cache.CacheManager; import nl.strohalm.cyclos.utils.query.PageHelper; import nl.strohalm.cyclos.utils.validation.Validator; import org.apache.commons.collections.CollectionUtils; /** * Implementation for account type service * @author luis */ public class AccountTypeServiceImpl implements AccountTypeServiceLocal { private static final String ALL_KEY = "_ALL_"; private TransferTypeServiceLocal transferTypeService; private AccountDAO accountDao; private AccountTypeDAO accountTypeDao; private AccountLimitLogDAO accountLimitLogDao; private GroupDAO groupDao; private FetchServiceLocal fetchService; private CacheManager cacheManager; private PermissionServiceLocal permissionService; @Override public void clearCache() { getCache().clear(); } @Override public MemberAccountType getDefault(MemberGroup group, final Relationship... fetch) { group = fetchService.fetch(group, MemberGroup.Relationships.ACCOUNT_SETTINGS); Collection<MemberGroupAccountSettings> accountSettings = group.getAccountSettings(); MemberGroupAccountSettings defaultAccount = null; if (CollectionUtils.isNotEmpty(accountSettings)) { accountSettings = fetchService.fetch(accountSettings, MemberGroupAccountSettings.Relationships.ACCOUNT_TYPE); for (final MemberGroupAccountSettings current : accountSettings) { if (current.isDefault()) { // Found the default account defaultAccount = current; break; } } if (defaultAccount == null) { // None found: get the first one defaultAccount = accountSettings.iterator().next(); } } return defaultAccount == null ? null : fetchService.fetch(defaultAccount.getAccountType(), fetch); } @Override public Map<MemberAccountType, BigDecimal> getMemberAccountTypesBalance(final Collection<MemberAccountType> types, final Collection<MemberGroup> groups, final Calendar timePoint) { Map<MemberAccountType, BigDecimal> balances = new TreeMap<MemberAccountType, BigDecimal>(); for (final MemberAccountType type : types) { final BigDecimal balance = accountTypeDao.getBalance(type, groups, timePoint); balances.put(fetchService.fetch(type, AccountType.Relationships.CURRENCY), balance); } return balances; } @Override public Map<SystemAccountType, BigDecimal> getSystemAccountTypesBalance(final Collection<SystemAccountType> types, final Calendar timePoint) { Map<SystemAccountType, BigDecimal> balances = new TreeMap<SystemAccountType, BigDecimal>(); for (final SystemAccountType type : types) { final BigDecimal balance = accountTypeDao.getBalance(type, timePoint); balances.put(fetchService.fetch(type, AccountType.Relationships.CURRENCY), balance); } return balances; } @SuppressWarnings("unchecked") @Override public Collection<AccountType> getVisibleAccountTypes() { if (LoggedUser.isSystem()) { return (Collection<AccountType>) listAll(); } if (!LoggedUser.hasUser()) { // Not system and no user - nothing is visible return Collections.emptyList(); } final Group group = LoggedUser.group(); return getCache().get("_VISIBLE_" + group.getId(), new CacheCallback() { @Override public Object retrieve() { Collection<AccountType> result; if (permissionService.permission().admin(AdminSystemPermission.ACCOUNTS_VIEW).hasPermission()) { result = (Collection<AccountType>) listAll(); } else { if (LoggedUser.isOperator()) { OperatorGroup group = LoggedUser.group(); result = fetchService.fetch(group, OperatorGroup.Relationships.CAN_VIEW_INFORMATION_OF).getCanViewInformationOf(); } else { MemberAccountTypeQuery memberQuery = new MemberAccountTypeQuery(); memberQuery.setRelatedToGroups(permissionService.getManagedMemberGroups()); result = (Collection<AccountType>) search(memberQuery); // A logged admin can see both system and member account types if (LoggedUser.isAdministrator()) { AdminGroup group = LoggedUser.group(); Collection<SystemAccountType> systemTypes = fetchService.fetch(group, AdminGroup.Relationships.VIEW_INFORMATION_OF).getViewInformationOf(); result = CollectionUtils.union(result, systemTypes); } } } return fetchService.fetch(result, AccountType.Relationships.CURRENCY); } }); } @Override public boolean hasAuthorizedPayments(AccountType accountType) { accountType = fetchService.fetch(accountType, AccountType.Relationships.FROM_TRANSFER_TYPES); for (final TransferType transferType : accountType.getFromTransferTypes()) { if (transferType.isRequiresAuthorization()) { return true; } } return false; } @Override public List<? extends AccountType> listAll() { return getCache().get(ALL_KEY, new CacheCallback() { @Override public Object retrieve() { return accountTypeDao.listAll(); } }); } @Override public Collection<AccountType> load(final Collection<Long> ids) { Collection<AccountType> accountTypes = new ArrayList<AccountType>(ids.size()); for (Long id : ids) { accountTypes.add(load(id)); } return accountTypes; } @Override public AccountType load(final Long id) { return getCache().get(id, new CacheCallback() { @Override public Object retrieve() { final AccountType accountType = accountTypeDao.load(id, AccountType.Relationships.CURRENCY, SystemAccountType.Relationships.EXTERNAL_ACCOUNTS); fillSystemLimits(accountType); return accountType; } }); } @Override public int remove(final Long... ids) { for (final Long id : ids) { final AccountType accountType = accountTypeDao.load(id); if (accountType instanceof SystemAccountType) { final SystemAccountType systemAccountType = ((SystemAccountType) accountType); final SystemAccount account = systemAccountType.getAccount(); systemAccountType.setAccount(null); accountTypeDao.update(systemAccountType); accountDao.delete(account.getId()); } } getCache().clear(); return accountTypeDao.delete(ids); } @Override public <AT extends AccountType> AT save(final AT accountType) { AT saved = null; validate(accountType); SystemAccount systemAccount = null; if (accountType.isTransient()) { saved = accountTypeDao.insert(accountType); if (saved instanceof SystemAccountType) { // Create the system account now final SystemAccountType systemAccountType = ((SystemAccountType) accountType); systemAccount = new SystemAccount(); systemAccount.setCreationDate(Calendar.getInstance()); systemAccount.setCreditLimit(systemAccountType.getCreditLimit()); systemAccount.setUpperCreditLimit(systemAccountType.getUpperCreditLimit()); systemAccount.setType(saved); systemAccount.setOwnerName(saved.getName()); systemAccount = accountDao.insert(systemAccount); // Add permission to the admin group AdminGroup group = (AdminGroup) LoggedUser.group(); group = groupDao.load(group.getId(), AdminGroup.Relationships.VIEW_INFORMATION_OF); final Collection<SystemAccountType> systemAccountTypes = group.getViewInformationOf(); systemAccountTypes.add(systemAccountType); groupDao.update(group); } // Member accounts are created when an account type gets related to a group } else { if (accountType instanceof SystemAccountType) { final SystemAccountType currentAccountType = (SystemAccountType) accountTypeDao.load(accountType.getId(), SystemAccountType.Relationships.VIEWED_BY_GROUPS); final Collection<AdminGroup> viewedByGroups = new ArrayList<AdminGroup>(); if (currentAccountType.getViewedByGroups() != null) { viewedByGroups.addAll(currentAccountType.getViewedByGroups()); } final SystemAccountType systemAccountType = (SystemAccountType) accountType; systemAccountType.setViewedByGroups(viewedByGroups); // When updating a system account type, should update it's account too systemAccount = (SystemAccount) accountDao.load(SystemAccountOwner.instance(), systemAccountType); final BigDecimal oldLimit = systemAccount.getCreditLimit() == null ? null : systemAccount.getCreditLimit().abs(); final BigDecimal oldUpperLimit = systemAccount.getUpperCreditLimit(); // When there was a credit limit, and it has changed, we must update the account final BigDecimal newLimit = systemAccountType.getCreditLimit() == null ? null : systemAccountType.getCreditLimit().abs(); final BigDecimal newUpperLimit = systemAccountType.getUpperCreditLimit(); final boolean updateLimit = (newLimit != null && !newLimit.equals(oldLimit)) || ((newUpperLimit != null) && !newUpperLimit.equals(oldUpperLimit)); if (updateLimit) { systemAccount.setCreditLimit(newLimit); systemAccount.setUpperCreditLimit(newUpperLimit); } systemAccount.setOwnerName(systemAccountType.getName()); systemAccountType.setAccount(systemAccount); accountDao.update(systemAccount); if (updateLimit) { // Generate the log AccountLimitLog log = new AccountLimitLog(); log.setAccount(systemAccount); log.setBy((Administrator) LoggedUser.element()); log.setDate(Calendar.getInstance()); log.setCreditLimit(newLimit); log.setUpperCreditLimit(newUpperLimit); accountLimitLogDao.insert(log); } } saved = accountTypeDao.update(accountType); } if (systemAccount != null) { ((SystemAccountType) saved).setAccount(systemAccount); saved = accountTypeDao.update(saved); } getCache().clear(); return saved; } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public List<? extends AccountType> search(final AccountTypeQuery query) { if (query instanceof MemberAccountTypeQuery) { final MemberAccountTypeQuery memberQuery = (MemberAccountTypeQuery) query; final AccountOwner canPay = memberQuery.getCanPay(); if (canPay != null) { Group group = null; if (canPay instanceof Member) { final Member member = fetchService.fetch((Member) canPay, Element.Relationships.GROUP); group = member.getGroup(); } Member owner = memberQuery.getOwner(); if (owner == null && LoggedUser.hasUser() && LoggedUser.isMember()) { owner = LoggedUser.element(); } // Can pay is handled differently: let's reuse the TransferTypeService to check which accounts have possible payment types final List<MemberAccountType> accountTypes = new ArrayList<MemberAccountType>(); // I know: double casts looks awful, but... for (final MemberAccountType accountType : (List<MemberAccountType>) (List) accountTypeDao.search(new MemberAccountTypeQuery())) { final TransferTypeQuery transferTypeQuery = new TransferTypeQuery(); transferTypeQuery.setPageForCount(); transferTypeQuery.setContext(TransactionContext.PAYMENT); transferTypeQuery.setChannel(Channel.WEB); transferTypeQuery.setUsePriority(true); transferTypeQuery.setToAccountType(accountType); transferTypeQuery.setToOwner(owner); transferTypeQuery.setFromOwner(canPay); transferTypeQuery.setGroup(group); if (PageHelper.getTotalCount(transferTypeService.search(transferTypeQuery)) > 0) { accountTypes.add(accountType); } } return accountTypes; } } return accountTypeDao.search(query); } public void setAccountDao(final AccountDAO accountDao) { this.accountDao = accountDao; } public void setAccountLimitLogDao(final AccountLimitLogDAO accountLimitLogDao) { this.accountLimitLogDao = accountLimitLogDao; } public void setAccountTypeDao(final AccountTypeDAO accountTypeDao) { this.accountTypeDao = accountTypeDao; } public void setCacheManager(final CacheManager cacheManager) { this.cacheManager = cacheManager; } public void setFetchServiceLocal(final FetchServiceLocal fetchService) { this.fetchService = fetchService; } public void setGroupDao(final GroupDAO groupDao) { this.groupDao = groupDao; } public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) { this.permissionService = permissionService; } public void setTransferTypeServiceLocal(final TransferTypeServiceLocal transferTypeService) { this.transferTypeService = transferTypeService; } @Override public void validate(final AccountType accountType) { getValidator().validate(accountType); } private void fillSystemLimits(final AccountType accountType) { if (accountType instanceof SystemAccountType) { final SystemAccountType sat = (SystemAccountType) accountType; final SystemAccount account = sat.getAccount(); if (account != null) { BigDecimal creditLimit = account.getCreditLimit(); if (creditLimit != null) { sat.setCreditLimit(creditLimit.abs().negate()); } sat.setUpperCreditLimit(account.getUpperCreditLimit()); } } } private Cache getCache() { return cacheManager.getCache("cyclos.AccountTypes"); } private Validator getValidator() { final Validator validator = new Validator("accountType"); validator.property("name").required().maxLength(100); validator.property("description").maxLength(1000); validator.property("currency").required(); return validator; } }