Example #1
0
  private void createOrderProcess(
      NewInvoiceDTO newInvoice, InvoiceDTO invoice, BillingProcessDTO process, Integer origin)
      throws SessionInternalError {
    LOG.debug("Generating order process records...");
    // update the orders involved, now that their old data is not needed
    // anymore
    for (int f = 0; f < newInvoice.getOrders().size(); f++) {

      OrderDTO order = (OrderDTO) newInvoice.getOrders().get(f);

      LOG.debug(" ... order " + order.getId());
      // this will help later
      List<PeriodOfTime> periodsList = newInvoice.getPeriods().get(f);
      Date startOfBillingPeriod = (Date) periodsList.get(0).getStart();
      Date endOfBillingPeriod = periodsList.get(periodsList.size() - 1).getEnd();
      Integer periods = (Integer) newInvoice.getPeriods().get(f).size();

      // We don't update orders if this is just a review
      if (newInvoice.getIsReview().intValue() == 0) {
        // update the to_process if applicable
        updateStatusFinished(order, startOfBillingPeriod, endOfBillingPeriod);

        // update this order process field
        updateNextBillableDay(order, endOfBillingPeriod);
      }

      // create the period and update the order-invoice relationship
      try {

        OrderProcessDAS das = new OrderProcessDAS();
        OrderProcessDTO orderProcess = new OrderProcessDTO();
        orderProcess.setPeriodStart(startOfBillingPeriod);
        orderProcess.setPeriodEnd(endOfBillingPeriod);
        orderProcess.setIsReview(newInvoice.getIsReview());
        orderProcess.setPurchaseOrder(order);
        InvoiceDAS invDas = new InvoiceDAS();
        orderProcess.setInvoice(invDas.find(invoice.getId()));
        BillingProcessDAS proDas = new BillingProcessDAS();
        orderProcess.setBillingProcess(process != null ? proDas.find(process.getId()) : null);
        orderProcess.setPeriodsIncluded(periods);
        orderProcess.setOrigin(origin);
        orderProcess = das.save(orderProcess);
        LOG.debug(
            "created order process id " + orderProcess.getId() + " for order " + order.getId());

      } catch (Exception e) {
        throw new SessionInternalError(e);
      }
    }
  }
  /**
   * Review all users for the given day, and age those that have outstanding invoices over the set
   * number of days for an ageing step.
   *
   * @param steps ageing steps
   * @param today today's date
   * @param executorId executor id
   */
  public void reviewAllUsers(
      Integer entityId, Set<AgeingEntityStepDTO> steps, Date today, Integer executorId) {
    LOG.debug("Reviewing users for entity " + entityId + " ...");

    // go over all the users already in the ageing system
    for (UserDTO user : new UserDAS().findAgeing(entityId)) {
      ageUser(steps, user, today, executorId);
    }

    // go over the active users with payable invoices
    try {
      UserDAS userDas = new UserDAS();
      InvoiceDAS invoiceDas = new InvoiceDAS();

      CachedRowSet users = new UserBL().findActiveWithOpenInvoices(entityId);

      while (users.next()) {
        Integer userId = users.getInt(1);
        UserDTO user = userDas.find(userId);
        int gracePeriod = getGracePeriod(entityId);

        LOG.debug(
            "Reviewing invoices for user "
                + user.getId()
                + " using a grace period of "
                + gracePeriod
                + " days.");

        for (InvoiceDTO invoice : invoiceDas.findProccesableByUser(user)) {
          if (isInvoiceOverdue(invoice, user, gracePeriod, today)) {
            ageUser(steps, user, today, executorId);
            break;
          }
        }
      }

    } catch (SQLException e) {
      LOG.error("Failed to fetch users with payable invoices.", e);

    } catch (NamingException e) {
      LOG.error("Exception fetching users with payable invoices.", e);
    }
  }
