Exemplo n.º 1
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;
  }
Exemplo n.º 2
0
  private boolean processOrdersForUser(
      UserDTO user,
      Integer entityId,
      BillingProcessDTO process,
      boolean isReview,
      boolean onlyRecurring,
      boolean useProcessDateForInvoice,
      int maximumPeriods,
      Hashtable<TimePeriod, NewInvoiceDTO> newInvoices) {

    boolean includedOrders = false;
    Integer userId = user.getUserId();

    LOG.debug("Processing orders for user " + userId);

    // initialize the subaccounts iterator if this user is a parent
    Iterator subAccountsIt = null;
    if (user.getCustomer().getIsParent() != null
        && user.getCustomer().getIsParent().intValue() == 1) {
      UserBL parent = new UserBL(userId);
      subAccountsIt = parent.getEntity().getCustomer().getChildren().iterator();
    }

    // get the orders that might be processable for this user
    OrderDAS orderDas = new OrderDAS();
    Collection<OrderDTO> orders = orderDas.findByUser_Status(userId, Constants.ORDER_STATUS_ACTIVE);

    // go through each of them, and update the DTO if it applies
    for (OrderDTO order : orders) {
      LOG.debug("Processing order :" + order.getId());
      // apply any order processing filter pluggable task
      try {
        PluggableTaskManager taskManager =
            new PluggableTaskManager(entityId, Constants.PLUGGABLE_TASK_ORDER_FILTER);
        OrderFilterTask task = (OrderFilterTask) taskManager.getNextClass();
        boolean isProcessable = true;
        while (task != null) {
          isProcessable = task.isApplicable(order, process);
          if (!isProcessable) {
            break; // no need to keep doing more tests
          }
          task = (OrderFilterTask) taskManager.getNextClass();
        }

        // include this order only if it complies with all the
        // rules
        if (isProcessable) {

          LOG.debug("Order processable");

          if (onlyRecurring) {
            if (order.getOrderPeriod().getId() != Constants.ORDER_PERIOD_ONCE) {
              includedOrders = true;
              LOG.debug("It is not one-timer. " + "Generating invoice");
            }
          } else {
            includedOrders = true;
          }
          /*
           * now find if there is already an invoice being
           * generated for the given due date period
           */
          // find the due date that applies to this order
          OrderBL orderBl = new OrderBL();
          orderBl.set(order);
          TimePeriod dueDatePeriod = orderBl.getDueDate();
          // look it up in the hashtable
          NewInvoiceDTO thisInvoice = (NewInvoiceDTO) newInvoices.get(dueDatePeriod);
          if (thisInvoice == null) {
            LOG.debug(
                "Adding new invoice for period "
                    + dueDatePeriod
                    + " process date:"
                    + process.getBillingDate());
            // we need a new one with this period
            // define the invoice date
            thisInvoice = new NewInvoiceDTO();
            if (useProcessDateForInvoice) {
              thisInvoice.setDate(process.getBillingDate());
            } else {
              thisInvoice.setDate(
                  orderBl.getInvoicingDate(),
                  order.getOrderPeriod().getId() != Constants.ORDER_PERIOD_ONCE);
            }
            thisInvoice.setIsReview(isReview ? new Integer(1) : new Integer(0));
            thisInvoice.setCarriedBalance(BigDecimal.ZERO);
            thisInvoice.setDueDatePeriod(dueDatePeriod);
          } else {
            LOG.debug("invoice found for period " + dueDatePeriod);
            if (!useProcessDateForInvoice) {
              thisInvoice.setDate(
                  orderBl.getInvoicingDate(),
                  order.getOrderPeriod().getId() != Constants.ORDER_PERIOD_ONCE);
            }
          }
          /*
           * The order periods plug-in might not add any period. This should not happen
           * but if it does, the invoice should not be included
           */
          if (addOrderToInvoice(
              entityId, order, thisInvoice, process.getBillingDate(), maximumPeriods)) {
            // add or replace
            newInvoices.put(dueDatePeriod, thisInvoice);
          }
          LOG.debug("After putting period there are " + newInvoices.size() + " periods.");

          // here it would be easy to update this order
          // to_process and
          // next_billable_time. I can't do that because these
          // fields
          // will be read by the following tasks, and they
          // will asume
          // they are not modified.
        }

      } catch (PluggableTaskException e) {
        LOG.fatal("Problems handling order filter task.", e);
        throw new SessionInternalError("Problems handling order filter task.");
      } catch (TaskException e) {
        LOG.fatal("Problems excecuting order filter task.", e);
        throw new SessionInternalError("Problems executing order filter task.");
      }
    } // for - all the orders for this user

    // see if there is any subaccounts to include in this invoice
    while (subAccountsIt != null) { // until there are no more subaccounts (subAccountsIt != null) {
      CustomerDTO customer = null;
      while (subAccountsIt.hasNext()) {
        customer = (CustomerDTO) subAccountsIt.next();
        if (customer.getInvoiceChild() == null || customer.getInvoiceChild().intValue() == 0) {
          break;
        } else {
          LOG.debug("Subaccount not included in parent's invoice " + customer.getId());
          customer = null;
        }
      }
      if (customer != null) {
        userId = customer.getBaseUser().getUserId();
        // if the child does not have any orders to invoice, this should
        // not affect the current value of includedOrders
        if (processOrdersForUser(
            customer.getBaseUser(),
            entityId,
            process,
            isReview,
            onlyRecurring,
            useProcessDateForInvoice,
            maximumPeriods,
            newInvoices)) {
          // if ANY child has orders to invoice, it is enough for includedOrders to be true
          includedOrders = true;
        }
        LOG.debug("Now processing subaccount " + userId);

      } else {
        subAccountsIt = null;
        LOG.debug("No more subaccounts to process");
      }
    }

    return includedOrders;
  }
