/**
   * Removes a user from the ageing process (makes them active), ONLY if they do not still have
   * overdue invoices.
   *
   * @param user user to make active
   * @param excludedInvoiceId invoice id to ignore when determining if the user CAN be made active
   * @param executorId executor id
   */
  public void removeUser(UserDTO user, Integer executorId, Integer excludedInvoiceId) {
    Date now = new Date();

    // validate that the user actually needs a status change
    if (user.getStatus().getId() != UserDTOEx.STATUS_ACTIVE) {
      LOG.debug("User " + user.getId() + " is already active, no need to remove from ageing.");
      return;
    }

    // validate that the user does not still have overdue invoices
    try {
      if (new InvoiceBL().isUserWithOverdueInvoices(user.getUserId(), now, excludedInvoiceId)) {
        LOG.debug(
            "User " + user.getId() + " still has overdue invoices, cannot remove from ageing.");
        return;
      }
    } catch (SQLException e) {
      LOG.error("Exception occurred checking for overdue invoices.", e);
      return;
    }

    // make the status change.
    LOG.debug("Removing user " + user.getUserId() + " from ageing (making active).");
    UserStatusDTO status = new UserStatusDAS().find(UserDTOEx.STATUS_ACTIVE);
    setUserStatus(user, status, now, null);
  }
  /**
   * Moves a user one step forward in the ageing process (move from active -> suspended etc.). The
   * user will only be moved if they have spent long enough in their present status.
   *
   * @param steps ageing steps
   * @param user user to age
   * @param today today's date
   * @return the resulting ageing step for the user after ageing
   */
  public AgeingEntityStepDTO ageUser(
      Set<AgeingEntityStepDTO> steps, UserDTO user, Date today, Integer executorId) {
    LOG.debug("Ageing user " + user.getId());

    Integer currentStatusId = user.getStatus().getId();
    UserStatusDTO nextStatus = null;
    AgeingEntityStepDTO ageingStep = null;

    if (currentStatusId.equals(UserDTOEx.STATUS_ACTIVE)) {
      // send welcome message (initial step after active).
      nextStatus = getNextAgeingStep(steps, UserDTOEx.STATUS_ACTIVE);

    } else {
      // user already in the ageing process
      ageingStep = new AgeingEntityStepDAS().findStep(user.getEntity().getId(), currentStatusId);

      if (ageingStep != null) {
        // determine the next ageing step
        if (isAgeingRequired(user, ageingStep, today)) {
          nextStatus = getNextAgeingStep(steps, currentStatusId);
          LOG.debug(
              "User "
                  + user.getId()
                  + " needs to be aged to '"
                  + getStatusDescription(nextStatus)
                  + "'");
        }

      } else {
        // User is in a non-existent ageing status... Either the status was removed or
        // the data is bad. As a workaround, just move to the next status.
        nextStatus = getNextAgeingStep(steps, currentStatusId);
        LOG.warn(
            "User "
                + user.getId()
                + " is in an invalid ageing step. Moving to '"
                + getStatusDescription(nextStatus)
                + "'");
      }
    }

    // set status
    if (nextStatus != null) {
      setUserStatus(user, nextStatus, today, null);

    } else {
      LOG.debug("Next status is null, no further ageing steps are available.");
      eLogger.warning(
          user.getEntity().getId(),
          user.getUserId(),
          user.getUserId(),
          EventLogger.MODULE_USER_MAINTENANCE,
          EventLogger.NO_FURTHER_STEP,
          Constants.TABLE_BASE_USER);
    }

    return ageingStep;
  }
  /**
   * Sets the user status to the given "aged" status. If the user status is already set to the aged
   * status no changes will be made. This method also performs an HTTP callback and sends a
   * notification message when a status change is made.
   *
   * <p>If the user becomes suspended and can no longer log-in to the system, all of their active
   * orders will be automatically suspended.
   *
   * <p>If the user WAS suspended and becomes active (and can now log-in to the system), any
   * automatically suspended orders will be re-activated.
   *
   * @param user user
   * @param status status to set
   * @param today today's date
   * @param executorId executor id
   */
  public void setUserStatus(UserDTO user, UserStatusDTO status, Date today, Integer executorId) {
    // only set status if the new "aged" status is different from the users current status
    if (status.getId() == user.getStatus().getId()) {
      return;
    }

    LOG.debug("Setting user " + user.getId() + " status to '" + getStatusDescription(status) + "'");

    if (executorId != null) {
      // this came from the gui
      eLogger.audit(
          executorId,
          user.getId(),
          Constants.TABLE_BASE_USER,
          user.getId(),
          EventLogger.MODULE_USER_MAINTENANCE,
          EventLogger.STATUS_CHANGE,
          user.getStatus().getId(),
          null,
          null);
    } else {
      // this is from a process, no executor involved
      eLogger.auditBySystem(
          user.getCompany().getId(),
          user.getId(),
          Constants.TABLE_BASE_USER,
          user.getId(),
          EventLogger.MODULE_USER_MAINTENANCE,
          EventLogger.STATUS_CHANGE,
          user.getStatus().getId(),
          null,
          null);
    }

    // make the change
    boolean couldLogin = user.getStatus().getCanLogin() == 1;
    UserStatusDTO oldStatus = user.getStatus();

    user.setUserStatus(status);
    user.setLastStatusChange(today);

    // status changed to deleted, remove user
    if (status.getId() == UserDTOEx.STATUS_DELETED) {
      LOG.debug("Deleting user " + user.getId());
      new UserBL(user.getId()).delete(executorId);
      return;
    }

    // status changed from active to suspended
    // suspend customer orders
    if (couldLogin && status.getCanLogin() == 0) {
      LOG.debug("User " + user.getId() + " cannot log-in to the system. Suspending active orders.");

      OrderBL orderBL = new OrderBL();
      ScrollableResults orders =
          new OrderDAS().findByUser_Status(user.getId(), Constants.ORDER_STATUS_ACTIVE);

      while (orders.next()) {
        OrderDTO order = (OrderDTO) orders.get()[0];
        orderBL.set(order);
        orderBL.setStatus(executorId, Constants.ORDER_STATUS_SUSPENDED_AGEING);
      }

      orders.close();
    }

    // status changed from suspended to active
    // re-active suspended customer orders
    if (!couldLogin && status.getCanLogin() == 1) {
      LOG.debug(
          "User "
              + user.getId()
              + " can now log-in to the system. Activating previously suspended orders.");

      OrderBL orderBL = new OrderBL();
      ScrollableResults orders =
          new OrderDAS().findByUser_Status(user.getId(), Constants.ORDER_STATUS_SUSPENDED_AGEING);

      while (orders.next()) {
        OrderDTO order = (OrderDTO) orders.get()[0];
        orderBL.set(order);
        orderBL.setStatus(executorId, Constants.ORDER_STATUS_ACTIVE);
      }

      orders.close();
    }

    // perform callbacks and notifications
    performAgeingCallback(user, oldStatus, status);
    sendAgeingNotification(user, oldStatus, status);

    // emit NewUserStatusEvent
    NewUserStatusEvent event =
        new NewUserStatusEvent(
            user.getCompany().getId(), user.getId(), oldStatus.getId(), status.getId());
    EventManager.process(event);
  }