Example #3
0
  /**
   * @param userId
   * @param newInvoice
   * @param process It can be null.
   */
  public void create(
      Integer userId, NewInvoiceDTO newInvoice, BillingProcessDTO process, Integer executorUserId) {
    // find out the entity id
    PreferenceBL pref = new PreferenceBL();
    UserBL user = null;
    Integer entityId;
    if (process != null) {
      entityId = process.getEntity().getId();
    } else {
      // this is a manual invoice, there's no billing process
      user = new UserBL(userId);
      entityId = user.getEntityId(userId);
    }

    // verify if this entity is using the 'continuous invoice date'
    // preference
    try {
      pref.set(entityId, Constants.PREFERENCE_CONTINUOUS_DATE);

      if (StringUtils.isNotBlank(pref.getString())) {
        Date lastDate = com.sapienter.jbilling.common.Util.parseDate(pref.getString());
        LOG.debug("Last date invoiced: " + lastDate);

        if (lastDate.after(newInvoice.getBillingDate())) {
          LOG.debug(
              "Due date is before the last recorded date. Moving due date forward for continuous invoice dates.");
          newInvoice.setBillingDate(lastDate);

        } else {
          // update the lastest date only if this is not a review
          if (newInvoice.getIsReview() == null || newInvoice.getIsReview() == 0) {
            pref.createUpdateForEntity(
                entityId,
                Constants.PREFERENCE_CONTINUOUS_DATE,
                com.sapienter.jbilling.common.Util.parseDate(newInvoice.getBillingDate()));
          }
        }
      }
    } catch (EmptyResultDataAccessException e) {
      // not interested, ignore
    }

    // in any case, ensure that the due date is => that invoice date
    if (newInvoice.getDueDate().before(newInvoice.getBillingDate())) {
      LOG.debug("Due date before billing date, moving date up to billing date.");
      newInvoice.setDueDate(newInvoice.getBillingDate());
    }

    // ensure that there are only so many decimals in the invoice
    int decimals = Constants.BIGDECIMAL_SCALE;
    try {
      pref.set(entityId, Constants.PREFERENCE_INVOICE_DECIMALS);
      decimals = pref.getInt();
    } catch (EmptyResultDataAccessException e) {
      // not interested, ignore
    }

    LOG.debug("Rounding " + newInvoice.getTotal() + " to " + decimals + " decimals");
    if (newInvoice.getTotal() != null) {
      newInvoice.setTotal(newInvoice.getTotal().setScale(decimals, Constants.BIGDECIMAL_ROUND));
    }
    if (newInvoice.getBalance() != null) {
      newInvoice.setBalance(newInvoice.getBalance().setScale(decimals, Constants.BIGDECIMAL_ROUND));
    }

    // some API calls only accept ID's and do not pass meta-fields
    // update and validate meta-fields if they've been populated
    if (newInvoice.getMetaFields() != null && !newInvoice.getMetaFields().isEmpty()) {
      newInvoice.updateMetaFieldsWithValidation(entityId, newInvoice);
    }

    // create the invoice row
    invoice = invoiceDas.create(userId, newInvoice, process);

    // add delegated/included invoice links
    if (newInvoice.getIsReview() == 0) {
      for (InvoiceDTO dto : newInvoice.getInvoices()) {
        dto.setInvoice(invoice);
      }
    }

    // add the customer notes if it applies
    try {
      pref.set(entityId, Constants.PREFERENCE_SHOW_NOTE_IN_INVOICE);
    } catch (EmptyResultDataAccessException e) {
      // use the default then
    }

    if (pref.getInt() == 1) {
      if (user == null) {
        user = new UserBL(userId);
      }
      if (user.getEntity().getCustomer() != null
          && user.getEntity().getCustomer().getNotes() != null) {
        // append the notes if there's some text already there
        newInvoice.setCustomerNotes(
            (newInvoice.getCustomerNotes() == null)
                ? user.getEntity().getCustomer().getNotes()
                : newInvoice.getCustomerNotes() + " " + user.getEntity().getCustomer().getNotes());
      }
    }
    // notes might come from the customer, the orders, or both
    if (newInvoice.getCustomerNotes() != null && newInvoice.getCustomerNotes().length() > 0) {
      invoice.setCustomerNotes(newInvoice.getCustomerNotes());
    }

    // calculate/compose the number
    String numberStr = null;
    if (newInvoice.getIsReview() != null && newInvoice.getIsReview() == 1) {
      // invoices for review will be seen by the entity employees
      // so the entity locale will be used
      EntityBL entity = new EntityBL(entityId);
      ResourceBundle bundle = ResourceBundle.getBundle("entityNotifications", entity.getLocale());
      numberStr = bundle.getString("invoice.review.number");
    } else if (newInvoice.getPublicNumber() == null || newInvoice.getPublicNumber().length() == 0) {
      String prefix;
      try {
        pref.set(entityId, Constants.PREFERENCE_INVOICE_PREFIX);
        prefix = pref.getString();
        if (prefix == null) {
          prefix = "";
        }
      } catch (EmptyResultDataAccessException e) {
        prefix = "";
      }
      int number;
      try {
        pref.set(entityId, Constants.PREFERENCE_INVOICE_NUMBER);
        number = pref.getInt();
      } catch (EmptyResultDataAccessException e1) {
        number = 1;
      }

      numberStr = prefix + number;
      // update for the next time
      number++;
      pref.createUpdateForEntity(entityId, Constants.PREFERENCE_INVOICE_NUMBER, number);
    } else { // for upload of legacy invoices
      numberStr = newInvoice.getPublicNumber();
    }

    invoice.setPublicNumber(numberStr);

    // set the invoice's contact info with the current user's primary
    ContactBL contact = new ContactBL();
    contact.set(userId);
    contact.createForInvoice(contact.getDTO(), invoice.getId());

    // add a log row for convenience
    if (null != executorUserId) {
      eLogger.audit(
          executorUserId,
          userId,
          Constants.TABLE_INVOICE,
          invoice.getId(),
          EventLogger.MODULE_INVOICE_MAINTENANCE,
          EventLogger.ROW_CREATED,
          null,
          null,
          null);
    } else {
      eLogger.auditBySystem(
          entityId,
          userId,
          Constants.TABLE_INVOICE,
          invoice.getId(),
          EventLogger.MODULE_INVOICE_MAINTENANCE,
          EventLogger.ROW_CREATED,
          null,
          null,
          null);
    }
  }