Exemplo n.º 3
0
  /**
   * Generates one single invoice for one single purchase order. This is meant to be called outside
   * the billing process.
   *
   * @param orderId
   * @return
   * @throws PluggableTaskException
   * @throws SessionInternalError
   */
  public InvoiceDTO generateInvoice(Integer orderId, Integer invoiceId)
      throws PluggableTaskException, SessionInternalError, SQLException {
    InvoiceDTO retValue = null;
    // find the order
    OrderBL order = new OrderBL(orderId);
    // define some data
    Integer entityId = order.getEntity().getUser().getEntity().getId();
    ConfigurationBL config = new ConfigurationBL(entityId);
    int maxPeriods = config.getEntity().getMaximumPeriods();
    boolean paymentApplication = config.getEntity().getAutoPaymentApplication() == 1;
    // The user could be the parent of a sub-account
    Integer userId = findUserId(order.getEntity());
    Date processDate = Calendar.getInstance().getTime();
    processDate = Util.truncateDate(processDate);
    // create the my invoice
    NewInvoiceDTO newInvoice = new NewInvoiceDTO();
    newInvoice.setDate(processDate);
    newInvoice.setIsReview(new Integer(0));
    // find the due date that applies
    TimePeriod period = order.getDueDate();
    newInvoice.setDueDatePeriod(period);
    // this is an isolated invoice that doesn't care about previous
    // overdue invoices
    newInvoice.setCarriedBalance(BigDecimal.ZERO);
    newInvoice.setInvoiceStatus(new InvoiceStatusDAS().find(Constants.INVOICE_STATUS_UNPAID));

    try {
      // put the order in the invoice using all the pluggable taks stuff
      addOrderToInvoice(entityId, order.getEntity(), newInvoice, processDate, maxPeriods);

      // this means that the user is trying to generate an invoice from
      // an order that the configurated tasks have rejected. Therefore
      // either this is the case an generating this invoice doesn't make
      // sense, or some business rules in the tasks have to be changed
      // (probably with a personalized task for this entity)
      if (newInvoice.getOrders().size() == 0) {
        return null;
      }

      // process events before orders added to invoice
      processOrderToInvoiceEvents(newInvoice, entityId);

      // generate the invoice lines
      composeInvoice(entityId, userId, newInvoice);

      // process events after orders added to invoice
      processOrderAddedOnInvoiceEvents(newInvoice, entityId);

      // put the resulting invoice in the database
      if (invoiceId == null) {
        // it is a new invoice from a singe order
        retValue =
            generateDBInvoice(userId, newInvoice, null, Constants.ORDER_PROCESS_ORIGIN_MANUAL);
        // try to get this new invioce paid by previously unlinked
        // payments
        if (paymentApplication) {
          PaymentBL pBL = new PaymentBL();
          pBL.automaticPaymentApplication(retValue);
        }
      } else {
        // it is an order going into an existing invoice
        InvoiceBL invoice = new InvoiceBL(invoiceId);
        boolean isUnpaid = invoice.getEntity().getToProcess() == 1;
        invoice.update(newInvoice);
        retValue = invoice.getEntity();
        createOrderProcess(newInvoice, retValue, null, Constants.ORDER_PROCESS_ORIGIN_MANUAL);
        eLogger.info(
            entityId,
            userId,
            invoiceId,
            EventLogger.MODULE_INVOICE_MAINTENANCE,
            EventLogger.INVOICE_ORDER_APPLIED,
            Constants.TABLE_INVOICE);
        // if the invoice is now not payable, take the user
        // out of ageing
        if (isUnpaid && retValue.getToProcess() == 0) {
          AgeingBL ageing = new AgeingBL();
          ageing.out(retValue.getBaseUser(), null);
        }
      }
    } catch (TaskException e) {
      // this means that the user is trying to generate an invoice from
      // an order that the configurated tasks have rejected. Therefore
      // either this is the case an generating this invoice doesn't make
      // sense, or some business rules in the tasks have to be changed
      // (probably with a personalized task for this entity)
      LOG.warn("Exception in generate invoice ", e);
    }

    if (retValue != null) {
      InvoicesGeneratedEvent generatedEvent = new InvoicesGeneratedEvent(entityId, null);
      generatedEvent.getInvoiceIds().add(retValue.getId());
      EventManager.process(generatedEvent);
    }

    return retValue;
  }