public BillingProcessWS(BillingProcessDTO dto) {
    this.id = dto.getId();
    this.entityId = dto.getEntity() != null ? dto.getEntity().getId() : null;
    this.periodUnitId = dto.getPeriodUnit() != null ? dto.getPeriodUnit().getId() : null;
    this.periodValue = dto.getPeriodValue();
    this.billingDate = dto.getBillingDate();
    this.isReview = dto.getIsReview();
    this.retriesToDo = dto.getRetriesToDo();

    // invoice ID's
    if (!dto.getInvoices().isEmpty()) {
      invoiceIds = new ArrayList<Integer>(dto.getInvoices().size());
      for (InvoiceDTO invoice : dto.getInvoices()) invoiceIds.add(invoice.getId());
    }

    // order processes
    if (!dto.getOrderProcesses().isEmpty()) {
      orderProcesses = new ArrayList<OrderProcessWS>(dto.getOrderProcesses().size());
      for (OrderProcessDTO process : dto.getOrderProcesses())
        orderProcesses.add(new OrderProcessWS(process));
    }

    if (!dto.getProcessRuns().isEmpty()) {
      // billing process runs
      processRuns = new ArrayList<ProcessRunWS>(dto.getProcessRuns().size());
      for (ProcessRunDTO run : dto.getProcessRuns()) processRuns.add(new ProcessRunWS(run));
    }
  }
  public static Date getEndOfProcessPeriod(BillingProcessDTO process) throws SessionInternalError {
    GregorianCalendar cal = new GregorianCalendar();

    cal.setTime(process.getBillingDate());
    cal.add(MapPeriodToCalendar.map(process.getPeriodUnit().getId()), process.getPeriodValue());

    return cal.getTime();
  }
  public Integer findOrCreate(BillingProcessDTO dto) {
    billingProcess =
        billingProcessDas.isPresent(
            dto.getEntity().getId(), dto.getIsReview(), dto.getBillingDate());
    if (billingProcess == null) {
      create(dto);
    }

    return billingProcess.getId();
  }
  public Integer create(BillingProcessDTO dto) {
    // create the record
    billingProcess =
        billingProcessDas.create(
            dto.getEntity(),
            dto.getBillingDate(),
            dto.getPeriodUnit().getId(),
            dto.getPeriodValue(),
            dto.getRetriesToDo());
    billingProcess.setIsReview(dto.getIsReview());
    processRun =
        processRunHome.create(
            billingProcess,
            dto.getBillingDate(),
            0,
            new ProcessRunStatusDAS().find(Constants.PROCESS_RUN_STATUS_RINNING));

    if (dto.getIsReview() == 1) {
      ConfigurationBL config = new ConfigurationBL(dto.getEntity().getId());
      config.getEntity().setReviewStatus(Constants.REVIEW_STATUS_GENERATED);
    }

    return billingProcess.getId();
  }
  public BillingProcessDTOEx getDtoEx(Integer language) {
    BillingProcessDTOEx retValue = new BillingProcessDTOEx();

    retValue.setBillingDate(billingProcess.getBillingDate());
    retValue.setEntity(billingProcess.getEntity());
    retValue.setId(billingProcess.getId());
    retValue.setPeriodUnit(billingProcess.getPeriodUnit());
    retValue.setPeriodValue(billingProcess.getPeriodValue());
    retValue.setIsReview(billingProcess.getIsReview());

    // now add the runs and grand total
    BillingProcessRunDTOEx grandTotal = new BillingProcessRunDTOEx();
    int totalInvoices = 0;
    int runsCounter = 0;
    List<BillingProcessRunDTOEx> runs = new ArrayList<BillingProcessRunDTOEx>();
    // go throuhg every run
    for (Iterator it = billingProcess.getProcessRuns().iterator(); it.hasNext(); ) {
      ProcessRunDTO run = (ProcessRunDTO) it.next();
      BillingProcessRunBL runBL = new BillingProcessRunBL(run);
      BillingProcessRunDTOEx runDto = runBL.getDTO(language);
      runs.add(runDto);
      runsCounter++;

      // add statistic for InProgress run proccess in DTO
      if (run.getPaymentFinished() == null) {
        addRuntimeStatistic(run.getBillingProcess().getId(), language, runDto);
      }

      LOG.debug(
          "Run:" + run.getId() + " has " + run.getProcessRunTotals().size() + " total records");
      // go over the totals, since there's one per currency
      for (Iterator it2 = runDto.getTotals().iterator(); it2.hasNext(); ) {
        // the total to process
        BillingProcessRunTotalDTOEx totalDto = (BillingProcessRunTotalDTOEx) it2.next();

        BillingProcessRunTotalDTOEx sum = getTotal(totalDto.getCurrency(), grandTotal.getTotals());

        BigDecimal totalTmp = totalDto.getTotalInvoiced().add(sum.getTotalInvoiced());
        sum.setTotalInvoiced(totalTmp);

        totalTmp = totalDto.getTotalPaid().add(sum.getTotalPaid());
        sum.setTotalPaid(totalTmp);

        // can't add up the not paid, because many runs will try to
        // get the same invoices paid, so the not paid field gets
        // duplicated amounts.
        totalTmp = sum.getTotalInvoiced().subtract(sum.getTotalPaid());
        sum.setTotalNotPaid(totalTmp);

        // make sure this total has the currency name initialized
        if (sum.getCurrencyName() == null) {
          CurrencyBL currency = new CurrencyBL(sum.getCurrency().getId());
          sum.setCurrencyName(currency.getEntity().getDescription(language));
        }
        // add the payment method totals
        for (Enumeration en = totalDto.getPmTotals().keys(); en.hasMoreElements(); ) {
          String method = (String) en.nextElement();
          BigDecimal methodTotal = new BigDecimal(totalDto.getPmTotals().get(method).toString());

          if (sum.getPmTotals().containsKey(method)) {
            if (sum.getPmTotals().get(method) != null) {
              methodTotal =
                  methodTotal.add(
                      new BigDecimal(((Float) sum.getPmTotals().get(method)).toString()));
            }
          }
          sum.getPmTotals().put(method, new Float(methodTotal.floatValue()));
        }

        LOG.debug(
            "Added total to run dto. PMs in total:"
                + sum.getPmTotals().size()
                + " now grandTotal totals:"
                + grandTotal.getTotals().size());
      }
      totalInvoices += runDto.getInvoicesGenerated();
    }

    grandTotal.setInvoicesGenerated(new Integer(totalInvoices));

    retValue.setRetries(new Integer(runsCounter));
    retValue.setRuns(runs);
    retValue.setGrandTotal(grandTotal);
    retValue.setBillingDateEnd(getEndOfProcessPeriod(billingProcess));
    retValue.setOrdersProcessed(new Integer(billingProcess.getOrderProcesses().size()));

    return retValue;
  }
  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;
  }
  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;
  }