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); } }
/** * @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); } }
public void set(Integer id) { invoice = invoiceDas.find(id); }
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; }