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; }
/** * This will remove all the records (sql delete, not just flag them). It will also update the * related orders if applicable */ public void delete(Integer executorId) throws SessionInternalError { if (invoice == null) { throw new SessionInternalError("An invoice has to be set before delete"); } // prevent a delegated Invoice from being deleted if (invoice.getDelegatedInvoiceId() != null && invoice.getDelegatedInvoiceId().intValue() > 0) { SessionInternalError sie = new SessionInternalError("A carried forward Invoice cannot be deleted"); sie.setErrorMessages( new String[] {"InvoiceDTO,invoice,invoice.error.fkconstraint," + invoice.getId()}); throw sie; } // start by updating purchase_order.next_billable_day if applicatble // for each of the orders included in this invoice for (OrderProcessDTO orderProcess : (Collection<OrderProcessDTO>) invoice.getOrderProcesses()) { OrderDTO order = orderProcess.getPurchaseOrder(); if (order.getNextBillableDay() == null) { // the next billable day doesn't need updating if (order.getStatusId().equals(Constants.ORDER_STATUS_FINISHED)) { OrderBL orderBL = new OrderBL(order); orderBL.setStatus(null, Constants.ORDER_STATUS_ACTIVE); } continue; } // only if this invoice is the responsible for the order's // next billable day if (order.getNextBillableDay().equals(orderProcess.getPeriodEnd())) { order.setNextBillableDay(orderProcess.getPeriodStart()); if (order.getStatusId().equals(Constants.ORDER_STATUS_FINISHED)) { OrderBL orderBL = new OrderBL(order); orderBL.setStatus(null, Constants.ORDER_STATUS_ACTIVE); } } } // go over the order process records again just to delete them // we are done with this order, delete the process row for (OrderProcessDTO orderProcess : (Collection<OrderProcessDTO>) invoice.getOrderProcesses()) { OrderDTO order = orderProcess.getPurchaseOrder(); OrderProcessDAS das = new OrderProcessDAS(); order.getOrderProcesses().remove(orderProcess); das.delete(orderProcess); } invoice.getOrderProcesses().clear(); // get rid of the contact associated with this invoice try { ContactBL contact = new ContactBL(); if (contact.setInvoice(invoice.getId())) { contact.delete(); } } catch (Exception e1) { LOG.error("Exception deleting the contact of an invoice", e1); } // remove the payment link/s PaymentBL payment = new PaymentBL(); Iterator<PaymentInvoiceMapDTO> it = invoice.getPaymentMap().iterator(); while (it.hasNext()) { PaymentInvoiceMapDTO map = it.next(); payment.removeInvoiceLink(map.getId()); invoice.getPaymentMap().remove(map); // needed because the collection has changed it = invoice.getPaymentMap().iterator(); } // log that this was deleted, otherwise there will be no trace if (executorId != null) { eLogger.audit( executorId, invoice.getBaseUser().getId(), Constants.TABLE_INVOICE, invoice.getId(), EventLogger.MODULE_INVOICE_MAINTENANCE, EventLogger.ROW_DELETED, null, null, null); } // before delete the invoice most delete the reference in table // PAYMENT_INVOICE new PaymentInvoiceMapDAS().deleteAllWithInvoice(invoice); Set<InvoiceDTO> invoices = invoice.getInvoices(); if (invoices.size() > 0) { for (InvoiceDTO delegate : invoices) { // set status to unpaid as against carried delegate.setInvoiceStatus(new InvoiceStatusDAS().find(Constants.INVOICE_STATUS_UNPAID)); // remove delegated invoice link delegate.setInvoice(null); getHome().save(delegate); } } // now delete the invoice itself getHome().delete(invoice); getHome().flush(); }
/** * 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; }