Friday, April 13, 2012

Rethinking Line Item Deletion

In the midst of troubleshooting some issues with subrental paperwork generation, we decided to tackle a related issue that's been neglected over the last year or so, and that would be how line item deletes are handled during paperwork syncs (pull sheet and PO generation) and how line item deletes are handled in general.


Visibility

The Flex workflow, where quotes are used to generate other types of documents, can be a source of confusion.  A quote line item has associations with other line items in other documents, but when you go to delete a line item, you don't necessarily get a heads up on what impact it will have on other documents.  This was a frequent source of unexplained weirdness. 

Another view of the same basic problem relates to syncs.  For example, say you delete line items from a quote and want those deletes to propagate down to the pull sheet.  Most Flex users know deletes don't get synced and this was a design choice to prevent accidental deletion, although we've always known we needed a better solution for this problem.

No Deletion Without Confirmation

The new approach to deletes can be summed up as "no deletion without confirmation," meaning that deletes during document syncs, instead of being ignored (or done automatically), should be prompted and presented to the user for confirmation.  We also think that manual deletes should show a confirmation dialog that shows the links to other documents and present a user with the opportunity to delete the whole object graph in one fell swoop or use the extended information to make a more informed decision about deletion.

The Delete Confirmation Dialog

To implement this new way of handling line item deletes, we've replaced the existing validation process (which just did a quick relationship check) and replaced it with a new dialog box.



This dialog will show users the line items they selected with any related line items shown in a tree structure.  From here, the user can make a judgement call about whether or not to stick with the original plan, delete the pull sheet line items as well, or forget the whole thing.

This dialog will also pop up on occasion when updating subrental paperwork or invoking a workflow action - if the automation process detects line items that might need to be deleted to make the paperwork consistent.


Generic Deletion

 Making this work on the back end presented a number of new challenges, because our existing facades for supporting client initiated delete operations have line item delete logic specific to the type of line item.  For example, the logic used to process a quote line item delete is slightly different than pull sheet deletes.  The creates a problem if you're trying to delete both quote and pull sheet line items in a single batch.  The system must have a system for switching the delete logic used for each line item.

To make this work, we had to correct a few architectural problems.  First, much of the type-specific logic was in the Facade layer of the application, which is the server endpoint the Flash client uses for communication.  From a purist's perspective, this is part of the view layer of the application and all business logic belongs in the service layer.  We had to take the existing facade delete methods and all logic related to processing the actual delete operation down into the service layer while retaining in the facade all logic related to validating the request and serializing the results for the client.

We also needed a generic endpoint for line item deletes that would determine which delete logic needed to be used.  To handle this, we added a new method or our base ProjectElementService interface as shown below:

public interface ProjectElementService extends KennewickService {
    ...
    public Collection<ProjectElementLineItem> deleteLineItem(String ids);
}
This is a pretty simple method whose operation should look relatively straightforward.  The tricky part is in the base service implementation as shown in the next code sample:

    @Override
    public Collection<ProjectElementLineItem> deleteLineItem(String id) {
       
       
        /*
         * Delegates to the appropriate service with class specific logic.
         */
       
        ProjectElementLineItem lineItem = getLineItemService().findById(id);
        String lockId = lineItem.getProjectElement().getObjectIdentifier();
        try {
            getConcurrencyService().acquireLock(lockId);
            ProjectElementDefinition def = lineItem.getProjectElement().getElementDefinition());
            ProjectElementService service = context.getBean(def.getExtension().getInstanceServiceId());
           
            return service.deleteLineItem(id);
        }
        finally {
            getConcurrencyService().releaseLock(lockId);
        }
       
    }
This takes advantage of the extension concept already built into Flex, where we can always get a reference to the Spring Bean ID of the service used to process project elements of a specific type.  For example, if this method were being invoked for a quote line item, the object returned from the Spring application context using the extension's instance service ID would be of type FinancialDocumentService (which is a subinterface of ProjectElementService).  The deleteLineItem() method is overridden on that service (and for all other project element classes with line item support) to include the delete logic specific to quote line items.

Delta Processing

When a quote or pull sheet is changed, the server sends back a packet of information to the Flash client to reflect the change.  We call this a delta.  The delta information is unique to the type of document.  Quote deltas are different from pull sheet deltas.  They aren't interchangeable. 

When you delete line items from a quote - and are presented with the option to also delete pull sheet line items or lines from other financial documents (like Rental PO's), the delta sent back to the client should only contain information for line items that actually belonged to the quote you were originally working with.  Otherwise, the delta would contain superfluous information and might even cause a fatal error.  To work around this issue, we added some sorting logic to the facade.  Here's a snippet from the facade method used to process quote line item deletes:

       //this first loop delegates to the generic deletion logic now moved to the service layer
        for (String id : lineIds) {          
            deleteLines.addAll(getElementService().deleteLineItem(id));

        }
       
        //this loop iterates over all line items processed in the deletion (including cascaded deletes)
        for (ProjectElementLineItem deleteLine : deleteLines) {
            deleteLine = (ProjectElementLineItem)ProxyUtils.getProxyTarget(deleteLine);
                        /*
                        The first two conditionals ensure that the each line item is a quote line and that it 
                        belongs to the quote.  Otherwise, we skip it and leave it out of the delta result.
                        */
            if (deleteLine instanceof FinancialDocumentLineItem) {
                FinancialDocumentLineItem docLine = (FinancialDocumentLineItem)deleteLine;
                if (docLine.getProjectElement().getObjectIdentifier().equals(documentId)) {
                    if ((docLine.getParentLineItem() != null)
                            && (docLine.getParentLineItem().getChildLineItems() != null)) {
       
                        docLine.getParentLineItem().getChildLineItems()
                                .remove(deleteLine);
                        getDocumentService().processFullRecalc(doc);
       
                        results.add(toDeleteDTO(docLine, collab));
                        FinancialDocumentLineItemDTO parentLineDTO = toDTO(
                                docLine.getRootLineItem(), doc, collab, false);
                        parentLineDTO.setTriggersRecalc(true);
                        results.add(parentLineDTO);
       
                    } else {
                        results.add(toDeleteDTO(docLine, collab));
                    }
            }
            }

        }

We have similar code in the pull sheet facade for managing the serialization/delta processing quirks presented there.

Progress So Far

This new dialog will be in version 4.4.33 of Flex.  Implementation for line item deletes is nearly complete and adding delete confirmation support to workflow and automation profiles will start on Monday.

No comments:

Post a Comment