/* 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.transactions; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import nl.strohalm.cyclos.access.AdminMemberPermission; import nl.strohalm.cyclos.access.BrokerPermission; import nl.strohalm.cyclos.access.OperatorPermission; import nl.strohalm.cyclos.dao.accounts.transactions.InvoiceDAO; import nl.strohalm.cyclos.dao.accounts.transactions.InvoicePaymentDAO; import nl.strohalm.cyclos.entities.Relationship; import nl.strohalm.cyclos.entities.access.Channel; import nl.strohalm.cyclos.entities.accounts.AccountOwner; import nl.strohalm.cyclos.entities.accounts.AccountType; import nl.strohalm.cyclos.entities.accounts.Currency; import nl.strohalm.cyclos.entities.accounts.SystemAccountOwner; import nl.strohalm.cyclos.entities.accounts.transactions.Invoice; import nl.strohalm.cyclos.entities.accounts.transactions.InvoicePayment; import nl.strohalm.cyclos.entities.accounts.transactions.InvoiceQuery; import nl.strohalm.cyclos.entities.accounts.transactions.InvoiceQuery.Direction; import nl.strohalm.cyclos.entities.accounts.transactions.InvoiceSummaryDTO; import nl.strohalm.cyclos.entities.accounts.transactions.Payment; import nl.strohalm.cyclos.entities.accounts.transactions.ScheduledPayment; import nl.strohalm.cyclos.entities.accounts.transactions.Transfer; import nl.strohalm.cyclos.entities.accounts.transactions.TransferType; import nl.strohalm.cyclos.entities.accounts.transactions.TransferTypeQuery; import nl.strohalm.cyclos.entities.alerts.Alert; import nl.strohalm.cyclos.entities.alerts.AlertQuery; import nl.strohalm.cyclos.entities.alerts.MemberAlert; import nl.strohalm.cyclos.entities.customization.fields.PaymentCustomFieldValue; import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException; 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.members.Element; import nl.strohalm.cyclos.entities.members.Member; import nl.strohalm.cyclos.entities.members.Operator; import nl.strohalm.cyclos.entities.reports.InvoiceSummaryType; import nl.strohalm.cyclos.entities.settings.AlertSettings; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.services.InitializingService; import nl.strohalm.cyclos.services.accounts.AccountTypeServiceLocal; import nl.strohalm.cyclos.services.accounts.MemberAccountTypeQuery; import nl.strohalm.cyclos.services.alerts.AlertServiceLocal; import nl.strohalm.cyclos.services.customization.PaymentCustomFieldServiceLocal; import nl.strohalm.cyclos.services.fetch.FetchServiceLocal; import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal; import nl.strohalm.cyclos.services.settings.SettingsServiceLocal; import nl.strohalm.cyclos.services.transactions.exceptions.MaxAmountPerDayExceededException; import nl.strohalm.cyclos.services.transactions.exceptions.NotEnoughCreditsException; import nl.strohalm.cyclos.services.transactions.exceptions.UpperCreditLimitReachedException; import nl.strohalm.cyclos.services.transfertypes.TransferTypeServiceLocal; import nl.strohalm.cyclos.utils.CacheCleaner; import nl.strohalm.cyclos.utils.DataIteratorHelper; import nl.strohalm.cyclos.utils.DateHelper; import nl.strohalm.cyclos.utils.MessageResolver; import nl.strohalm.cyclos.utils.Period; import nl.strohalm.cyclos.utils.PropertyHelper; import nl.strohalm.cyclos.utils.RelationshipHelper; import nl.strohalm.cyclos.utils.TimePeriod; import nl.strohalm.cyclos.utils.TransactionHelper; import nl.strohalm.cyclos.utils.Transactional; import nl.strohalm.cyclos.utils.access.LoggedUser; import nl.strohalm.cyclos.utils.conversion.CalendarConverter; import nl.strohalm.cyclos.utils.conversion.NumberConverter; import nl.strohalm.cyclos.utils.notifications.AdminNotificationHandler; import nl.strohalm.cyclos.utils.notifications.MemberNotificationHandler; import nl.strohalm.cyclos.utils.query.PageHelper; import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType; import nl.strohalm.cyclos.utils.validation.DelegatingValidator; import nl.strohalm.cyclos.utils.validation.GeneralValidation; import nl.strohalm.cyclos.utils.validation.InvalidError; import nl.strohalm.cyclos.utils.validation.PropertyValidation; import nl.strohalm.cyclos.utils.validation.RequiredError; import nl.strohalm.cyclos.utils.validation.ValidationError; import nl.strohalm.cyclos.utils.validation.Validator; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.ObjectUtils; import org.springframework.transaction.TransactionStatus; /** * Invoice service implementation * @author luis */ public class InvoiceServiceImpl implements InvoiceServiceLocal, InitializingService { private InvoiceDAO invoiceDao; private InvoicePaymentDAO invoicePaymentDao; private PaymentServiceLocal paymentService; private SettingsServiceLocal settingsService; private TransferTypeServiceLocal transferTypeService; private FetchServiceLocal fetchService; private AccountTypeServiceLocal accountTypeService; private AlertServiceLocal alertService; private MessageResolver messageResolver; private PaymentCustomFieldServiceLocal paymentCustomFieldService; private MemberNotificationHandler memberNotificationHandler; private AdminNotificationHandler adminNotificationHandler; private TransactionHelper transactionHelper; private PermissionServiceLocal permissionService; @Override public Invoice accept(final Invoice inputInvoice) throws NotEnoughCreditsException, UpperCreditLimitReachedException, MaxAmountPerDayExceededException, UnexpectedEntityException { // Remember the selected transfer type TransferType inputTransferType = inputInvoice.getTransferType(); final Invoice invoice = fetchService.fetch(inputInvoice); if (invoice.getStatus() != Invoice.Status.OPEN) { throw new UnexpectedEntityException(); } // If there was a transfer type on the invoice, use it - no matter what the user selected if (invoice.getTransferType() != null) { inputTransferType = invoice.getTransferType(); } final TransferType transferType = inputTransferType; final Element performedBy = LoggedUser.hasUser() ? LoggedUser.element() : null; // Validate transfer type final List<TransferType> possibleTypes = getPossibleTransferTypes(invoice); if (!possibleTypes.contains(transferType)) { throw new UnexpectedEntityException(); } // If invoice has scheduled payments, check if the first one is not expired final Calendar today = DateHelper.truncate(Calendar.getInstance()); if (CollectionUtils.isNotEmpty(invoice.getPayments())) { final InvoicePayment invoicePayment = invoice.getPayments().get(0); if (invoicePayment.getDate().before(today)) { throw new UnexpectedEntityException(); } } // The accepting of the invoice and the transfer must be transactional. Plus, any LockingExceptions must be retried return transactionHelper.maybeRunInNewTransaction(new Transactional<Invoice>() { @Override public Invoice afterCommit(final Invoice result) { return fetchService.fetch(result); } @Override public Invoice doInTransaction(final TransactionStatus status) { return doAccept(invoice, transferType, performedBy); } }); } @Override public int alertExpiredSystemInvoices(final Calendar time) { final AlertSettings alertSettings = settingsService.getAlertSettings(); final TimePeriod tp = alertSettings.getIdleInvoiceExpiration(); // don't do anything if expiration period is set to 0 if (tp == null || tp.getNumber() <= 0) { return 0; } // Get the limit date for open invoices final Calendar limit = tp.remove(DateHelper.truncate(time)); int processed = 0; // List the expired invoices final InvoiceQuery query = new InvoiceQuery(); query.fetch(RelationshipHelper.nested(Invoice.Relationships.DESTINATION_ACCOUNT_TYPE, AccountType.Relationships.CURRENCY), Invoice.Relationships.TO_MEMBER); query.setOwner(SystemAccountOwner.instance()); query.setDirection(Direction.OUTGOING); query.setPeriod(Period.endingAt(limit)); query.setStatus(Invoice.Status.OPEN); query.setResultType(ResultType.ITERATOR); final List<Invoice> invoices = search(query); if (!invoices.isEmpty()) { final LocalSettings localSettings = settingsService.getLocalSettings(); final NumberConverter<BigDecimal> numberConverter = localSettings.getNumberConverter(); final CalendarConverter dateTimeConverter = localSettings.getDateTimeConverter(); for (final Invoice invoice : invoices) { // Create the alert String amount; if (invoice.getDestinationAccountType() != null) { amount = localSettings.getUnitsConverter(invoice.getDestinationAccountType().getCurrency().getPattern()).toString(invoice.getAmount()); } else { amount = numberConverter.toString(invoice.getAmount()); } final String date = dateTimeConverter.toString(invoice.getDate()); alertService.create(invoice.getToMember(), MemberAlert.Alerts.INVOICE_IDLE_TIME_EXCEEDED, amount, date); invoice.setStatus(Invoice.Status.EXPIRED); invoiceDao.update(invoice); processed++; } } return processed; } @Override public boolean canAccept(final Invoice invoice) { // Test the basic action if (!testAction(invoice, true)) { return false; } boolean asUser = false; if (invoice.isToSystem()) { // Member to system if (!permissionService.hasPermission(AdminMemberPermission.INVOICES_ACCEPT)) { return false; } } else { Member member = invoice.getToMember(); if (invoice.isFromSystem()) { // System to member invoice if (!permissionService.permission(member) .admin(AdminMemberPermission.INVOICES_ACCEPT_AS_MEMBER_FROM_SYSTEM) .broker(BrokerPermission.INVOICES_ACCEPT_AS_MEMBER_FROM_SYSTEM) .member() .operator(OperatorPermission.INVOICES_MANAGE) .hasPermission()) { return false; } } else { if (!permissionService.permission(member) .admin(AdminMemberPermission.INVOICES_ACCEPT_AS_MEMBER_FROM_MEMBER) .broker(BrokerPermission.INVOICES_ACCEPT_AS_MEMBER_FROM_MEMBER) .member() .operator(OperatorPermission.INVOICES_MANAGE) .hasPermission()) { return false; } } asUser = !ObjectUtils.equals(LoggedUser.member(), member); } // To this point, the invoice is in an 'acceptable' state, and the user has permission. The check to go is for transfer types return hasTTPermission(invoice, asUser); } @Override public boolean canCancel(final Invoice invoice) { // Test the basic action if (!testAction(invoice, false)) { return false; } if (invoice.isFromSystem()) { // System to member invoice return permissionService.hasPermission(AdminMemberPermission.INVOICES_CANCEL); } else { // System to member invoice return permissionService.permission(invoice.getFromMember()) .admin(AdminMemberPermission.INVOICES_CANCEL_AS_MEMBER) .broker(BrokerPermission.INVOICES_CANCEL_AS_MEMBER) .member() .operator(OperatorPermission.INVOICES_MANAGE) .hasPermission(); } } @Override public Invoice cancel(Invoice invoice) throws UnexpectedEntityException { if (invoice.getStatus() != Invoice.Status.OPEN) { throw new UnexpectedEntityException(); } final Element performedBy = LoggedUser.hasUser() ? LoggedUser.element() : null; invoice.setPerformedBy(performedBy); invoice.setStatus(Invoice.Status.CANCELLED); invoice = invoiceDao.update(invoice); // Notify memberNotificationHandler.cancelledInvoiceNotification(invoice); return invoice; } @Override public boolean canDeny(final Invoice invoice) { // Test the basic action if (!testAction(invoice, true)) { return false; } boolean asUser = false; if (invoice.isToSystem()) { // System to member invoice if (!permissionService.hasPermission(AdminMemberPermission.INVOICES_DENY)) { return false; } } else if (invoice.isFromSystem()) { // Invoices from system cannot be denied return false; } else { // System to member invoice Member member = invoice.getToMember(); if (!permissionService.permission(member) .admin(AdminMemberPermission.INVOICES_DENY_AS_MEMBER) .broker(BrokerPermission.INVOICES_DENY_AS_MEMBER) .member() .operator(OperatorPermission.INVOICES_MANAGE) .hasPermission()) { return false; } asUser = !ObjectUtils.equals(LoggedUser.member(), member); } // To this point, the invoice is in a 'deniable' state, and the user has permission. The check to go is for transfer types return hasTTPermission(invoice, asUser); } @Override public Invoice deny(Invoice invoice) throws UnexpectedEntityException { if (invoice.getStatus() != Invoice.Status.OPEN) { throw new UnexpectedEntityException(); } final Element performedBy = LoggedUser.hasUser() ? LoggedUser.element() : null; invoice.setPerformedBy(performedBy); invoice.setStatus(Invoice.Status.DENIED); invoice = invoiceDao.update(invoice); // Compute the denied invoices to check if an alert should be created final AlertSettings alertSettings = settingsService.getAlertSettings(); if (alertSettings.getAmountDeniedInvoices() > 0) { final Member toMember = invoice.getToMember(); final InvoiceQuery invoiceQuery = new InvoiceQuery(); invoiceQuery.setDirection(Direction.INCOMING); invoiceQuery.setOwner(toMember); invoiceQuery.setStatus(Invoice.Status.DENIED); invoiceQuery.setPageForCount(); final int deniedInvoices = PageHelper.getTotalCount(invoiceDao.search(invoiceQuery)); if (deniedInvoices >= alertSettings.getAmountDeniedInvoices()) { final AlertQuery query = new AlertQuery(); query.setType(Alert.Type.MEMBER); query.setMember(toMember); query.setKey(MemberAlert.Alerts.DENIED_INVOICES.getValue()); query.setPageForCount(); final boolean hasAlert = PageHelper.getTotalCount(alertService.search(query)) > 0; if (!hasAlert) { alertService.create(toMember, MemberAlert.Alerts.DENIED_INVOICES, deniedInvoices); } } } memberNotificationHandler.deniedInvoiceNotification(invoice); return invoice; } @Override public void expireScheduledInvoices(final Calendar time) { // List the invoices with expired scheduled payments final InvoiceQuery query = new InvoiceQuery(); query.fetch(RelationshipHelper.nested(Invoice.Relationships.DESTINATION_ACCOUNT_TYPE, AccountType.Relationships.CURRENCY)); query.setPaymentPeriod(Period.endingAt(DateHelper.truncatePreviosDay(time))); query.setDirection(Direction.OUTGOING); query.setStatus(Invoice.Status.OPEN); query.setResultType(ResultType.ITERATOR); CacheCleaner cacheCleaner = new CacheCleaner(fetchService); final List<Invoice> invoices = search(query); try { for (final Invoice invoice : invoices) { invoice.setStatus(Invoice.Status.EXPIRED); invoiceDao.update(invoice); memberNotificationHandler.expiredInvoiceNotification(invoice); cacheCleaner.clearCache(); } } finally { DataIteratorHelper.close(invoices); } } public Validator getCalculateValidator() { final Validator calculateValidator = new Validator("invoice"); calculateValidator.property("amount").required().positiveNonZero(); calculateValidator.property("paymentCount").required().positiveNonZero(); calculateValidator.property("firstExpirationDate").required().futureOrToday(); calculateValidator.property("recurrence.number").required().positiveNonZero(); calculateValidator.property("recurrence.field").required(); return calculateValidator; } @Override public List<TransferType> getPossibleTransferTypes(Invoice invoice) { if (invoice.isPersistent()) { invoice = fetchService.fetch(invoice); } if (invoice.getTransferType() != null) { return Collections.singletonList(invoice.getTransferType()); } final TransferTypeQuery ttQuery = new TransferTypeQuery(); ttQuery.fetch(TransferType.Relationships.CUSTOM_FIELDS); ttQuery.setChannel(Channel.WEB); ttQuery.setContext(TransactionContext.PAYMENT); ttQuery.setFromOwner(invoice.getTo()); ttQuery.setToOwner(invoice.getFrom()); ttQuery.setToAccountType(invoice.getDestinationAccountType()); ttQuery.setUsePriority(true); if (CollectionUtils.isNotEmpty(invoice.getPayments())) { ttQuery.setSchedulable(true); } if (invoice.isToSystem()) { if (LoggedUser.hasUser()) { ttQuery.setGroup(LoggedUser.group()); } } else { ttQuery.setGroup(invoice.getToMember().getGroup()); } return transferTypeService.search(ttQuery); } @Override public TransactionSummaryVO getSummary(final InvoiceSummaryDTO dto) { return invoiceDao.getSummary(dto); } @Override public TransactionSummaryVO getSummaryByType(final Currency currency, final InvoiceSummaryType invoiceSummaryType) { Collection<MemberGroup> memberGroups = null; if (LoggedUser.hasUser()) { AdminGroup adminGroup = LoggedUser.group(); adminGroup = fetchService.fetch(adminGroup, AdminGroup.Relationships.MANAGES_GROUPS); memberGroups = adminGroup.getManagesGroups(); } return invoiceDao.getSummaryByType(currency, invoiceSummaryType, memberGroups); } @Override public void initializeService() { final Calendar time = Calendar.getInstance(); expireScheduledInvoices(time); // Process the expired system invoices alertExpiredSystemInvoices(time); } public List<Invoice> listFromMember(final Member member) { final InvoiceQuery query = new InvoiceQuery(); query.setDirection(InvoiceQuery.Direction.OUTGOING); query.setOwner(member); query.setStatus(Invoice.Status.OPEN); return invoiceDao.search(query); } public List<Invoice> listToMember(final Member member) { final InvoiceQuery query = new InvoiceQuery(); query.setDirection(InvoiceQuery.Direction.INCOMING); query.setOwner(member); query.setStatus(Invoice.Status.OPEN); return invoiceDao.search(query); } @Override public Invoice load(final Long id, final Relationship... fetch) { return invoiceDao.load(id, fetch); } @Override public Invoice loadByPayment(final Payment payment, final Relationship... fetch) { return invoiceDao.loadByPayment(payment, fetch); } @Override public List<Invoice> search(final InvoiceQuery queryParameters) { return invoiceDao.search(queryParameters); } @Override public Invoice send(final Invoice invoice) throws UnexpectedEntityException { preprocessInvoice(invoice); validate(invoice); return doSend(invoice, false); } @Override public Invoice sendAutomatically(final Invoice invoice) { preprocessInvoice(invoice); return doSend(invoice, true); } public void setAccountTypeServiceLocal(final AccountTypeServiceLocal accountTypeService) { this.accountTypeService = accountTypeService; } public void setAdminNotificationHandler(final AdminNotificationHandler adminNotificationHandler) { this.adminNotificationHandler = adminNotificationHandler; } public void setAlertServiceLocal(final AlertServiceLocal alertService) { this.alertService = alertService; } public void setFetchServiceLocal(final FetchServiceLocal fetchService) { this.fetchService = fetchService; } public void setInvoiceDao(final InvoiceDAO invoiceDao) { this.invoiceDao = invoiceDao; } public void setInvoicePaymentDao(final InvoicePaymentDAO invoicePaymentDao) { this.invoicePaymentDao = invoicePaymentDao; } public void setMemberNotificationHandler(final MemberNotificationHandler memberNotificationHandler) { this.memberNotificationHandler = memberNotificationHandler; } public void setMessageResolver(final MessageResolver messageResolver) { this.messageResolver = messageResolver; } public void setPaymentCustomFieldServiceLocal(final PaymentCustomFieldServiceLocal paymentCustomFieldService) { this.paymentCustomFieldService = paymentCustomFieldService; } public void setPaymentServiceLocal(final PaymentServiceLocal paymentService) { this.paymentService = paymentService; } public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) { this.permissionService = permissionService; } public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) { this.settingsService = settingsService; } public void setTransactionHelper(final TransactionHelper transactionHelper) { this.transactionHelper = transactionHelper; } public void setTransferTypeServiceLocal(final TransferTypeServiceLocal transferTypeService) { this.transferTypeService = transferTypeService; } @Override public void validate(final Invoice invoice) { getValidator(invoice).validate(invoice); } @Override public void validateForAccept(final Invoice invoice) { getAcceptValidator().validate(invoice); } public void validateForCalculation(final ProjectionDTO dto) { getCalculateValidator().validate(dto); } private Invoice doAccept(Invoice invoice, TransferType transferType, Element performedBy) { // Ensure to reload data which was (possibly) created in another transaction (associated with another session) invoice = fetchService.reload(invoice); performedBy = fetchService.reload(performedBy); transferType = fetchService.reload(transferType); // Perform the payment final TransferDTO dto = new TransferDTO(); dto.setAutomatic(true); dto.setFromOwner(invoice.getTo()); dto.setToOwner(invoice.getFrom()); dto.setBy(performedBy); dto.setTransferType(transferType); dto.setAmount(invoice.getAmount()); // For operators which sent the invoice, we also want to copy as receiver to the transfer if (invoice.getSentBy() instanceof Operator) { dto.setReceiver(invoice.getSentBy()); } dto.setDescription(invoice.getDescription()); dto.setAccountFeeLog(invoice.getAccountFeeLog()); dto.setCustomValues(new ArrayList<PaymentCustomFieldValue>()); for (final PaymentCustomFieldValue fieldValue : invoice.getCustomValues()) { final PaymentCustomFieldValue transferValue = new PaymentCustomFieldValue(); transferValue.setField(fieldValue.getField()); transferValue.setValue(fieldValue.getValue()); dto.getCustomValues().add(transferValue); } // Check if there are associated invoice payments if (CollectionUtils.isNotEmpty(invoice.getPayments())) { dto.setShowScheduledToReceiver(true); final List<ScheduledPaymentDTO> payments = new ArrayList<ScheduledPaymentDTO>(); for (final InvoicePayment invoicePayment : invoice.getPayments()) { final ScheduledPaymentDTO scheduledPaymentDTO = new ScheduledPaymentDTO(); scheduledPaymentDTO.setAmount(invoicePayment.getAmount()); scheduledPaymentDTO.setDate(invoicePayment.getDate()); payments.add(scheduledPaymentDTO); } dto.setPayments(payments); } final Payment payment = paymentService.insertWithoutNotification(dto); // Update the invoice invoice.setPayment(payment); invoice.setStatus(Invoice.Status.ACCEPTED); invoice.setPerformedBy(performedBy); invoice = invoiceDao.update(invoice); // Update invoice payments with scheduled transfers if (payment instanceof ScheduledPayment) { final ScheduledPayment scheduledPayment = (ScheduledPayment) payment; final Iterator<InvoicePayment> invoicePaymentsIterator = invoice.getPayments().iterator(); final Iterator<Transfer> transfersIterator = scheduledPayment.getTransfers().iterator(); while (invoicePaymentsIterator.hasNext()) { final InvoicePayment invoicePayment = invoicePaymentsIterator.next(); final Transfer transfer = transfersIterator.next(); invoicePayment.setTransfer(transfer); invoicePaymentDao.update(invoicePayment); } } // Notify memberNotificationHandler.acceptedInvoiceNotification(invoice); return invoice; } private Invoice doSend(Invoice invoice, final boolean automatic) { validate(invoice); // Validate if the selected transfer type is allowed final TransferType transferType = fetchService.fetch(invoice.getTransferType(), TransferType.Relationships.TO); final List<InvoicePayment> payments = invoice.getPayments(); if (transferType != null) { final TransferTypeQuery ttQuery = new TransferTypeQuery(); if (!automatic) { ttQuery.setContext(TransactionContext.PAYMENT); } else { ttQuery.setContext(TransactionContext.AUTOMATIC); } ttQuery.setFromOwner(invoice.getTo()); ttQuery.setToOwner(invoice.getFrom()); final List<TransferType> possibleTypes = transferTypeService.search(ttQuery); if (!possibleTypes.contains(transferType)) { throw new UnexpectedEntityException(); } invoice.setDestinationAccountType(transferType.getTo()); } else { // Validates the destination account type final AccountType destinationAccountType = fetchService.fetch(invoice.getDestinationAccountType()); final MemberAccountTypeQuery atQuery = new MemberAccountTypeQuery(); atQuery.setCanPay(invoice.getTo()); atQuery.setOwner(invoice.getFromMember()); final List<? extends AccountType> possibleTypes = accountTypeService.search(atQuery); if (!possibleTypes.contains(destinationAccountType)) { throw new UnexpectedEntityException(); } } final Collection<PaymentCustomFieldValue> customValues = invoice.getCustomValues(); // Insert the invoice invoice = invoiceDao.insert(invoice); invoice.setCustomValues(customValues); paymentCustomFieldService.saveValues(invoice); if (CollectionUtils.isNotEmpty(payments)) { for (final InvoicePayment payment : payments) { payment.setInvoice(invoice); invoicePaymentDao.insert(payment); } } // Notify if (invoice.isToSystem()) { adminNotificationHandler.notifySystemInvoice(invoice); } else { memberNotificationHandler.receivedInvoiceNotification(invoice); } return invoice; } private Validator getAcceptValidator() { final Validator acceptValidator = new Validator("invoice"); acceptValidator.property("id").required().positiveNonZero(); acceptValidator.property("transferType").required(); return acceptValidator; } private Validator getValidator(final Invoice invoice) { final Validator validator = new Validator("invoice"); validator.property("from").required(); validator.property("to").add(new PropertyValidation() { private static final long serialVersionUID = -5222363482447066104L; @Override public ValidationError validate(final Object object, final Object name, final Object value) { final Invoice invoice = (Invoice) object; // Can't be the same from / to if (invoice.getFrom() != null && invoice.getTo() != null && invoice.getFrom().equals(invoice.getTo())) { return new InvalidError(); } return null; } }); validator.property("description").required().maxLength(1000); validator.property("amount").required().positiveNonZero(); if (LoggedUser.hasUser()) { final boolean asMember = !LoggedUser.accountOwner().equals(invoice.getFrom()); if (asMember || LoggedUser.isMember() || LoggedUser.isOperator()) { validator.property("destinationAccountType").required(); } else { validator.property("transferType").required(); } } validator.general(new GeneralValidation() { private static final long serialVersionUID = 4085922259108191939L; @Override public ValidationError validate(final Object object) { // Validate the scheduled payments final Invoice invoice = (Invoice) object; final List<InvoicePayment> payments = invoice.getPayments(); if (CollectionUtils.isEmpty(payments)) { return null; } // Validate the from member Member fromMember = invoice.getFromMember(); if (fromMember == null && LoggedUser.isMember()) { fromMember = LoggedUser.element(); } Calendar maxPaymentDate = null; if (fromMember != null) { fromMember = fetchService.fetch(fromMember, RelationshipHelper.nested(Element.Relationships.GROUP)); final MemberGroup group = fromMember.getMemberGroup(); // Validate the max payments final int maxSchedulingPayments = group.getMemberSettings().getMaxSchedulingPayments(); if (payments.size() > maxSchedulingPayments) { return new ValidationError("errors.greaterEquals", messageResolver.message("transfer.paymentCount"), maxSchedulingPayments); } // Get the maximum payment date final TimePeriod maxSchedulingPeriod = group.getMemberSettings().getMaxSchedulingPeriod(); if (maxSchedulingPeriod != null) { maxPaymentDate = maxSchedulingPeriod.add(DateHelper.truncate(Calendar.getInstance())); } } final BigDecimal invoiceAmount = invoice.getAmount(); final BigDecimal minimumPayment = paymentService.getMinimumPayment(); BigDecimal totalAmount = BigDecimal.ZERO; Calendar lastDate = DateHelper.truncate(Calendar.getInstance()); for (final InvoicePayment payment : payments) { final Calendar date = payment.getDate(); // Validate the max payment date if (maxPaymentDate != null && date.after(maxPaymentDate)) { final LocalSettings localSettings = settingsService.getLocalSettings(); final CalendarConverter dateConverter = localSettings.getRawDateConverter(); return new ValidationError("payment.invalid.schedulingDate", dateConverter.toString(maxPaymentDate)); } final BigDecimal amount = payment.getAmount(); if (amount == null || amount.compareTo(minimumPayment) < 0) { return new RequiredError(messageResolver.message("transfer.amount")); } if (date == null) { return new RequiredError(messageResolver.message("transfer.date")); } else if (date.before(lastDate)) { return new ValidationError("invoice.invalid.paymentDates"); } totalAmount = totalAmount.add(amount); lastDate = date; } if (invoiceAmount != null && totalAmount.compareTo(invoiceAmount) != 0) { return new ValidationError("invoice.invalid.paymentAmount"); } return null; } }); // Custom fields final List<TransferType> possibleTransferTypes = getPossibleTransferTypes(invoice); if (possibleTransferTypes.size() == 1) { validator.chained(new DelegatingValidator(new DelegatingValidator.DelegateSource() { @Override public Validator getValidator() { final TransferType transferType = possibleTransferTypes.iterator().next(); return paymentCustomFieldService.getValueValidator(transferType); } })); } return validator; } private boolean hasTTPermission(final Invoice invoice, final boolean asUser) { TransferType transferType = invoice.getTransferType(); if (transferType != null && !transferType.getContext().isPayment()) { // The invoices sent with disabled TTs are those from account fee charges // We cannot check for permission, because disabled TTs cannot be assigned to permission groups return true; } Relationship fetch = asUser ? AdminGroup.Relationships.TRANSFER_TYPES_AS_MEMBER /* same relationship for brokers */: Group.Relationships.TRANSFER_TYPES; List<TransferType> ttsWithPermission = PropertyHelper.get(fetchService.fetch(LoggedUser.group(), fetch), fetch.getName()); List<TransferType> possibleTransferTypes = getPossibleTransferTypes(invoice); return CollectionUtils.containsAny(possibleTransferTypes, ttsWithPermission); } /** * Pre-process an invoice before sending from the logged user */ private void preprocessInvoice(final Invoice invoice) { if (LoggedUser.hasUser()) { if (invoice.getFrom() == null) { invoice.setFrom(LoggedUser.accountOwner()); } invoice.setSentBy(LoggedUser.element()); } if (invoice.getDate() == null) { invoice.setDate(Calendar.getInstance()); } invoice.setStatus(Invoice.Status.OPEN); } private boolean testAction(final Invoice invoice, final boolean shouldBeIncoming) { // Only open invoices can be receive actions if (!invoice.isOpen()) { return false; } // Test whether the logged user is or manages the expected account owner AccountOwner owner; if (shouldBeIncoming) { owner = invoice.getTo(); } else { owner = invoice.getFrom(); } if (owner instanceof SystemAccountOwner) { return LoggedUser.isAdministrator(); } return permissionService.manages((Member) owner); } }