/* jBilling - The Enterprise Open Source Billing System Copyright (C) 2003-2011 Enterprise jBilling Software Ltd. and Emiliano Conde This file is part of jbilling. jbilling is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. jbilling is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with jbilling. If not, see <http://www.gnu.org/licenses/>. */ package com.sapienter.jbilling.server.order; import com.sapienter.jbilling.common.CommonConstants; import com.sapienter.jbilling.common.SessionInternalError; import com.sapienter.jbilling.common.Util; import com.sapienter.jbilling.server.item.ItemBL; import com.sapienter.jbilling.server.item.ItemDecimalsException; import com.sapienter.jbilling.server.item.PricingField; import com.sapienter.jbilling.server.item.db.ItemDAS; import com.sapienter.jbilling.server.item.tasks.IItemPurchaseManager; import com.sapienter.jbilling.server.invoice.InvoiceBL; import com.sapienter.jbilling.server.list.ResultList; import com.sapienter.jbilling.server.mediation.Record; import com.sapienter.jbilling.server.notification.INotificationSessionBean; import com.sapienter.jbilling.server.notification.MessageDTO; import com.sapienter.jbilling.server.notification.NotificationBL; import com.sapienter.jbilling.server.notification.NotificationNotFoundException; import com.sapienter.jbilling.server.order.db.OrderBillingTypeDAS; import com.sapienter.jbilling.server.order.db.OrderDAS; import com.sapienter.jbilling.server.order.db.OrderDTO; import com.sapienter.jbilling.server.order.db.OrderLineDAS; import com.sapienter.jbilling.server.order.db.OrderLineDTO; import com.sapienter.jbilling.server.order.db.OrderLineTypeDAS; import com.sapienter.jbilling.server.order.db.OrderLineTypeDTO; import com.sapienter.jbilling.server.order.db.OrderPeriodDAS; import com.sapienter.jbilling.server.order.db.OrderPeriodDTO; import com.sapienter.jbilling.server.order.db.OrderProcessDTO; import com.sapienter.jbilling.server.order.db.OrderStatusDAS; import com.sapienter.jbilling.server.order.event.NewActiveUntilEvent; import com.sapienter.jbilling.server.order.event.NewOrderEvent; import com.sapienter.jbilling.server.order.event.NewQuantityEvent; import com.sapienter.jbilling.server.order.event.NewStatusEvent; import com.sapienter.jbilling.server.order.event.OrderDeletedEvent; import com.sapienter.jbilling.server.order.event.PeriodCancelledEvent; import com.sapienter.jbilling.server.pluggableTask.OrderProcessingTask; import com.sapienter.jbilling.server.pluggableTask.TaskException; import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskException; import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskManager; import com.sapienter.jbilling.server.process.ConfigurationBL; import com.sapienter.jbilling.server.process.db.PeriodUnitDAS; import com.sapienter.jbilling.server.provisioning.db.ProvisioningStatusDAS; import com.sapienter.jbilling.server.provisioning.event.SubscriptionActiveEvent; import com.sapienter.jbilling.server.system.event.EventManager; import com.sapienter.jbilling.server.user.ContactBL; import com.sapienter.jbilling.server.user.UserBL; import com.sapienter.jbilling.server.user.db.CompanyDAS; import com.sapienter.jbilling.server.user.db.CompanyDTO; import com.sapienter.jbilling.server.user.db.UserDAS; import com.sapienter.jbilling.server.user.db.UserDTO; import com.sapienter.jbilling.server.util.Constants; import com.sapienter.jbilling.server.util.Context; import com.sapienter.jbilling.server.util.PreferenceBL; import com.sapienter.jbilling.server.util.audit.EventLogger; import com.sapienter.jbilling.server.util.db.CurrencyDAS; import org.apache.log4j.Logger; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; import javax.sql.rowset.CachedRowSet; import javax.naming.NamingException; import javax.sql.DataSource; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * @author Emil */ public class OrderBL extends ResultList implements OrderSQL { private OrderDTO order = null; private OrderLineDAS orderLineDAS = null; private OrderPeriodDAS orderPeriodDAS = null; private OrderDAS orderDas = null; private OrderBillingTypeDAS orderBillingTypeDas = null; private ProvisioningStatusDAS provisioningStatusDas = null; private static final Logger LOG = Logger.getLogger(OrderBL.class); private EventLogger eLogger = null; public OrderBL(Integer orderId) { init(); set(orderId); } public OrderBL() { init(); } public OrderBL(OrderDTO order) { init(); this.order = order; } private void init() { eLogger = EventLogger.getInstance(); orderLineDAS = new OrderLineDAS(); orderPeriodDAS = new OrderPeriodDAS(); orderDas = new OrderDAS(); orderBillingTypeDas = new OrderBillingTypeDAS(); provisioningStatusDas = new ProvisioningStatusDAS(); } public OrderDTO getEntity() { return order; } public OrderPeriodDTO getPeriod(Integer language, Integer id) { return (orderPeriodDAS.find(id)); } public void set(Integer id) { order = orderDas.find(id); } public void setForUpdate(Integer id) { order = orderDas.findForUpdate(id); } public void set(OrderDTO newOrder) { order = newOrder; } public OrderWS getWS(Integer languageId) { OrderWS retValue = new OrderWS(order.getId(), order.getBillingTypeId(), order.getNotify(), order.getActiveSince(), order.getActiveUntil(), order.getCreateDate(), order.getNextBillableDay(), order.getCreatedBy(), order.getStatusId(), order.getDeleted(), order.getCurrencyId(), order.getLastNotified(), order.getNotificationStep(), order.getDueDateUnitId(), order.getDueDateValue(), order.getAnticipatePeriods(), order.getDfFm(), order.getIsCurrent(), order.getNotes(), order.getNotesInInvoice(), order.getOwnInvoice(), order.getOrderPeriod().getId(), order.getBaseUserByUserId().getId(), order.getVersionNum(), order.getCycleStarts()); retValue.setTotal(order.getTotal()); retValue.setPeriodStr(order.getOrderPeriod().getDescription(languageId)); retValue.setStatusStr(order.getOrderStatus().getDescription(languageId)); retValue.setBillingTypeStr(order.getOrderBillingType().getDescription(languageId)); List<OrderLineWS> lines = new ArrayList<OrderLineWS>(); for (Iterator it = order.getLines().iterator(); it.hasNext();) { OrderLineDTO line = (OrderLineDTO) it.next(); if (line.getDeleted() == 0) { lines.add(getOrderLineWS(line.getId())); } } //this will initialized Generated Invoices in the OrderDTO instance order.addExtraFields(languageId); retValue.setGeneratedInvoices(new InvoiceBL().DTOtoWS(new ArrayList(order.getInvoices()))); retValue.setOrderLines(new OrderLineWS[lines.size()]); lines.toArray(retValue.getOrderLines()); return retValue; } public OrderDTO getDTO() { return order; } public void addItem(OrderDTO order, Integer itemID, BigDecimal quantity, Integer language, Integer userId, Integer entityId, Integer currencyId, List<Record> records) throws ItemDecimalsException { try { PluggableTaskManager<IItemPurchaseManager> taskManager = new PluggableTaskManager<IItemPurchaseManager>(entityId, Constants.PLUGGABLE_TASK_ITEM_MANAGER); IItemPurchaseManager myTask = taskManager.getNextClass(); while (myTask != null) { myTask.addItem(itemID, quantity, language, userId, entityId, currencyId, order, records); myTask = taskManager.getNextClass(); } } catch (PluggableTaskException e) { throw new SessionInternalError("Item Manager task error", OrderBL.class, e); } catch (TaskException e) { if (e.getCause() instanceof ItemDecimalsException) { throw (ItemDecimalsException) e.getCause(); } else { // do not change this error text, it is used to identify the error throw new SessionInternalError("Item Manager task error", OrderBL.class, e); } } } public void addItem(Integer itemID, BigDecimal quantity, Integer language, Integer userId, Integer entityId, Integer currencyId, List<Record> records) { addItem(this.order, itemID, quantity, language, userId, entityId, currencyId, records); } public void addItem(Integer itemID, BigDecimal quantity, Integer language, Integer userId, Integer entityId, Integer currencyId) throws ItemDecimalsException { addItem(itemID, quantity, language, userId, entityId, currencyId, null); } public void addItem(Integer itemID, Integer quantity, Integer language, Integer userId, Integer entityId, Integer currencyId, List<Record> records) throws ItemDecimalsException { addItem(itemID, new BigDecimal(quantity), language, userId, entityId, currencyId, records); } public void addItem(Integer itemID, Integer quantity, Integer language, Integer userId, Integer entityId, Integer currencyId) throws ItemDecimalsException { addItem(itemID, new BigDecimal(quantity), language, userId, entityId, currencyId, null); } public void deleteItem(Integer itemID) { order.removeLine(itemID); } public void delete(Integer executorId) { // the event is needed before the deletion EventManager.process(new OrderDeletedEvent( order.getBaseUserByUserId().getCompany().getId(), order)); for (OrderLineDTO line : order.getLines()) { line.setDeleted(1); } order.setDeleted(1); eLogger.audit(executorId, order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ROW_DELETED, null, null, null); } /** * Method recalculate. * Goes over the processing tasks configured in the database for this * entity. The order entity is then modified. */ public void recalculate(Integer entityId) throws SessionInternalError, ItemDecimalsException { LOG.debug("Processing and order for reviewing." + order.getLines().size()); // make sure the user is there UserDAS user = new UserDAS(); order.setBaseUserByUserId(user.find(order.getBaseUserByUserId().getId())); // some things can't be null, otherwise hibernate complains order.setDefaults(); order.touch(); try { PluggableTaskManager taskManager = new PluggableTaskManager( entityId, Constants.PLUGGABLE_TASK_PROCESSING_ORDERS); OrderProcessingTask task = (OrderProcessingTask) taskManager.getNextClass(); while (task != null) { task.doProcessing(order); task = (OrderProcessingTask) taskManager.getNextClass(); } } catch (PluggableTaskException e) { LOG.fatal("Problems handling order processing task.", e); throw new SessionInternalError("Problems handling order " + "processing task."); } catch (TaskException e) { if (e.getCause() instanceof ItemDecimalsException) { throw (ItemDecimalsException) e.getCause(); } LOG.fatal("Problems excecuting order processing task.", e); throw new SessionInternalError("Problems executing order processing task."); } } public Integer create(Integer entityId, Integer userAgentId, OrderDTO orderDto) throws SessionInternalError { try { // if the order is a one-timer, force pre-paid to avoid any // confusion if (orderDto.getOrderPeriod().getId() == Constants.ORDER_PERIOD_ONCE) { orderDto.setOrderBillingType(orderBillingTypeDas.find(Constants.ORDER_BILLING_PRE_PAID)); // one time orders can not be the main subscription orderDto.setIsCurrent(0); } UserDAS user = new UserDAS(); if (userAgentId != null) { orderDto.setBaseUserByCreatedBy(user.find(userAgentId)); } // create the record orderDto.setBaseUserByUserId(user.find(orderDto.getBaseUserByUserId().getId())); orderDto.setOrderPeriod(orderPeriodDAS.find(orderDto.getOrderPeriod().getId())); // set the provisioning status for (OrderLineDTO line : orderDto.getLines()) { // set default provisioning status id for order lines if (line.getProvisioningStatus() == null) { line.setProvisioningStatus(provisioningStatusDas.find( Constants.PROVISIONING_STATUS_INACTIVE)); } else { line.setProvisioningStatus(provisioningStatusDas.find( line.getProvisioningStatus().getId())); } } orderDto.setDefaults(); order = orderDas.save(orderDto); // link the lines to the new order for (OrderLineDTO line : order.getLines()) { line.setPurchaseOrder(order); } if (order.getIsCurrent() != null && order.getIsCurrent().intValue() == 1) { setMainSubscription(userAgentId); } //check if order is created with activeSince<= now (or null) Date now = new Date(); Date activeSince = order.getActiveSince(); if (activeSince == null || activeSince.before(now)) { // generate SubscriptionActiveEvent for order SubscriptionActiveEvent newEvent = new SubscriptionActiveEvent(entityId, order); EventManager.process(newEvent); LOG.debug("OrderBL.create(): generated SubscriptionActiveEvent for order: " + order.getId()); } // add a log row for convenience eLogger.auditBySystem(entityId, order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ROW_CREATED, null, null, null); EventManager.process(new NewOrderEvent(entityId, order)); } catch (Exception e) { throw new SessionInternalError("Create exception creating order entity bean", OrderBL.class, e); } return order.getId(); } public void updateActiveUntil(Integer executorId, Date to, OrderDTO newOrder) { audit(executorId, order.getActiveUntil()); // this needs an event NewActiveUntilEvent event = new NewActiveUntilEvent(order.getId(), to, order.getActiveUntil()); EventManager.process(event); // update the period of the latest invoice as well. This is needed // because it is the way to extend a subscription when the // order status is finished. Then the next_invoice_date is null. if (order.getOrderStatus().getId() == CommonConstants.ORDER_STATUS_FINISHED) { updateEndOfOrderProcess(to); } // update it order.setActiveUntil(to); // if the new active until is earlier than the next invoice date, we have a // period already invoice being cancelled if (isDateInvoiced(to)) { // pass the new order, rather than the existing one. Otherwise, the exsiting gets // and changes overwritten by the data of the new order. EventManager.process(new PeriodCancelledEvent(newOrder, order.getBaseUserByUserId().getCompany().getId(), executorId)); } } /** * Method checkOrderLineQuantities. * Creates a NewQuantityEvent for each order line that has had * its quantity modified (including those added or deleted). * @return An array with all the events that should be fiered. This * prevents events being fired when the order has not be saved and it is * still 'mutating'. */ public List<NewQuantityEvent> checkOrderLineQuantities(List<OrderLineDTO> oldLines, List<OrderLineDTO> newLines, Integer entityId, Integer orderId, boolean sendEvents) { List<NewQuantityEvent> retValue = new ArrayList<NewQuantityEvent>(); // NewQuantityEvent is generated when an order line and it's quantity // has changed, including from >0 to 0 (deleted) and 0 to >0 (added). // First, copy and sort new and old order lines by order line id. List<OrderLineDTO> oldOrderLines = new ArrayList(oldLines); List<OrderLineDTO> newOrderLines = new ArrayList(newLines); Comparator<OrderLineDTO> sortByOrderLineId = new Comparator<OrderLineDTO>() { public int compare(OrderLineDTO ol1, OrderLineDTO ol2) { return ol1.getId() - ol2.getId(); } }; Collections.sort(oldOrderLines, sortByOrderLineId); Collections.sort(newOrderLines, sortByOrderLineId); // remove any deleted lines for (Iterator<OrderLineDTO> it = oldOrderLines.iterator(); it.hasNext();) { if (it.next().getDeleted() != 0) { it.remove(); } } for (Iterator<OrderLineDTO> it = newOrderLines.iterator(); it.hasNext();) { if (it.next().getDeleted() != 0) { it.remove(); } } Iterator<OrderLineDTO> itOldLines = oldOrderLines.iterator(); Iterator<OrderLineDTO> itNewLines = newOrderLines.iterator(); // Step through the sorted order lines, checking if it exists only in // one, the other or both. If both, then check if quantity has changed. OrderLineDTO currentOldLine = itOldLines.hasNext() ? itOldLines.next() : null; OrderLineDTO currentNewLine = itNewLines.hasNext() ? itNewLines.next() : null; while (currentOldLine != null && currentNewLine != null) { int oldLineId = currentOldLine.getId(); int newLineId = currentNewLine.getId(); if (oldLineId < newLineId) { // order line has been deleted LOG.debug("Deleted order line. Order line Id: " + oldLineId); retValue.add(new NewQuantityEvent(entityId, currentOldLine.getQuantity(), BigDecimal.ZERO, orderId, currentOldLine, null)); currentOldLine = itOldLines.hasNext() ? itOldLines.next() : null; } else if (oldLineId > newLineId) { // order line has been added LOG.debug("Added order line. Order line Id: " + newLineId); retValue.add(new NewQuantityEvent(entityId, BigDecimal.ZERO, currentNewLine.getQuantity(), orderId, currentNewLine, null)); currentNewLine = itNewLines.hasNext() ? itNewLines.next() : null; } else { // order line exists in both, so check quantity BigDecimal oldLineQuantity = currentOldLine.getQuantity(); BigDecimal newLineQuantity = currentNewLine.getQuantity(); if (oldLineQuantity.compareTo(newLineQuantity) != 0) { LOG.debug("Order line quantity changed. Order line Id: " + oldLineId); retValue.add(new NewQuantityEvent(entityId, oldLineQuantity, newLineQuantity, orderId, currentOldLine, currentNewLine)); } currentOldLine = itOldLines.hasNext() ? itOldLines.next() : null; currentNewLine = itNewLines.hasNext() ? itNewLines.next() : null; } } // check for any remaining item lines that must have been deleted or added while (currentOldLine != null) { LOG.debug("Deleted order line. Order line id: " + currentOldLine.getId()); retValue.add(new NewQuantityEvent(entityId, currentOldLine.getQuantity(), BigDecimal.ZERO, orderId, currentOldLine, null)); currentOldLine = itOldLines.hasNext() ? itOldLines.next() : null; } while (currentNewLine != null) { LOG.debug("Added order line. Order line id: " + currentNewLine.getId()); retValue.add(new NewQuantityEvent(entityId, BigDecimal.ZERO, currentNewLine.getQuantity(), orderId, currentNewLine, null)); currentNewLine = itNewLines.hasNext() ? itNewLines.next() : null; } if (sendEvents) { for (NewQuantityEvent event: retValue) { EventManager.process(event); } } return retValue; } public void update(Integer executorId, OrderDTO dto) { // update first the order own fields if (!Util.equal(order.getActiveUntil(), dto.getActiveUntil())) { updateActiveUntil(executorId, dto.getActiveUntil(), dto); } if (!Util.equal(order.getActiveSince(), dto.getActiveSince())) { audit(executorId, order.getActiveSince()); order.setActiveSince(dto.getActiveSince()); } setStatus(executorId, dto.getStatusId()); if (order.getOrderPeriod().getId() != dto.getOrderPeriod().getId()) { audit(executorId, order.getOrderPeriod().getId()); order.setOrderPeriod(orderPeriodDAS.find(dto.getOrderPeriod().getId())); } // set the provisioning status for (OrderLineDTO line : dto.getLines()) { // set default provisioning status id for order lines if (line.getProvisioningStatus() == null) { line.setProvisioningStatus(provisioningStatusDas.find( Constants.PROVISIONING_STATUS_INACTIVE)); } else { line.setProvisioningStatus(provisioningStatusDas.find( line.getProvisioningStatus().getId())); } } // this should not be necessary any more, since the order is a pojo... order.setOrderBillingType(dto.getOrderBillingType()); order.setNotify(dto.getNotify()); order.setDueDateUnitId(dto.getDueDateUnitId()); order.setDueDateValue(dto.getDueDateValue()); order.setDfFm(dto.getDfFm()); order.setAnticipatePeriods(dto.getAnticipatePeriods()); order.setOwnInvoice(dto.getOwnInvoice()); order.setNotes(dto.getNotes()); order.setNotesInInvoice(dto.getNotesInInvoice()); order.setCycleStarts(dto.getCycleStarts()); if (dto.getIsCurrent() != null && dto.getIsCurrent().intValue() == 1) { setMainSubscription(executorId); } if (dto.getIsCurrent() != null && dto.getIsCurrent().intValue() == 0) { unsetMainSubscription(executorId); } // this one needs more to get updated updateNextBillableDay(executorId, dto.getNextBillableDay()); /* * now proces the order lines */ // get new quantity events as necessary List<NewQuantityEvent> events = checkOrderLineQuantities(order.getLines(), dto.getLines(), order.getBaseUserByUserId().getCompany().getId(), order.getId(), false); // do not send them now, it will be done later when the order is saved // Determine if the item of the order changes and, if it is, // LOG a subscription change event. LOG.info("Order lines: " + order.getLines().size() + " --> new Order: " + dto.getLines().size()); OrderLineDTO oldLine = null; int nonDeletedLines = 0; if (dto.getLines().size() == 1 && order.getLines().size() >= 1) { // This event needs to LOG the old item id and description, so // it can only happen when updating orders with only one line. for (Iterator i = order.getLines().iterator(); i.hasNext();) { // Check which order is not deleted. OrderLineDTO temp = (OrderLineDTO) i.next(); if (temp.getDeleted() == 0) { oldLine = temp; nonDeletedLines++; } } } // now update this order's lines order.getLines().clear(); order.getLines().addAll(dto.getLines()); for (OrderLineDTO line : order.getLines()) { // link them all, just in case there's a new one line.setPurchaseOrder(order); // new lines need createDatetime set line.setDefaults(); } order = orderDas.save(order); if (oldLine != null && nonDeletedLines == 1) { OrderLineDTO newLine = null; for (Iterator i = order.getLines().iterator(); i.hasNext();) { OrderLineDTO temp = (OrderLineDTO) i.next(); if (temp.getDeleted() == 0) { newLine = temp; } } if (newLine != null && !oldLine.getItemId().equals(newLine.getItemId())) { if (executorId != null) { eLogger.audit(executorId, order.getBaseUserByUserId().getId(), Constants.TABLE_ORDER_LINE, newLine.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ORDER_LINE_UPDATED, oldLine.getId(), oldLine.getDescription(), null); } else { // it is the mediation process eLogger.auditBySystem(order.getBaseUserByUserId().getCompany().getId(), order.getBaseUserByUserId().getId(), Constants.TABLE_ORDER_LINE, newLine.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ORDER_LINE_UPDATED, oldLine.getId(), oldLine.getDescription(), null); } } } if (executorId != null) { eLogger.audit(executorId, order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ROW_UPDATED, null, null, null); } else { eLogger.auditBySystem(order.getBaseUserByUserId().getCompany().getId(), order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ROW_UPDATED, null, null, null); } // last, once the order is saved and all done, send out the // order modified events for (NewQuantityEvent event: events) { EventManager.process(event); } } private void updateEndOfOrderProcess(Date newDate) { OrderProcessDTO process = null; if (newDate == null) { LOG.debug("Attempting to update an order process end date to null. Skipping"); return; } if (order.getActiveUntil() != null) { process = orderDas.findProcessByEndDate(order.getId(), order.getActiveUntil()); } if (process != null) { LOG.debug("Updating process id " + process.getId()); process.setPeriodEnd(newDate); } else { LOG.debug("Did not find any process for order " + order.getId() + " and date " + order.getActiveUntil()); } } private void updateNextBillableDay(Integer executorId, Date newDate) { if (newDate == null) { return; } // only if the new date is in the future if (order.getNextBillableDay() == null || newDate.after(order.getNextBillableDay())) { // this audit can be added to the order details screen // otherwise the user can't account for the lost time eLogger.audit(executorId, order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ORDER_NEXT_BILL_DATE_UPDATED, null, null, order.getNextBillableDay()); // update the period of the latest invoice as well updateEndOfOrderProcess(newDate); // do the actual update order.setNextBillableDay(newDate); } else { LOG.info("order " + order.getId() + " next billable day not updated from " + order.getNextBillableDay() + " to " + newDate); } } /** * Method lookUpEditable. * Gets the row from order_line_type for the type specifed * @param type * The order line type to look. * @return Boolean * If it is editable or not * @throws SessionInternalError * If there was a problem accessing the entity bean */ static public Boolean lookUpEditable(Integer type) throws SessionInternalError { Boolean editable = null; try { OrderLineTypeDAS das = new OrderLineTypeDAS(); OrderLineTypeDTO typeBean = das.find(type); editable = new Boolean(typeBean.getEditable().intValue() == 1); } catch (Exception e) { LOG.fatal( "Exception looking up the editable flag of an order " + "line type. Type = " + type, e); throw new SessionInternalError("Looking up editable flag"); } return editable; } public CachedRowSet getList(Integer entityID, Integer userRole, Integer userId) throws SQLException, Exception { if (userRole.equals(Constants.TYPE_INTERNAL) || userRole.equals(Constants.TYPE_ROOT) || userRole.equals(Constants.TYPE_CLERK)) { prepareStatement(OrderSQL.listInternal); cachedResults.setInt(1, entityID.intValue()); } else if (userRole.equals(Constants.TYPE_PARTNER)) { prepareStatement(OrderSQL.listPartner); cachedResults.setInt(1, entityID.intValue()); cachedResults.setInt(2, userId.intValue()); } else if (userRole.equals(Constants.TYPE_CUSTOMER)) { prepareStatement(OrderSQL.listCustomer); cachedResults.setInt(1, userId.intValue()); } else { throw new Exception("The orders list for the type " + userRole + " is not supported"); } execute(); conn.close(); return cachedResults; } public Integer getLatest(Integer userId) throws SessionInternalError { Integer retValue = null; try { prepareStatement(OrderSQL.getLatest); cachedResults.setInt(1, userId.intValue()); execute(); if (cachedResults.next()) { int value = cachedResults.getInt(1); if (!cachedResults.wasNull()) { retValue = new Integer(value); } } cachedResults.close(); conn.close(); } catch (Exception e) { throw new SessionInternalError(e); } return retValue; } public Integer getLatestByItemType(Integer userId, Integer itemTypeId) throws SessionInternalError { Integer retValue = null; try { prepareStatement(OrderSQL.getLatestByItemType); cachedResults.setInt(1, userId.intValue()); cachedResults.setInt(2, itemTypeId.intValue()); execute(); if (cachedResults.next()) { int value = cachedResults.getInt(1); if (!cachedResults.wasNull()) { retValue = new Integer(value); } } cachedResults.close(); conn.close(); } catch (Exception e) { throw new SessionInternalError(e); } return retValue; } public CachedRowSet getOrdersByProcessId(Integer processId) throws SQLException, Exception { prepareStatement(OrderSQL.listByProcess); cachedResults.setInt(1, processId.intValue()); execute(); conn.close(); return cachedResults; } public List<Integer> getOrdersByProcess(Integer processId) throws SQLException, Exception { conn = ((DataSource) Context.getBean(Context.Name.DATA_SOURCE)).getConnection(); PreparedStatement stmt = conn.prepareStatement(OrderSQL.listByProcess); stmt.setInt(1, processId.intValue()); ResultSet res = stmt.executeQuery(); List<Integer> orders=new ArrayList(); while (res.next()) { orders.add(res.getInt(1)); } res.close(); conn.close(); return orders; } public void setStatus(Integer executorId, Integer statusId) { if (statusId == null || order.getStatusId().equals(statusId)) { return; } if (executorId != null) { eLogger.audit(executorId, order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ORDER_STATUS_CHANGE, order.getStatusId(), null, null); } else { eLogger.auditBySystem(order.getBaseUserByUserId().getCompany().getId(), order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ORDER_STATUS_CHANGE, order.getStatusId(), null, null); } NewStatusEvent event = new NewStatusEvent( order.getId(), order.getStatusId(), statusId); EventManager.process(event); order.setOrderStatus(new OrderStatusDAS().find(statusId)); } /** * To be called from the http api, this simply looks for lines * in the order that lack some fields, it finds that info based * in the item. * * @param dto order with lines to pricess * @param entityId entity id */ public void fillInLines(OrderDTO dto, Integer entityId) throws NamingException, SessionInternalError { ItemBL itemBl = new ItemBL(); // iterate over order lines for (OrderLineDTO line : dto.getLines()) { itemBl.set(line.getItemId()); Integer languageId = itemBl.getEntity().getEntity().getLanguageId(); // populate the basic item price and item description ItemDAS itemDas = new ItemDAS(); line.setItem(itemDas.find(line.getItemId())); if (line.getPrice() == null) { line.setPrice(itemBl.getPrice(dto.getUserId(), dto.getCurrencyId(), line.getQuantity(), entityId)); } if (line.getDescription() == null) { line.setDescription(itemBl.getEntity().getDescription(languageId)); } } } private void audit(Integer executorId, Date date) { eLogger.audit(executorId, order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ROW_UPDATED, null, null, date); } private void audit(Integer executorId, Integer in) { eLogger.audit(executorId, order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ROW_UPDATED, in, null, null); } public static boolean validate(OrderWS dto) { boolean retValue = true; if (dto.getUserId() == null || dto.getPeriod() == null || dto.getBillingTypeId() == null || dto.getOrderLines() == null) { retValue = false; } else { for (int f = 0; f < dto.getOrderLines().length; f++) { if (!validate(dto.getOrderLines()[f])) { retValue = false; break; } } } return retValue; } public static boolean validate(OrderLineWS dto) { boolean retValue = true; if (dto.getTypeId() == null || dto.getDescription() == null || dto.getQuantity() == null) { retValue = false; } return retValue; } public void reviewNotifications(Date today) throws NamingException, SQLException, Exception { INotificationSessionBean notificationSess = (INotificationSessionBean) Context.getBean(Context.Name.NOTIFICATION_SESSION); for (CompanyDTO ent : new CompanyDAS().findEntities()) { // find the orders for this entity // SQL args Object[] sqlArgs = new Object[4]; sqlArgs[0] = new java.sql.Date(today.getTime()); // calculate the until date // get the this entity preferences for each of the steps PreferenceBL pref = new PreferenceBL(); int totalSteps = 3; int stepDays[] = new int[totalSteps]; boolean config = false; int minStep = -1; for (int f = 0; f < totalSteps; f++) { try { pref.set(ent.getId(), new Integer( Constants.PREFERENCE_DAYS_ORDER_NOTIFICATION_S1.intValue() + f)); if (pref.isNull()) { stepDays[f] = -1; } else { stepDays[f] = pref.getInt(); config = true; if (minStep == -1) { minStep = f; } } } catch (EmptyResultDataAccessException e) { stepDays[f] = -1; } } if (!config) { LOG.warn("Preference missing to send a notification for " + "entity " + ent.getId()); continue; } Calendar cal = Calendar.getInstance(); cal.clear(); cal.setTime(today); cal.add(Calendar.DAY_OF_MONTH, stepDays[minStep]); sqlArgs[1] = new java.sql.Date(cal.getTime().getTime()); // the entity sqlArgs[2] = ent.getId(); // the total number of steps sqlArgs[3] = totalSteps; JdbcTemplate jdbcTemplate = (JdbcTemplate) Context.getBean( Context.Name.JDBC_TEMPLATE); SqlRowSet results = jdbcTemplate.queryForRowSet( OrderSQL.getAboutToExpire, sqlArgs); while (results.next()) { int orderId = results.getInt(1); Date activeUntil = results.getDate(2); int currentStep = results.getInt(3); int days = -1; // find out how many days apply for this order step for (int f = currentStep; f < totalSteps; f++) { if (stepDays[f] >= 0) { days = stepDays[f]; currentStep = f + 1; break; } } if (days == -1) { throw new SessionInternalError("There are no more steps " + "configured, but the order was selected. Order " + " id = " + orderId); } // check that this order requires a notification cal.setTime(today); cal.add(Calendar.DAY_OF_MONTH, days); if (activeUntil.compareTo(today) >= 0 && activeUntil.compareTo(cal.getTime()) <= 0) { /*/ ok LOG.debug("Selecting order " + orderId + " today = " + today + " active unitl = " + activeUntil + " days = " + days); */ } else { /* LOG.debug("Skipping order " + orderId + " today = " + today + " active unitl = " + activeUntil + " days = " + days); */ continue; } set(new Integer(orderId)); UserBL user = new UserBL(order.getBaseUserByUserId().getId()); try { NotificationBL notification = new NotificationBL(); ContactBL contact = new ContactBL(); contact.set(user.getEntity().getUserId()); MessageDTO message = notification.getOrderNotification( ent.getId(), new Integer(currentStep), user.getEntity().getLanguageIdField(), order.getActiveSince(), order.getActiveUntil(), user.getEntity().getUserId(), order.getTotal(), order.getCurrencyId()); // update the order record only if the message is sent if (notificationSess.notify(user.getEntity(), message). booleanValue()) { // if in the last step, turn the notification off, so // it is skiped in the next process if (currentStep >= totalSteps) { order.setNotify(new Integer(0)); } order.setNotificationStep(new Integer(currentStep)); order.setLastNotified(Calendar.getInstance().getTime()); } } catch (NotificationNotFoundException e) { LOG.warn("Without a message to send, this entity can't" + " notify about orders. Skipping"); break; } } } } public TimePeriod getDueDate() { TimePeriod retValue = new TimePeriod(); if (order.getDueDateValue() == null) { // let's go see the customer if (order.getBaseUserByUserId().getCustomer().getDueDateValue() == null) { // still unset, let's go to the entity ConfigurationBL config = new ConfigurationBL( order.getBaseUserByUserId().getCompany().getId()); retValue.setUnitId(config.getEntity().getDueDateUnitId()); retValue.setValue(config.getEntity().getDueDateValue()); } else { retValue.setUnitId(order.getBaseUserByUserId().getCustomer().getDueDateUnitId()); retValue.setValue(order.getBaseUserByUserId().getCustomer().getDueDateValue()); } } else { retValue.setUnitId(order.getDueDateUnitId()); retValue.setValue(order.getDueDateValue()); } // df fm only applies if the entity uses it PreferenceBL preference = new PreferenceBL(); try { preference.set(order.getUser().getEntity().getId(), Constants.PREFERENCE_USE_DF_FM); } catch (EmptyResultDataAccessException e) { // no problem go ahead use the defualts } if (preference.getInt() == 1) { // now all over again for the Df Fm if (order.getDfFm() == null) { // let's go see the customer if (order.getUser().getCustomer().getDfFm() == null) { // still unset, let's go to the entity ConfigurationBL config = new ConfigurationBL( order.getUser().getEntity().getId()); retValue.setDf_fm(config.getEntity().getDfFm()); } else { retValue.setDf_fm(order.getUser().getCustomer().getDfFm()); } } else { retValue.setDf_fm(order.getDfFm()); } } else { retValue.setDf_fm((Boolean) null); } retValue.setOwn_invoice(order.getOwnInvoice()); return retValue; } public Date getInvoicingDate() { Date retValue; if (order.getNextBillableDay() != null) { retValue = order.getNextBillableDay(); } else { if (order.getActiveSince() != null) { retValue = order.getActiveSince(); } else { retValue = order.getCreateDate(); } } return retValue; } public boolean isDateInvoiced(Date date) { return date != null && order.getNextBillableDay() != null && date.before(order.getNextBillableDay()); } public Integer[] getListIds(Integer userId, Integer number, Integer entityId) { List<Integer> result = orderDas.findIdsByUserLatestFirst(userId, number); return result.toArray(new Integer[result.size()]); } public Integer[] getListIdsByItemType(Integer userId, Integer itemTypeId, Integer number) { List<Integer> result = orderDas.findIdsByUserAndItemTypeLatestFirst(userId, itemTypeId, number); return result.toArray(new Integer[result.size()]); } public Integer[] getByUserAndPeriod(Integer userId, Integer statusId) throws SessionInternalError { // find the order records first try { List result = new ArrayList(); prepareStatement(OrderSQL.getByUserAndPeriod); cachedResults.setInt(1, userId.intValue()); cachedResults.setInt(2, statusId.intValue()); execute(); while (cachedResults.next()) { result.add(new Integer(cachedResults.getInt(1))); } cachedResults.close(); conn.close(); // now convert the vector to an int array Integer retValue[] = new Integer[result.size()]; result.toArray(retValue); return retValue; } catch (Exception e) { throw new SessionInternalError(e); } } public Collection<OrderDTO> getActiveRecurringByUser(Integer userId) { return orderDas.findByUserSubscriptions(userId); } public OrderPeriodDTO[] getPeriods(Integer entityId, Integer languageId) { OrderPeriodDTO retValue[] = null; CompanyDAS companyDas = new CompanyDAS(); CompanyDTO company = companyDas.find(entityId); Set<OrderPeriodDTO> periods = company.getOrderPeriods(); if (periods == null || periods.size() == 0) { return new OrderPeriodDTO[0]; } retValue = new OrderPeriodDTO[periods.size()]; int i = 0; for (OrderPeriodDTO period : periods) { period.setDescription(period.getDescription(languageId)); retValue[i++] = period; } return retValue; } public void updatePeriods(Integer languageId, OrderPeriodDTO periods[]) { for (OrderPeriodDTO period : periods) { orderPeriodDAS.save(period).setDescription( period.getDescription(), languageId); period.getCompany().getOrderPeriods().add(period); } } public void addPeriod(Integer entityId, Integer languageId) { OrderPeriodDTO newPeriod = new OrderPeriodDTO(); CompanyDAS companyDas = new CompanyDAS(); newPeriod.setCompany(companyDas.find(entityId)); PeriodUnitDAS periodDas = new PeriodUnitDAS(); newPeriod.setPeriodUnit(periodDas.find(1)); newPeriod.setValue(1); newPeriod = orderPeriodDAS.save(newPeriod); newPeriod.setDescription(" ", languageId); } public boolean deletePeriod(Integer periodId) { OrderPeriodDTO period = orderPeriodDAS.find( periodId); if (period.getPurchaseOrders().size() > 0) { return false; } else { orderPeriodDAS.delete(period); return true; } } public OrderLineWS getOrderLineWS(Integer id) { OrderLineDTO line = orderLineDAS.findNow(id); if (line == null) { LOG.warn("Order line " + id + " not found"); return null; } OrderLineWS retValue = new OrderLineWS(line.getId(), line.getItem().getId(), line.getDescription(), line.getAmount(), line.getQuantity(), line.getPrice() == null ? null : line.getPrice(), line.getCreateDatetime(), line.getDeleted(), line.getOrderLineType().getId(), line.getEditable(), line.getPurchaseOrder().getId(), line.getUseItem(), line.getVersionNum(), line.getProvisioningStatusId(), line.getProvisioningRequestId()); return retValue; } public OrderLineDTO getOrderLine(Integer id) { OrderLineDTO line = orderLineDAS.findNow(id); if (line == null) { throw new SessionInternalError("Order line " + id + " not found"); } return line; } public OrderLineDTO getOrderLine(OrderLineWS ws) { OrderLineDTO dto = new OrderLineDTO(); dto.setId(ws.getId()); dto.setAmount(ws.getAmountAsDecimal()); dto.setCreateDatetime(ws.getCreateDatetime()); dto.setDeleted(ws.getDeleted()); dto.setUseItem(ws.getUseItem()); dto.setDescription(ws.getDescription()); dto.setEditable(ws.getEditable()); dto.setItem(new ItemDAS().find(ws.getItemId())); dto.setItemId(ws.getItemId()); dto.setOrderLineType(new OrderLineTypeDAS().find(ws.getTypeId())); dto.setPrice(ws.getPriceAsDecimal()); dto.setPurchaseOrder(orderDas.find(ws.getOrderId())); dto.setQuantity(ws.getQuantityAsDecimal()); dto.setVersionNum(ws.getVersionNum()); dto.setProvisioningStatus(provisioningStatusDas.find(ws.getProvisioningStatusId())); dto.setProvisioningRequestId(ws.getProvisioningRequestId()); return dto; } public List<OrderLineDTO> getRecurringOrderLines(Integer userId) { return orderLineDAS.findRecurringByUser(userId); } public OrderLineDTO getRecurringOrderLine(Integer userId, Integer itemId) { return orderLineDAS.findRecurringByUserItem(userId, itemId); } public List<OrderLineDTO> getOnetimeOrderLines(Integer userId, Integer itemId) { return orderLineDAS.findOnetimeByUserItem(userId, itemId); } public List<OrderLineDTO> getOnetimeOrderLines(Integer userId, Integer itemId, Integer months) { return orderLineDAS.findOnetimeByUserItem(userId, itemId, months); } public List<OrderLineDTO> getOnetimeOrderLinesByParent(Integer parentUserId, Integer itemId, Integer months) { return orderLineDAS.findOnetimeByParentUserItem(parentUserId, itemId, months); } public void updateOrderLine(OrderLineWS dto) { OrderLineDTO line = getOrderLine(dto.getId()); if (dto.getQuantity() != null && (BigDecimal.ZERO.compareTo(dto.getQuantityAsDecimal()) == 0)) { // deletes the order line if the quantity is 0 orderLineDAS.delete(line); } else { line.setAmount(dto.getAmountAsDecimal()); line.setDeleted(dto.getDeleted()); line.setDescription(dto.getDescription()); ItemDAS item = new ItemDAS(); line.setItem(item.find(dto.getItemId())); line.setPrice(dto.getPriceAsDecimal()); line.setQuantity(dto.getQuantityAsDecimal()); line.setProvisioningStatus(provisioningStatusDas.find( dto.getProvisioningStatusId())); line.setProvisioningRequestId(dto.getProvisioningRequestId()); } } /** * Returns the current one-time order for this user for the given date. */ public OrderDTO getCurrentOrder(Integer userId, Date date) { CurrentOrder co = new CurrentOrder(userId, date); set(orderDas.findNow(co.getCurrent())); return order; } /** * For the mediation process, get or create a current order. The returned * order is not attached to the session. * * @param userId * @param eventDate * @param currencyId * @return */ public static OrderDTO getOrCreateCurrentOrder(Integer userId, Date eventDate, Integer currencyId, boolean persist) { CurrentOrder co = new CurrentOrder(userId, eventDate); Integer currentOrderId = co.getCurrent(); if (currentOrderId == null) { // this is almost an error, put them in a new order? currentOrderId = co.create(eventDate, currencyId, new UserBL().getEntityId(userId)); LOG.warn("Created current one-time order without a suitable main " + "subscription order:" + currentOrderId); } OrderDAS orderDas = new OrderDAS(); OrderDTO order = orderDas.find(currentOrderId); if (!persist) { order.touch(); orderDas.detach(order); } return order; } /** * The order has to be set and made persitent with an ID * @param executorId */ public void setMainSubscription(Integer executorId) { // set the field order.setIsCurrent(1); // there can only be one main subscription order Integer oldCurrent = order.getUser().getCustomer().getCurrentOrderId(); if (oldCurrent == null || !oldCurrent.equals(order.getId())) { eLogger.audit(executorId, order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ORDER_MAIN_SUBSCRIPTION_UPDATED, oldCurrent, null, null); // this is the new subscription order order.getUser().getCustomer().setCurrentOrderId(order.getId()); // if there was an old one if (oldCurrent != null) { // update so it does not have the 'is subscription' flag on OrderDTO old = orderDas.findNow(oldCurrent); if (old == null) { LOG.error("Can not find order to set 'is_current' off " + oldCurrent); } else { old.setIsCurrent(new Integer(0)); } } } } private void unsetMainSubscription(Integer executorId) { if (order.getIsCurrent() == null) { order.setIsCurrent(0); } // it is removing the main subscription if (order.getIsCurrent().intValue() == 1) { eLogger.audit(executorId, order.getBaseUserByUserId().getId(), Constants.TABLE_PUCHASE_ORDER, order.getId(), EventLogger.MODULE_ORDER_MAINTENANCE, EventLogger.ORDER_MAIN_SUBSCRIPTION_UPDATED, null, null, null); // this is the new subscription order order.getUser().getCustomer().setCurrentOrderId(null); order.setIsCurrent(0); } } public Integer getMainOrderId(Integer userId) { UserDTO user = new UserDAS().find(userId); if (user.getCustomer() != null) return user.getCustomer().getCurrentOrderId(); return null; } public void addRelationships(Integer userId, Integer periodId, Integer currencyId) { if (periodId != null) { OrderPeriodDTO period = orderPeriodDAS.find(periodId); order.setOrderPeriod(period); } if (userId != null) { UserDAS das = new UserDAS(); order.setBaseUserByUserId(das.find(userId)); } if (currencyId != null) { CurrencyDAS das = new CurrencyDAS(); order.setCurrency(das.find(currencyId)); } } public OrderDTO getDTO(OrderWS other) { OrderDTO retValue = new OrderDTO(); retValue.setId(other.getId()); retValue.setBaseUserByUserId(new UserDAS().find(other.getUserId())); retValue.setBaseUserByCreatedBy(new UserDAS().find(other.getCreatedBy())); retValue.setCurrency(new CurrencyDAS().find(other.getCurrencyId())); retValue.setOrderStatus(new OrderStatusDAS().find(other.getStatusId())); retValue.setOrderPeriod(new OrderPeriodDAS().find(other.getPeriod())); retValue.setOrderBillingType(new OrderBillingTypeDAS().find(other.getBillingTypeId())); retValue.setActiveSince(other.getActiveSince()); retValue.setActiveUntil(other.getActiveUntil()); retValue.setCreateDate(other.getCreateDate()); retValue.setNextBillableDay(other.getNextBillableDay()); retValue.setDeleted(other.getDeleted()); retValue.setNotify(other.getNotify()); retValue.setLastNotified(other.getLastNotified()); retValue.setNotificationStep(other.getNotificationStep()); retValue.setDueDateUnitId(other.getDueDateUnitId()); retValue.setDueDateValue(other.getDueDateValue()); retValue.setDfFm(other.getDfFm()); retValue.setAnticipatePeriods(other.getAnticipatePeriods()); retValue.setOwnInvoice(other.getOwnInvoice()); retValue.setNotes(other.getNotes()); retValue.setNotesInInvoice(other.getNotesInInvoice()); for (OrderLineWS line : other.getOrderLines()) { if (line != null) { retValue.getLines().add(getOrderLine(line)); } } retValue.setIsCurrent(other.getIsCurrent()); retValue.setCycleStarts(other.getCycleStarts()); retValue.setVersionNum(other.getVersionNum()); if (other.getPricingFields() != null) { List<PricingField> pf = new ArrayList<PricingField>(); pf.addAll(Arrays.asList(PricingField.getPricingFieldsValue(other.getPricingFields()))); retValue.setPricingFields(pf); } return retValue; } public void setProvisioningStatus(Integer orderLineId, Integer provisioningStatus) { OrderLineDTO line = orderLineDAS.findForUpdate(orderLineId); Integer oldStatus = line.getProvisioningStatusId(); line.setProvisioningStatus(provisioningStatusDas.find(provisioningStatus)); LOG.debug("order line " + line + ": updated provisioning status :" + line.getProvisioningStatusId()); // add a log for provisioning module eLogger.auditBySystem(order.getBaseUserByUserId().getCompany().getId(), order.getBaseUserByUserId().getId(), Constants.TABLE_ORDER_LINE, orderLineId, EventLogger.MODULE_PROVISIONING, EventLogger.PROVISIONING_STATUS_CHANGE, oldStatus, null, null); } public static OrderDTO createAsWithLine(OrderDTO order, Integer itemId, Double quantity) { return createAsWithLine(order, itemId, new BigDecimal(quantity).setScale(Constants.BIGDECIMAL_SCALE, Constants.BIGDECIMAL_ROUND)); } public static OrderDTO createAsWithLine(OrderDTO order, Integer itemId, BigDecimal quantity) { // copy the current order OrderDTO newOrder = new OrderDTO(order); newOrder.setId(0); newOrder.setVersionNum(null); // the period needs to be in the session newOrder.setOrderPeriodId(order.getOrderPeriod().getId()); // the status should be active newOrder.setOrderStatus(new OrderStatusDAS().find(Constants.ORDER_STATUS_ACTIVE)); // but without the lines newOrder.getLines().clear(); // but do get the new line in OrderLineBL.addItem(newOrder, itemId, quantity); return new OrderDAS().save(newOrder); } }