/* 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.controls.accounts.details; import java.io.Serializable; import java.math.BigDecimal; import java.util.Calendar; import java.util.EnumSet; import java.util.List; import javax.servlet.http.HttpServletRequest; 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.annotations.Inject; import nl.strohalm.cyclos.controls.ActionContext; import nl.strohalm.cyclos.controls.BaseQueryAction; import nl.strohalm.cyclos.entities.EntityReference; import nl.strohalm.cyclos.entities.accounts.Account; import nl.strohalm.cyclos.entities.accounts.AccountOwner; import nl.strohalm.cyclos.entities.accounts.AccountStatus; import nl.strohalm.cyclos.entities.accounts.AccountType; import nl.strohalm.cyclos.entities.accounts.MemberAccount; import nl.strohalm.cyclos.entities.accounts.SystemAccountOwner; import nl.strohalm.cyclos.entities.accounts.SystemAccountType; import nl.strohalm.cyclos.entities.accounts.transactions.Payment; import nl.strohalm.cyclos.entities.accounts.transactions.PaymentFilter; import nl.strohalm.cyclos.entities.accounts.transactions.PaymentFilterQuery; import nl.strohalm.cyclos.entities.accounts.transactions.Transfer; import nl.strohalm.cyclos.entities.accounts.transactions.TransferQuery; import nl.strohalm.cyclos.entities.customization.fields.PaymentCustomField; import nl.strohalm.cyclos.entities.customization.fields.PaymentCustomFieldValue; import nl.strohalm.cyclos.entities.groups.AdminGroup; import nl.strohalm.cyclos.entities.groups.Group; import nl.strohalm.cyclos.entities.groups.GroupFilter; import nl.strohalm.cyclos.entities.groups.GroupFilterQuery; import nl.strohalm.cyclos.entities.groups.GroupQuery; import nl.strohalm.cyclos.entities.groups.MemberGroup; import nl.strohalm.cyclos.entities.members.Element; import nl.strohalm.cyclos.entities.members.Member; import nl.strohalm.cyclos.entities.members.OperatorQuery; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.entities.settings.events.LocalSettingsEvent; import nl.strohalm.cyclos.services.accounts.AccountDTO; import nl.strohalm.cyclos.services.accounts.AccountService; import nl.strohalm.cyclos.services.accounts.AccountTypeService; import nl.strohalm.cyclos.services.accounts.GetTransactionsDTO; import nl.strohalm.cyclos.services.customization.PaymentCustomFieldService; import nl.strohalm.cyclos.services.elements.ElementService; import nl.strohalm.cyclos.services.groups.GroupFilterService; import nl.strohalm.cyclos.services.permissions.PermissionService; import nl.strohalm.cyclos.services.transactions.PaymentService; import nl.strohalm.cyclos.services.transfertypes.PaymentFilterService; import nl.strohalm.cyclos.utils.CustomFieldHelper; import nl.strohalm.cyclos.utils.Period; import nl.strohalm.cyclos.utils.PropertyHelper; import nl.strohalm.cyclos.utils.RequestHelper; import nl.strohalm.cyclos.utils.TimePeriod; import nl.strohalm.cyclos.utils.TransformedIteratorList; import nl.strohalm.cyclos.utils.binding.BeanBinder; import nl.strohalm.cyclos.utils.binding.BeanCollectionBinder; import nl.strohalm.cyclos.utils.binding.DataBinder; import nl.strohalm.cyclos.utils.binding.DataBinderHelper; import nl.strohalm.cyclos.utils.binding.PropertyBinder; import nl.strohalm.cyclos.utils.binding.SimpleCollectionBinder; import nl.strohalm.cyclos.utils.conversion.AccountOwnerConverter; import nl.strohalm.cyclos.utils.conversion.ReferenceConverter; import nl.strohalm.cyclos.utils.conversion.Transformer; import nl.strohalm.cyclos.utils.query.QueryParameters; /** * Action used to retrieve the account history * @author luis */ public class AccountHistoryAction extends BaseQueryAction { /** * An account history entry, containing the current account and a transfer * @author luis */ public static class Entry implements Serializable { private static final long serialVersionUID = -4696245469424024391L; /** * Build the entry list */ public static List<Entry> build(final PermissionService permissionService, final ElementService elementService, final Account account, final List<Transfer> transfers, final boolean fetchMember) { final TransformTransferInEntry transformTransferInEntry = new TransformTransferInEntry(permissionService, elementService, account, fetchMember); return new TransformedIteratorList<Transfer, Entry>(transformTransferInEntry, transfers); } private final Account account; private final Account related; private final Long relatedMemberId; private final Transfer transfer; private final int signal; private Entry(final Account account, final Transfer transfer, final Account relatedAccount, final Long relatedMemberId) { this.transfer = transfer; this.account = account; related = relatedAccount; this.relatedMemberId = relatedMemberId; signal = account.equals(transfer.getActualFrom()) ? -1 : +1; } public Account getAccount() { return account; } public BigDecimal getAmount() { final BigDecimal amount = transfer.getActualAmount(); return signal < 0 ? amount.negate() : amount; } public Account getRelated() { return related; } public Long getRelatedMemberId() { return relatedMemberId; } public int getSignal() { return signal; } public Transfer getTransfer() { return transfer; } public boolean isCredit() { return signal > 0; } public boolean isDebit() { return signal < 0; } } /** * A transformer between transfers and entries */ private static class TransformTransferInEntry implements Transformer<Transfer, Entry> { private Account account; private boolean fetchMember; private TransformTransferInEntry(final PermissionService permissionService, final ElementService elementService, final Account account, final boolean fetchMember) { this.account = account; this.fetchMember = fetchMember; } @Override public Entry transform(final Transfer transfer) { final Account from = transfer.getActualFrom(); final Account to = transfer.getActualTo(); final Account relatedAccount = account.equals(from) ? to : from; Long relatedMemberId = null; if (fetchMember) { if (relatedAccount instanceof MemberAccount) { final MemberAccount ma = (MemberAccount) relatedAccount; relatedMemberId = ma.getMember().getId(); } } return new Entry(account, transfer, relatedAccount, relatedMemberId); } } /** * Returns a databinder for a transferquery */ public static DataBinder<TransferQuery> transferQueryDataBinder(final LocalSettings localSettings) { final BeanBinder<PaymentCustomFieldValue> customValueBinder = BeanBinder.instance(PaymentCustomFieldValue.class); customValueBinder.registerBinder("field", PropertyBinder.instance(PaymentCustomField.class, "field")); customValueBinder.registerBinder("value", PropertyBinder.instance(String.class, "value")); final BeanBinder<TransferQuery> binder = BeanBinder.instance(TransferQuery.class); binder.registerBinder("owner", PropertyBinder.instance(AccountOwner.class, "owner", AccountOwnerConverter.instance())); binder.registerBinder("status", PropertyBinder.instance(Transfer.Status.class, "status")); binder.registerBinder("type", PropertyBinder.instance(AccountType.class, "type", ReferenceConverter.instance(AccountType.class))); binder.registerBinder("period", DataBinderHelper.rawPeriodBinder(localSettings, "period")); binder.registerBinder("paymentFilter", PropertyBinder.instance(PaymentFilter.class, "paymentFilter", ReferenceConverter.instance(PaymentFilter.class))); binder.registerBinder("description", PropertyBinder.instance(String.class, "description")); binder.registerBinder("transactionNumber", PropertyBinder.instance(String.class, "transactionNumber")); binder.registerBinder("member", PropertyBinder.instance(Member.class, "member", ReferenceConverter.instance(Member.class))); binder.registerBinder("by", PropertyBinder.instance(Element.class, "by", ReferenceConverter.instance(Element.class))); binder.registerBinder("conciliated", PropertyBinder.instance(Boolean.class, "conciliated")); binder.registerBinder("groups", SimpleCollectionBinder.instance(MemberGroup.class, "groups")); binder.registerBinder("groupFilters", SimpleCollectionBinder.instance(GroupFilter.class, "groupFilters")); binder.registerBinder("customValues", BeanCollectionBinder.instance(customValueBinder, "customValues")); binder.registerBinder("pageParameters", DataBinderHelper.pageBinder()); return binder; } protected AccountService accountService; protected PaymentFilterService paymentFilterService; protected PaymentCustomFieldService paymentCustomFieldService; private AccountTypeService accountTypeService; private PaymentService paymentService; private GroupFilterService groupFilterService; private DataBinder<TransferQuery> dataBinder; private CustomFieldHelper customFieldHelper; public DataBinder<TransferQuery> getDataBinder() { if (dataBinder == null) { final LocalSettings localSettings = settingsService.getLocalSettings(); dataBinder = transferQueryDataBinder(localSettings); } return dataBinder; } @Override public void onLocalSettingsUpdate(final LocalSettingsEvent event) { super.onLocalSettingsUpdate(event); dataBinder = null; } @Inject public void setAccountService(final AccountService accountService) { this.accountService = accountService; } @Inject public void setAccountTypeService(final AccountTypeService accountTypeService) { this.accountTypeService = accountTypeService; } @Inject public void setCustomFieldHelper(final CustomFieldHelper customFieldHelper) { this.customFieldHelper = customFieldHelper; } @Inject public void setGroupFilterService(final GroupFilterService groupFilterService) { this.groupFilterService = groupFilterService; } @Inject public void setPaymentCustomFieldService(final PaymentCustomFieldService paymentCustomFieldService) { this.paymentCustomFieldService = paymentCustomFieldService; } @Inject public void setPaymentFilterService(final PaymentFilterService paymentFilterService) { this.paymentFilterService = paymentFilterService; } @Inject public void setPaymentService(final PaymentService paymentService) { this.paymentService = paymentService; } @Override protected void executeQuery(final ActionContext context, final QueryParameters queryParameters) { final HttpServletRequest request = context.getRequest(); final TransferQuery query = (TransferQuery) queryParameters; final Account account = (Account) request.getAttribute("account"); final List<Transfer> transfers = paymentService.search(query); request.setAttribute("transfers", transfers); request.setAttribute("accountHistory", Entry.build(permissionService, elementService, account, transfers, fetchMember())); } /** * Used to determine if the each transfer's related member will be fetch */ protected boolean fetchMember() { return true; } @Override protected QueryParameters prepareForm(final ActionContext context) { final HttpServletRequest request = context.getRequest(); final AccountHistoryForm form = context.getForm(); final LocalSettings localSettings = settingsService.getLocalSettings(); // Set the owner and the account type on the first request boolean firstTime = false; if (RequestHelper.isGet(request)) { form.setQuery("owner", form.getMemberId()); form.setQuery("type", form.getTypeId()); firstTime = true; } // Retrieve the query parameters final TransferQuery query = getDataBinder().readFromString(form.getQuery()); query.fetch(Payment.Relationships.CUSTOM_VALUES, Payment.Relationships.FROM, Payment.Relationships.TO, Payment.Relationships.TYPE); query.setReverseOrder(true); // Fetch the account type, and add relationship externalAccounts final AccountType type = accountTypeService.load(query.getType().getId()); // Set the default status to PROCESSED if (query.getStatus() == null) { query.setStatus(Transfer.Status.PROCESSED); form.setQuery("status", query.getStatus().name()); } if (firstTime) { if (type instanceof SystemAccountType) { // Ensure the initial period filter will start from the 1st day of the previous month, to avoid potentially huge DB scans final Period lastMonthPeriod = TimePeriod.ONE_MONTH.previousPeriod(Calendar.getInstance()); query.setPeriod(Period.begginingAt(lastMonthPeriod.getBegin())); final String formattedDate = localSettings.getDateConverter().toString(lastMonthPeriod.getBegin()); PropertyHelper.set(form.getQuery("period"), "begin", formattedDate); } } // Fetch the owner if is a member AccountOwner owner = query.getOwner(); if (owner == null) { owner = SystemAccountOwner.instance(); } else if (owner instanceof EntityReference) { owner = (AccountOwner) elementService.load(((Member) owner).getId()); } // Check if authorization status will be shown boolean showStatus = false; if (owner instanceof SystemAccountOwner) { showStatus = permissionService.hasPermission(AdminSystemPermission.ACCOUNTS_AUTHORIZED_INFORMATION); } else if (context.isAdmin()) { showStatus = permissionService.hasPermission(AdminMemberPermission.ACCOUNTS_AUTHORIZED_INFORMATION); } else if (context.getAccountOwner().equals(owner)) { showStatus = permissionService.hasPermission(MemberPermission.ACCOUNT_AUTHORIZED_INFORMATION); } else if (owner instanceof Member && context.isBrokerOf((Member) owner)) { showStatus = permissionService.hasPermission(BrokerPermission.ACCOUNTS_AUTHORIZED_INFORMATION); } if (showStatus) { request.setAttribute("paymentStatus", EnumSet.of(Transfer.Status.PROCESSED, Transfer.Status.PENDING, Transfer.Status.DENIED, Transfer.Status.CANCELED)); } // Retrieve the account final Account account = accountService.getAccount(new AccountDTO(owner, type)); // Fetch the member on filter if (query.getMember() instanceof EntityReference) { query.setMember((Member) elementService.load(query.getMember().getId(), Element.Relationships.USER)); } // When a member, get it's operators final Member loggedMember = context.getMember(); if (loggedMember != null && form.isAdvanced()) { final OperatorQuery oq = new OperatorQuery(); oq.setMember(loggedMember); final List<? extends Element> operators = elementService.search(oq); request.setAttribute("operators", operators); } // When a system account, get groups / group filters if (type instanceof SystemAccountType) { final AdminGroup adminGroup = context.getGroup(); final GroupQuery groups = new GroupQuery(); groups.setManagedBy(adminGroup); groups.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER); groups.setStatus(Group.Status.NORMAL); request.setAttribute("memberGroups", groupService.search(groups)); final GroupFilterQuery groupFilters = new GroupFilterQuery(); groupFilters.setAdminGroup(adminGroup); request.setAttribute("groupFilters", groupFilterService.search(groupFilters)); } // Get the account status final AccountStatus status = accountService.getRatedStatus(account, null); // Get the credit limit final BigDecimal min = paymentService.getMinimumPayment(); final GetTransactionsDTO params = new GetTransactionsDTO(query.getOwner(), query.getType()); final BigDecimal creditLimit = accountService.getCreditLimit(params); // Don't show if zero if (creditLimit != null && creditLimit.abs().compareTo(min) == 1) { request.setAttribute("creditLimit", creditLimit.negate()); } // Retrieve the payment filters final PaymentFilterQuery pfQuery = new PaymentFilterQuery(); pfQuery.setAccountType(query.getType()); pfQuery.setContext(PaymentFilterQuery.Context.ACCOUNT_HISTORY); pfQuery.setElement(owner instanceof SystemAccountOwner ? context.getElement() : (Member) owner); final List<PaymentFilter> paymentFilters = paymentFilterService.search(pfQuery); // Set the required request attributes request.setAttribute("owner", owner instanceof SystemAccountOwner ? null : owner); request.setAttribute("type", type); request.setAttribute("paymentFilters", paymentFilters); request.setAttribute("myAccount", context.getAccountOwner().equals(owner)); request.setAttribute("status", status); request.setAttribute("unitsPattern", type.getCurrency().getPattern()); request.setAttribute("account", account); if (type instanceof SystemAccountType) { final SystemAccountType systemType = (SystemAccountType) type; request.setAttribute("showConciliated", !systemType.getExternalAccounts().isEmpty()); } else { request.setAttribute("showConciliated", Boolean.FALSE); } // Get the custom fields final List<PaymentCustomField> customFieldsForSearch = paymentCustomFieldService.listForSearch(account, false); final List<PaymentCustomField> customFieldsForList = paymentCustomFieldService.listForList(account, false); request.setAttribute("customFieldsForSearch", customFieldHelper.buildEntries(customFieldsForSearch, query.getCustomValues())); request.setAttribute("customFieldsForList", customFieldsForList); // Determine where to go back String backTo = null; if (type instanceof SystemAccountType) { if (!form.isSingleAccount()) { // On system accounts, only go back when there's an account overview backTo = context.getPathPrefix() + "/accountOverview"; } } else { // A member account, go back to either overview or profile final Member member = (Member) owner; if (form.isSingleAccount()) { backTo = context.getPathPrefix() + "/profile?memberId=" + member.getId(); } else { backTo = context.getPathPrefix() + "/accountOverview?memberId=" + member.getId(); } } request.setAttribute("backTo", backTo); return query; } @Override protected boolean willExecuteQuery(final ActionContext context, final QueryParameters queryParameters) throws Exception { // The query is always executed return true; } }