Bad Design: A tale of De-Architecture and De-Engineering

Every one of us has been guilty, or will be guilty, of creating a bad solution to a problem at some point in our career. If you have not done so yet, just be prepared, your day will come. Smile

Most of the time, people do not start off trying to create a bad solution, it just ends up like that. The cause can be just about anything, but here are a few:

  • Bad information (from research or the customer)
  • Change in design of Dynamics CRM (i.e., they changed something and didn’t tell you)
  • Assumptions
  • Over-thinking a problem
  • Under-thinking a problem
  • Unfamiliarity with the product, its features, and functionality points

Background

Back in 2011 I inherited a customer from another consultant who was too busy to handle the upgrade from CRM 4.0 to 2011. The system had been fairly customized and had a variety of plugins and a fair amount of JavaScript.

One of the things that had been done was to modify the calculations of the Quote/Order/Invoice system in order to incorporate a customer-requested design change.

When I saw the code, and there was a lot of it (plugins and JavaScript), I question the consultant on why it had been done this way. His reply was, “they had very specific requirements about how everything was calculated.”  Looking at the code, I could honestly not see what would have caused anyone to rewrite the calculation engine, but that is what had been done.

The main part of the customization was that custom total fields were added and the calculated values were stored in those fields while also updating the original total fields with the custom calculations.

The one thing that was different and extremely important was a custom field that allowed the user to enter a specific price per unit that would take the place of any system-calculated price.

So I upgraded everything, cleaned up the code a bit, and everything continued to work as designed/requested.

Then we upgraded to CRM 2015, and things started to NOT work as design.  Specifically with the Opportunity Product.  You would change a value and see before your eyes, the numbers change.

After spending about 3 hours one night tracing the actions of the system, I found this flow:

  1. User changes the quantity on the form
  2. Our custom JavaScript fires
  3. Internal CRM JavaScript fires which performed a calculation and saved the record
  4. Our custom Plugins fire
  5. [Possibly] Internal CRM plugins fire

The net result of this was that Dynamics CRM was overwriting our newly calculated values with their own, which was driving the users crazy.

Dynamics CRM Internals Notes

If you did not know, the Quote, Order, and Invoice entities are exactly the same, just in different buckets. Also, the Opportunity Product, Quote Product, Order Product, and Invoice Product entities also share the same structure.

These entities are really hard to customize because there is a fair bit of magic that happens behind the scenes through either JavaScript or Plugins, or both.

Also, even though these entities are similar in layout and design, it would appear that do not all function exactly  the same.

Also, everything is version-specific.  With CRM 2015, only the Opportunity Product uses the new form design. Quote/Order/Invoice Products still use the Information form.

Also remember that starting with Dynamics CRM 2013, the QOI and Opportunity forms were all redesigned to use the new layout which includes an editable grid for the Product line items. Since you can create records from the grid, and even change some fields, keep in mind that any JavaScript on the Product detail form will never be executed.

Any work that must be done on the create of one of these records must be accomplished using a plugin.

The Problem

The main problem was the internal Dynamics CRM code was overwriting the results of our custom code – somewhere – and after reviewing the facts, I realized there was just no way I could circumvent the actions of the product.

Correcting the Problem

The solution to this problem was actually rather simple and I still wonder why this wasn’t tried before.

It turns out that there is a Manual Discount field which is actually used by the automatic calculation engine.  After spending about 30 minutes with the customer, we were able to successfully test the use of the Manual Discount field as the basis for our custom per unit price.

I used that field to reduce the price per unit so that the calculation appeared to be using the manually entered price per unit, even though it was not. The code looks like this:

Code Snippet
  1. function newDiscountCalculation() {
  2.     if (Xrm.Page.getAttribute(“new_netpriceperunit”).getValue() == null) {
  3.         Xrm.Page.getAttribute(“manualdiscountamount”).setValue(0.00);
  4.         return;
  5.     }
  6.     var priceDifference = Xrm.Page.getAttribute(“priceperunit”).getValue() –
  7.                           Xrm.Page.getAttribute(“new_netpriceperunit”).getValue();
  8.     if (priceDifference === 0) {
  9.         return;
  10.     }
  11.     var manualDiscount = (priceDifference –
  12.                           Xrm.Page.getAttribute(“volumediscountamount”).getValue()) *
  13.                           Xrm.Page.getAttribute(“quantity”).getValue();
  14.     Xrm.Page.getAttribute(“manualdiscountamount”).setValue(manualDiscount);
  15. }

 

So that took care of the data issue, the remainder of the problem was corrected by removing ALL of the custom JavaScript and Plugins that also performed the calculations.

The only issue that remained was we had years worth of data in the wrong fields and many of the additional functionality points: Templates, reports, etc., were using these custom total fields.

Final Results

So here are the results of this process:

Functionality

As far as the users go, there was not change to the functionality they have been using for the past 7 years or so.

Plugins

I decommissioned and removed five plugins because their function was either unnecessary or redundant. The plugins that currently exist perform addition functions but the only pricing-related things they do are to put the values from the standard total fields into the custom total fields because those are the fields that are being used throughout the entire system.

JavaScript

About 95% of the JavaScript related to these entities was removed. Most of the work is actually done in the QOI Product entities and since the fields are named the same, I was able to have a single JavaScript library shared among all of these entities.

Lessons Learned

You’ve probably heard me say this many times but it is never a good idea to recreate Dynamics CRM built-in functionality.  Sooner or later you will pay the price for that decision and it is never pretty.

So, don’t do it.

Leave a Reply 3 comments