public Integer createUpdate(Integer executorId, BillingProcessConfigurationDTO dto) { configuration = configurationDas.findByEntity(dto.getEntity()); if (configuration != null) { if (!configuration.getGenerateReport().equals(dto.getGenerateReport())) { eLogger.audit( executorId, null, Constants.TABLE_BILLING_PROCESS_CONFIGURATION, configuration.getId(), EventLogger.MODULE_BILLING_PROCESS, EventLogger.ROW_UPDATED, new Integer(configuration.getGenerateReport()), null, null); configuration.setGenerateReport(dto.getGenerateReport()); configuration.setReviewStatus( dto.getGenerateReport() == 1 ? Constants.REVIEW_STATUS_GENERATED : Constants.REVIEW_STATUS_APPROVED); } else { eLogger.audit( executorId, null, Constants.TABLE_BILLING_PROCESS_CONFIGURATION, configuration.getId(), EventLogger.MODULE_BILLING_PROCESS, EventLogger.ROW_UPDATED, null, null, null); } configuration.setNextRunDate(dto.getNextRunDate()); } else { configuration = configurationDas.create(dto.getEntity(), dto.getNextRunDate(), dto.getGenerateReport()); } configuration.setDaysForReport(dto.getDaysForReport()); configuration.setDaysForRetry(dto.getDaysForRetry()); configuration.setRetries(dto.getRetries()); configuration.setPeriodUnit(dto.getPeriodUnit()); configuration.setPeriodValue(dto.getPeriodValue()); configuration.setDueDateUnitId(dto.getDueDateUnitId()); configuration.setDueDateValue(dto.getDueDateValue()); configuration.setDfFm(dto.getDfFm()); configuration.setOnlyRecurring(dto.getOnlyRecurring()); configuration.setInvoiceDateProcess(dto.getInvoiceDateProcess()); configuration.setAutoPayment(dto.getAutoPayment()); configuration.setAutoPaymentApplication(dto.getAutoPaymentApplication()); configuration.setMaximumPeriods(dto.getMaximumPeriods()); return configuration.getId(); }
private void init() { eLogger = EventLogger.getInstance(); billingProcessDas = new BillingProcessDAS(); // now create the run info row processRunHome = new ProcessRunDAS(); }
/** * Moves a user one step forward in the ageing process (move from active -> suspended etc.). The * user will only be moved if they have spent long enough in their present status. * * @param steps ageing steps * @param user user to age * @param today today's date * @return the resulting ageing step for the user after ageing */ public AgeingEntityStepDTO ageUser( Set<AgeingEntityStepDTO> steps, UserDTO user, Date today, Integer executorId) { LOG.debug("Ageing user " + user.getId()); Integer currentStatusId = user.getStatus().getId(); UserStatusDTO nextStatus = null; AgeingEntityStepDTO ageingStep = null; if (currentStatusId.equals(UserDTOEx.STATUS_ACTIVE)) { // send welcome message (initial step after active). nextStatus = getNextAgeingStep(steps, UserDTOEx.STATUS_ACTIVE); } else { // user already in the ageing process ageingStep = new AgeingEntityStepDAS().findStep(user.getEntity().getId(), currentStatusId); if (ageingStep != null) { // determine the next ageing step if (isAgeingRequired(user, ageingStep, today)) { nextStatus = getNextAgeingStep(steps, currentStatusId); LOG.debug( "User " + user.getId() + " needs to be aged to '" + getStatusDescription(nextStatus) + "'"); } } else { // User is in a non-existent ageing status... Either the status was removed or // the data is bad. As a workaround, just move to the next status. nextStatus = getNextAgeingStep(steps, currentStatusId); LOG.warn( "User " + user.getId() + " is in an invalid ageing step. Moving to '" + getStatusDescription(nextStatus) + "'"); } } // set status if (nextStatus != null) { setUserStatus(user, nextStatus, today, null); } else { LOG.debug("Next status is null, no further ageing steps are available."); eLogger.warning( user.getEntity().getId(), user.getUserId(), user.getUserId(), EventLogger.MODULE_USER_MAINTENANCE, EventLogger.NO_FURTHER_STEP, Constants.TABLE_BASE_USER); } return ageingStep; }
public void setReviewApproval(Integer executorId, boolean flag) { eLogger.audit( executorId, null, Constants.TABLE_BILLING_PROCESS_CONFIGURATION, configuration.getId(), EventLogger.MODULE_BILLING_PROCESS, EventLogger.ROW_UPDATED, configuration.getReviewStatus(), null, null); configuration.setReviewStatus( flag ? Constants.REVIEW_STATUS_APPROVED : Constants.REVIEW_STATUS_DISAPPROVED); }
public void purgeReview(Integer entityId, boolean isReview) { BillingProcessDTO review = billingProcessDas.findReview(entityId); if (review == null) { // no review, nothing to delete then return; } // if we are here, a review exists ConfigurationBL configBL = new ConfigurationBL(entityId); if (configBL.getEntity().getGenerateReport().intValue() == 1 && !new Integer(configBL.getEntity().getReviewStatus()) .equals(Constants.REVIEW_STATUS_APPROVED) && !isReview) { eLogger.warning( entityId, null, configBL.getEntity().getId(), EventLogger.MODULE_BILLING_PROCESS, EventLogger.BILLING_REVIEW_NOT_APPROVED, Constants.TABLE_BILLING_PROCESS_CONFIGURATION); } // delete the review LOG.debug("Removing review id = " + review.getId() + " from " + " entity " + entityId); // this is needed while the order process is JPA, but the billing process is Entity OrderProcessDAS processDas = new OrderProcessDAS(); com.sapienter.jbilling.server.process.db.BillingProcessDTO processDto = new BillingProcessDAS().find(review.getId()); for (OrderProcessDTO orderDto : processDto.getOrderProcesses()) { processDas.delete(orderDto); } processDto.getOrderProcesses().clear(); // delete processRunUsers otherwise will be constraint violation for (ProcessRunDTO processRun : review.getProcessRuns()) { new ProcessRunUserDAS().removeProcessRunUsersForProcessRun(processRun.getId()); } // otherwise this line would cascade de delete getHome().delete(review); }
private void init() { eLogger = EventLogger.getInstance(); configurationDas = new BillingProcessConfigurationDAS(); }
private void init() { eLogger = EventLogger.getInstance(); invoiceDas = new InvoiceDAS(); }
/** * 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(); }
/** * @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); } }
private boolean addOrderToInvoice( Integer entityId, OrderDTO order, NewInvoiceDTO newInvoice, Date processDate, int maxPeriods) throws SessionInternalError, TaskException, PluggableTaskException { // require the calculation of the period dates PluggableTaskManager taskManager = new PluggableTaskManager(entityId, Constants.PLUGGABLE_TASK_ORDER_PERIODS); OrderPeriodTask optask = (OrderPeriodTask) taskManager.getNextClass(); if (optask == null) { throw new SessionInternalError( "There has to be " + "one order period pluggable task configured"); } Date start = optask.calculateStart(order); Date end = optask.calculateEnd(order, processDate, maxPeriods, start); List<PeriodOfTime> periods = optask.getPeriods(); // there isn't anything billable from this order if (periods.size() == 0) { return false; } if (start != null && end != null && start.after(end)) { // how come it starts after it ends ??? throw new SessionInternalError( "Calculated for " + "order " + order.getId() + " a period that" + " starts after it ends:" + start + " " + end); } // add this order to the invoice being created newInvoice.addOrder(order, start, end, periods); // prepaid orders shouldn't have to be included // past time. if (order.getBillingTypeId().compareTo(Constants.ORDER_BILLING_PRE_PAID) == 0 && start != null && // it has to be recursive too processDate.after(start)) { eLogger.warning( entityId, order.getBaseUserByUserId().getId(), order.getId(), EventLogger.MODULE_BILLING_PROCESS, EventLogger.BILLING_PROCESS_UNBILLED_PERIOD, Constants.TABLE_PUCHASE_ORDER); LOG.warn("Order " + order.getId() + " is prepaid " + "but has past time not billed."); } // initialize the currency of the new invoice if (newInvoice.getCurrency() == null) { newInvoice.setCurrency(order.getCurrency()); } else { // now we are not supporting orders with different // currencies in the same invoice. Later this could be done if (newInvoice.getCurrency().getId() != order.getCurrency().getId()) { throw new SessionInternalError( "Orders with different " + "currencies not supported in one invoice. " + "Currency = " + newInvoice.getCurrency().getId() + "order = " + order.getId()); } } return true; }
/** * 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; }
/** * BasicAgeingTask * * @author Brian Cowdery * @since 28/04/11 */ public class BasicAgeingTask extends PluggableTask implements IAgeingTask { private static final Logger LOG = Logger.getLogger(BasicAgeingTask.class); private final EventLogger eLogger = EventLogger.getInstance(); private static Calendar calendar = GregorianCalendar.getInstance(); static { calendar.clear(); } private Map<Integer, Integer> gracePeriodCache = new HashMap<Integer, Integer>(); protected int getGracePeriod(Integer entityId) { if (!gracePeriodCache.containsKey(entityId)) { PreferenceBL preference = new PreferenceBL(); preference.set(entityId, Constants.PREFERENCE_GRACE_PERIOD); gracePeriodCache.put(entityId, preference.getInt()); } return gracePeriodCache.get(entityId); } /** * 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); } } /** * Moves a user one step forward in the ageing process (move from active -> suspended etc.). The * user will only be moved if they have spent long enough in their present status. * * @param steps ageing steps * @param user user to age * @param today today's date * @return the resulting ageing step for the user after ageing */ public AgeingEntityStepDTO ageUser( Set<AgeingEntityStepDTO> steps, UserDTO user, Date today, Integer executorId) { LOG.debug("Ageing user " + user.getId()); Integer currentStatusId = user.getStatus().getId(); UserStatusDTO nextStatus = null; AgeingEntityStepDTO ageingStep = null; if (currentStatusId.equals(UserDTOEx.STATUS_ACTIVE)) { // send welcome message (initial step after active). nextStatus = getNextAgeingStep(steps, UserDTOEx.STATUS_ACTIVE); } else { // user already in the ageing process ageingStep = new AgeingEntityStepDAS().findStep(user.getEntity().getId(), currentStatusId); if (ageingStep != null) { // determine the next ageing step if (isAgeingRequired(user, ageingStep, today)) { nextStatus = getNextAgeingStep(steps, currentStatusId); LOG.debug( "User " + user.getId() + " needs to be aged to '" + getStatusDescription(nextStatus) + "'"); } } else { // User is in a non-existent ageing status... Either the status was removed or // the data is bad. As a workaround, just move to the next status. nextStatus = getNextAgeingStep(steps, currentStatusId); LOG.warn( "User " + user.getId() + " is in an invalid ageing step. Moving to '" + getStatusDescription(nextStatus) + "'"); } } // set status if (nextStatus != null) { setUserStatus(user, nextStatus, today, null); } else { LOG.debug("Next status is null, no further ageing steps are available."); eLogger.warning( user.getEntity().getId(), user.getUserId(), user.getUserId(), EventLogger.MODULE_USER_MAINTENANCE, EventLogger.NO_FURTHER_STEP, Constants.TABLE_BASE_USER); } return ageingStep; } /** * Returns true if the given invoice is overdue. * * @param invoice invoice to check * @param user user owning the invoice * @param gracePeriod company wide grace period * @param today today's date * @return true if invoice is overdue, false if not */ public boolean isInvoiceOverdue( InvoiceDTO invoice, UserDTO user, Integer gracePeriod, Date today) { calendar.clear(); calendar.setTime(invoice.getDueDate()); calendar.add(Calendar.DATE, gracePeriod); if (calendar.getTime().before(today)) { LOG.debug( "Invoice is overdue (due date " + invoice.getDueDate() + " + " + gracePeriod + " days grace, is before today " + today + ")"); return true; } LOG.debug( "Invoice is NOT overdue (due date " + invoice.getDueDate() + " + " + gracePeriod + " days grace is after today " + today + ")"); return false; } /** * Returns true if the user requires ageing. * * @param user user being reviewed * @param currentStep current ageing step of the user * @param today today's date * @return true if user requires ageing, false if not */ public boolean isAgeingRequired(UserDTO user, AgeingEntityStepDTO currentStep, Date today) { Date lastStatusChange = user.getLastStatusChange() != null ? user.getLastStatusChange() : user.getCreateDatetime(); calendar.clear(); calendar.setTime(lastStatusChange); calendar.add(Calendar.DATE, currentStep.getDays()); if (calendar.getTime().equals(today) || calendar.getTime().before(today)) { LOG.debug( "User status has expired (last change " + lastStatusChange + " + " + currentStep.getDays() + " days is before today " + today + ")"); return true; } LOG.debug( "User does not need to be aged (last change " + lastStatusChange + " + " + currentStep.getDays() + " days is after today " + today + ")"); return false; } /** * Removes a user from the ageing process (makes them active), ONLY if they do not still have * overdue invoices. * * @param user user to make active * @param excludedInvoiceId invoice id to ignore when determining if the user CAN be made active * @param executorId executor id */ public void removeUser(UserDTO user, Integer executorId, Integer excludedInvoiceId) { Date now = new Date(); // validate that the user actually needs a status change if (user.getStatus().getId() != UserDTOEx.STATUS_ACTIVE) { LOG.debug("User " + user.getId() + " is already active, no need to remove from ageing."); return; } // validate that the user does not still have overdue invoices try { if (new InvoiceBL().isUserWithOverdueInvoices(user.getUserId(), now, excludedInvoiceId)) { LOG.debug( "User " + user.getId() + " still has overdue invoices, cannot remove from ageing."); return; } } catch (SQLException e) { LOG.error("Exception occurred checking for overdue invoices.", e); return; } // make the status change. LOG.debug("Removing user " + user.getUserId() + " from ageing (making active)."); UserStatusDTO status = new UserStatusDAS().find(UserDTOEx.STATUS_ACTIVE); setUserStatus(user, status, now, null); } /** * Sets the user status to the given "aged" status. If the user status is already set to the aged * status no changes will be made. This method also performs an HTTP callback and sends a * notification message when a status change is made. * * <p>If the user becomes suspended and can no longer log-in to the system, all of their active * orders will be automatically suspended. * * <p>If the user WAS suspended and becomes active (and can now log-in to the system), any * automatically suspended orders will be re-activated. * * @param user user * @param status status to set * @param today today's date * @param executorId executor id */ public void setUserStatus(UserDTO user, UserStatusDTO status, Date today, Integer executorId) { // only set status if the new "aged" status is different from the users current status if (status.getId() == user.getStatus().getId()) { return; } LOG.debug("Setting user " + user.getId() + " status to '" + getStatusDescription(status) + "'"); if (executorId != null) { // this came from the gui eLogger.audit( executorId, user.getId(), Constants.TABLE_BASE_USER, user.getId(), EventLogger.MODULE_USER_MAINTENANCE, EventLogger.STATUS_CHANGE, user.getStatus().getId(), null, null); } else { // this is from a process, no executor involved eLogger.auditBySystem( user.getCompany().getId(), user.getId(), Constants.TABLE_BASE_USER, user.getId(), EventLogger.MODULE_USER_MAINTENANCE, EventLogger.STATUS_CHANGE, user.getStatus().getId(), null, null); } // make the change boolean couldLogin = user.getStatus().getCanLogin() == 1; UserStatusDTO oldStatus = user.getStatus(); user.setUserStatus(status); user.setLastStatusChange(today); // status changed to deleted, remove user if (status.getId() == UserDTOEx.STATUS_DELETED) { LOG.debug("Deleting user " + user.getId()); new UserBL(user.getId()).delete(executorId); return; } // status changed from active to suspended // suspend customer orders if (couldLogin && status.getCanLogin() == 0) { LOG.debug("User " + user.getId() + " cannot log-in to the system. Suspending active orders."); OrderBL orderBL = new OrderBL(); ScrollableResults orders = new OrderDAS().findByUser_Status(user.getId(), Constants.ORDER_STATUS_ACTIVE); while (orders.next()) { OrderDTO order = (OrderDTO) orders.get()[0]; orderBL.set(order); orderBL.setStatus(executorId, Constants.ORDER_STATUS_SUSPENDED_AGEING); } orders.close(); } // status changed from suspended to active // re-active suspended customer orders if (!couldLogin && status.getCanLogin() == 1) { LOG.debug( "User " + user.getId() + " can now log-in to the system. Activating previously suspended orders."); OrderBL orderBL = new OrderBL(); ScrollableResults orders = new OrderDAS().findByUser_Status(user.getId(), Constants.ORDER_STATUS_SUSPENDED_AGEING); while (orders.next()) { OrderDTO order = (OrderDTO) orders.get()[0]; orderBL.set(order); orderBL.setStatus(executorId, Constants.ORDER_STATUS_ACTIVE); } orders.close(); } // perform callbacks and notifications performAgeingCallback(user, oldStatus, status); sendAgeingNotification(user, oldStatus, status); // emit NewUserStatusEvent NewUserStatusEvent event = new NewUserStatusEvent( user.getCompany().getId(), user.getId(), oldStatus.getId(), status.getId()); EventManager.process(event); } protected boolean performAgeingCallback( UserDTO user, UserStatusDTO oldStatus, UserStatusDTO newStatus) { String url = null; try { PreferenceBL pref = new PreferenceBL(); pref.set(user.getEntity().getId(), Constants.PREFERENCE_URL_CALLBACK); url = pref.getString(); } catch (EmptyResultDataAccessException e) { /* ignore, no callback preference configured */ } if (url != null && url.length() > 0) { try { LOG.debug("Performing ageing HTTP callback for URL: " + url); // cook the parameters to be sent NameValuePair[] data = new NameValuePair[6]; data[0] = new NameValuePair("cmd", "ageing_update"); data[1] = new NameValuePair("user_id", String.valueOf(user.getId())); data[2] = new NameValuePair("login_name", user.getUserName()); data[3] = new NameValuePair("from_status", String.valueOf(oldStatus.getId())); data[4] = new NameValuePair("to_status", String.valueOf(newStatus.getId())); data[5] = new NameValuePair("can_login", String.valueOf(newStatus.getCanLogin())); // make the call HttpClient client = new HttpClient(); client.setConnectionTimeout(30000); PostMethod post = new PostMethod(url); post.setRequestBody(data); client.executeMethod(post); } catch (Exception e) { LOG.error("Exception occurred posting ageing HTTP callback for URL: " + url, e); return false; } } return true; } protected boolean sendAgeingNotification( UserDTO user, UserStatusDTO oldStatus, UserStatusDTO newStatus) { try { MessageDTO message = new NotificationBL() .getAgeingMessage( user.getEntity().getId(), user.getLanguage().getId(), newStatus.getId(), user.getId()); INotificationSessionBean notification = (INotificationSessionBean) Context.getBean(Context.Name.NOTIFICATION_SESSION); notification.notify(user, message); } catch (NotificationNotFoundException e) { LOG.warn( "Failed to send ageing notification. Entity " + user.getEntity().getId() + " does not have an ageing message configured for status '" + getStatusDescription(newStatus) + "'."); return false; } return true; } /** * Get the status for the next step in the ageing process, based on the users current status. * * @param steps configured ageing steps * @param currentStatusId the current user status */ public UserStatusDTO getNextAgeingStep(Set<AgeingEntityStepDTO> steps, Integer currentStatusId) { for (AgeingEntityStepDTO step : steps) { Integer stepStatusId = step.getUserStatus().getId(); if (stepStatusId.compareTo(currentStatusId) > 0) { return step.getUserStatus(); } } return null; } /** * Null safe convenience method to return the status description. * * @param status user status * @return description */ private String getStatusDescription(UserStatusDTO status) { if (status != null) { return status.getDescription(); } return null; } }
/** * Sets the user status to the given "aged" status. If the user status is already set to the aged * status no changes will be made. This method also performs an HTTP callback and sends a * notification message when a status change is made. * * <p>If the user becomes suspended and can no longer log-in to the system, all of their active * orders will be automatically suspended. * * <p>If the user WAS suspended and becomes active (and can now log-in to the system), any * automatically suspended orders will be re-activated. * * @param user user * @param status status to set * @param today today's date * @param executorId executor id */ public void setUserStatus(UserDTO user, UserStatusDTO status, Date today, Integer executorId) { // only set status if the new "aged" status is different from the users current status if (status.getId() == user.getStatus().getId()) { return; } LOG.debug("Setting user " + user.getId() + " status to '" + getStatusDescription(status) + "'"); if (executorId != null) { // this came from the gui eLogger.audit( executorId, user.getId(), Constants.TABLE_BASE_USER, user.getId(), EventLogger.MODULE_USER_MAINTENANCE, EventLogger.STATUS_CHANGE, user.getStatus().getId(), null, null); } else { // this is from a process, no executor involved eLogger.auditBySystem( user.getCompany().getId(), user.getId(), Constants.TABLE_BASE_USER, user.getId(), EventLogger.MODULE_USER_MAINTENANCE, EventLogger.STATUS_CHANGE, user.getStatus().getId(), null, null); } // make the change boolean couldLogin = user.getStatus().getCanLogin() == 1; UserStatusDTO oldStatus = user.getStatus(); user.setUserStatus(status); user.setLastStatusChange(today); // status changed to deleted, remove user if (status.getId() == UserDTOEx.STATUS_DELETED) { LOG.debug("Deleting user " + user.getId()); new UserBL(user.getId()).delete(executorId); return; } // status changed from active to suspended // suspend customer orders if (couldLogin && status.getCanLogin() == 0) { LOG.debug("User " + user.getId() + " cannot log-in to the system. Suspending active orders."); OrderBL orderBL = new OrderBL(); ScrollableResults orders = new OrderDAS().findByUser_Status(user.getId(), Constants.ORDER_STATUS_ACTIVE); while (orders.next()) { OrderDTO order = (OrderDTO) orders.get()[0]; orderBL.set(order); orderBL.setStatus(executorId, Constants.ORDER_STATUS_SUSPENDED_AGEING); } orders.close(); } // status changed from suspended to active // re-active suspended customer orders if (!couldLogin && status.getCanLogin() == 1) { LOG.debug( "User " + user.getId() + " can now log-in to the system. Activating previously suspended orders."); OrderBL orderBL = new OrderBL(); ScrollableResults orders = new OrderDAS().findByUser_Status(user.getId(), Constants.ORDER_STATUS_SUSPENDED_AGEING); while (orders.next()) { OrderDTO order = (OrderDTO) orders.get()[0]; orderBL.set(order); orderBL.setStatus(executorId, Constants.ORDER_STATUS_ACTIVE); } orders.close(); } // perform callbacks and notifications performAgeingCallback(user, oldStatus, status); sendAgeingNotification(user, oldStatus, status); // emit NewUserStatusEvent NewUserStatusEvent event = new NewUserStatusEvent( user.getCompany().getId(), user.getId(), oldStatus.getId(), status.getId()); EventManager.process(event); }