Example #4
0
 public void set(Integer id) {
   invoice = invoiceDas.find(id);
 }
Example #5
0
  public InvoiceDTO[] generateInvoice(
      BillingProcessDTO process, UserDTO user, boolean isReview, boolean onlyRecurring)
      throws SessionInternalError {

    Integer userId = user.getUserId();
    Integer entityId = user.getEntity().getId();

    // get the configuration
    boolean useProcessDateForInvoice = true;
    int maximumPeriods = 1;
    boolean paymentApplication = false;
    try {
      ConfigurationBL config = new ConfigurationBL(process.getEntity().getId());
      useProcessDateForInvoice = config.getEntity().getInvoiceDateProcess() == 1;
      maximumPeriods = config.getEntity().getMaximumPeriods();
      paymentApplication = config.getEntity().getAutoPaymentApplication() == 1;
    } catch (Exception e) {
      // swallow exception
    }

    // this contains the generated invoices, one per due date
    // found in the applicable purchase orders.
    // The key is the object TimePeriod
    Hashtable<TimePeriod, NewInvoiceDTO> newInvoices = new Hashtable<TimePeriod, NewInvoiceDTO>();
    InvoiceDTO[] retValue = null;

    LOG.debug(
        "In generateInvoice for user " + userId + " process date:" + process.getBillingDate());

    /*
     * Go through the orders first
     * This method will recursively call itself to find sub-accounts in any
     * level
     */
    boolean includedOrders =
        processOrdersForUser(
            user,
            entityId,
            process,
            isReview,
            onlyRecurring,
            useProcessDateForInvoice,
            maximumPeriods,
            newInvoices);

    if (!includedOrders || newInvoices.size() == 0) {
      // check if invoices without orders are allowed
      PreferenceBL preferenceBL = new PreferenceBL();
      try {
        preferenceBL.set(entityId, Constants.PREFERENCE_ALLOW_INVOICES_WITHOUT_ORDERS);
      } catch (EmptyResultDataAccessException fe) {
        // use default
      }

      if (preferenceBL.getInt() == 0) {
        LOG.debug("No applicable orders. No invoice generated (skipping invoices).");
        return null;
      }
    }

    if (!isReview) {
      for (Map.Entry<TimePeriod, NewInvoiceDTO> newInvoiceEntry : newInvoices.entrySet()) {
        // process events before orders added to invoice
        processOrderToInvoiceEvents(newInvoiceEntry.getValue(), entityId);
      }
    }

    /*
     * Include those invoices that should've been paid
     * (or have negative balance, as credits)
     */
    LOG.debug("Considering overdue invoices");
    // find the invoice home interface
    InvoiceDAS invoiceDas = new InvoiceDAS();
    // any of the new invoices being created could hold the overdue invoices
    NewInvoiceDTO holder =
        newInvoices.isEmpty() ? null : (NewInvoiceDTO) newInvoices.elements().nextElement();

    Collection dueInvoices = invoiceDas.findWithBalanceByUser(user);
    LOG.debug("Processing invoices for user " + user.getUserId());
    // go through each of them, and update the DTO if it applies

    for (Iterator it = dueInvoices.iterator(); it.hasNext(); ) {
      InvoiceDTO invoice = (InvoiceDTO) it.next();
      if (invoice.getToProcess() == InvoiceDTO.DO_NOT_PROCESS) {
        LOG.debug("skipping invoice " + invoice.getId() + " because DO_NOT_PROCESS is set");
        continue;
      }
      LOG.debug("Processing invoice " + invoice.getId());
      // apply any invoice processing filter pluggable task
      try {
        PluggableTaskManager taskManager =
            new PluggableTaskManager(entityId, Constants.PLUGGABLE_TASK_INVOICE_FILTER);
        InvoiceFilterTask task = (InvoiceFilterTask) taskManager.getNextClass();
        boolean isProcessable = true;
        while (task != null) {
          isProcessable = task.isApplicable(invoice, process);
          if (!isProcessable) {
            break; // no need to keep doing more tests
          }
          task = (InvoiceFilterTask) taskManager.getNextClass();
        }

        // include this invoice only if it complies with all the rules
        if (isProcessable) {
          // check for an invoice
          if (holder == null) {
            // Since there are no new invoices (therefore no orders),
            // don't let invoices with positive balances generate
            // an invoice.
            if (BigDecimal.ZERO.compareTo(invoice.getBalance()) < 0) {
              continue;
            }

            // no invoice/s yet (no applicable orders), so create one
            holder = new NewInvoiceDTO();
            holder.setDate(process.getBillingDate());
            holder.setIsReview(isReview ? new Integer(1) : new Integer(0));
            holder.setCarriedBalance(BigDecimal.ZERO);
            holder.setInvoiceStatus(new InvoiceStatusDAS().find(Constants.INVOICE_STATUS_UNPAID));

            // need to set a due date, so use the order default
            OrderBL orderBl = new OrderBL();
            OrderDTO order = new OrderDTO();
            order.setBaseUserByUserId(user);
            orderBl.set(order);
            TimePeriod dueDatePeriod = orderBl.getDueDate();

            holder.setDueDatePeriod(dueDatePeriod);
            newInvoices.put(dueDatePeriod, holder);
          }

          InvoiceBL ibl = new InvoiceBL(invoice);
          holder.addInvoice(ibl.getDTO());
          // for those invoices wiht only overdue invoices, the
          // currency has to be initialized

          if (holder.getCurrency() == null) {
            holder.setCurrency(invoice.getCurrency());
          } else if (holder.getCurrency().getId() != invoice.getCurrency().getId()) {
            throw new SessionInternalError(
                "An invoice with different "
                    + "currency is not supported. "
                    + "Currency = "
                    + holder.getCurrency().getId()
                    + "invoice = "
                    + invoice.getId());
          }
          // update the amount of the new invoice that is due to
          // previously unpaid overdue invoices

          // carry the remaining balance, plus the previously carried balance to the new invoice
          BigDecimal balance =
              (invoice.getBalance() == null) ? BigDecimal.ZERO : invoice.getBalance();
          BigDecimal carried = balance.add(holder.getCarriedBalance());
          holder.setCarriedBalance(carried);
        }

        LOG.debug("invoice " + invoice.getId() + " result " + isProcessable);

      } catch (PluggableTaskException e) {
        LOG.fatal("Problems handling task invoice filter.", e);
        throw new SessionInternalError("Problems handling task invoice filter.");
      } catch (TaskException e) {
        LOG.fatal("Problems excecuting task invoice filter.", e);
        throw new SessionInternalError("Problems executing task invoice filter.");
      }
    }

    if (newInvoices.size() == 0) {
      // no orders or invoices for this invoice
      LOG.debug("No applicable orders or invoices. No invoice generated (skipping invoices).");
      return null;
    }

    try {
      retValue = new InvoiceDTO[newInvoices.size()];
      int index = 0;
      for (NewInvoiceDTO invoice : newInvoices.values()) {
        LOG.debug(
            "Processing invoice "
                + invoice.getId()
                + " "
                + invoice.getBalance()
                + " "
                + invoice.getTotal());
        /*
         * Apply invoice composition tasks to the new invoices object
         */
        composeInvoice(entityId, user.getUserId(), invoice);

        LOG.debug(
            "  after composeInvoice "
                + invoice.getId()
                + " "
                + invoice.getBalance()
                + " "
                + invoice.getTotal());
        if (!isReview) {
          // process events after orders added to invoice
          processOrderAddedOnInvoiceEvents(invoice, entityId);
          LOG.debug("processing old invoices");
          for (InvoiceDTO oldInvoice : invoice.getInvoices()) {
            // since this invoice is being delegated, mark it as being carried forward
            // so that it is not re-processed later. do not clear the old balance!
            LOG.debug(
                "  setting status unpaid and carried on "
                    + oldInvoice.getId()
                    + " "
                    + oldInvoice.getBalance()
                    + " "
                    + oldInvoice.getTotal());
            oldInvoice.setInvoiceStatus(
                new InvoiceStatusDAS().find(Constants.INVOICE_STATUS_UNPAID_AND_CARRIED));
          }
        }

        /*
         * apply this object to the DB, generating the actual rows
         */
        // only if the invoice generated actually has some lines in it
        if (invoice.areLinesGeneratedEmpty()) {
          LOG.warn(
              "User "
                  + user.getUserId()
                  + " had orders or invoices but"
                  + " the invoice composition task didn't generate any lines.");
          continue;
        }

        // If this is a web services API call, the billing
        // process id is 0. Don't link to the billing process
        // object for API calls.
        retValue[index] =
            generateDBInvoice(
                user.getUserId(),
                invoice,
                (process.getId() != 0 ? process : null),
                Constants.ORDER_PROCESS_ORIGIN_PROCESS);

        // try to get this new invioce paid by previously unlinked
        // payments
        if (paymentApplication && !isReview) {
          PaymentBL pBL = new PaymentBL();
          pBL.automaticPaymentApplication(retValue[index]);
        }

        index++;
      }
    } catch (PluggableTaskException e1) {
      LOG.error("Error handling pluggable tasks when composing an invoice");
      throw new SessionInternalError(e1);
    } catch (TaskException e1) {
      LOG.error("Task exception when composing an invoice");
      throw new SessionInternalError(e1);
    } catch (Exception e1) {
      LOG.error("Error, probably linking payments", e1);
      throw new SessionInternalError(e1);
    }

    InvoicesGeneratedEvent generatedEvent = new InvoicesGeneratedEvent(entityId, process.getId());
    generatedEvent.addInvoices(retValue);
    EventManager.process(generatedEvent);

    return retValue;
  }