CASE: unreconciled in fori (account templates)

In this case we are going to have a better look at how unreconciled indicators work in the background, and why it is not ideal to have them included in fori-loops.

This only applies to Account Templates though.
Reconciliation texts’ results and reconciled status will always render in input mode.
In the future this should also become the case for account templates, but right now it’s important to keep the below in mind.

There is already quite a lot of information on this topic available on our Developer Documentation.
Here’s a small recap:

In Silverfin, there are three different modes on which we can render: input-, preview-, and export mode. For performance reasons, the total reconciliation status of a template is stored in the cache. The value that is stored in the cache comes from rendering the template in input mode for reconciliation templates and from rendering the template in export mode for account templates. A very important assumption that is being made when working like this, is that the results (both the results coming from unreconciled tags and named results) should always produce the same values for every mode we render on.

However, things like ic and nic -tags impact what is being rendered in which mode. As a result, we can have different results in input and non-input modes. This could lead to caching issues or inconsistencies between the reconciliation status of the template and the unreconciled indicators within the template.

Also fori-loops can cause issues when combining them with unreconciled indicators. These loops will always foresee an additional empty loop in input mode that allows you to add an additional item to the collection. In export/preview mode, only the existing/populated items in the collection will be shown and used for calculations. When using the unreconciled tag inside a fori-loop, the calculation of the reconciled status will be done including the empty loop. This could potentially result in discrepancies and weird template behaviour.

Example 1

In this template the user has to complete a table with information about the shareholders and the weight of their shares. You make use of a fori-loop in which the user has to complete this information for every shareholder. In the last iteration of the fori-loop (with forloop.last), you check if the total shareholder interest is equal to 100%. If this is not the case, the amount (and template) will become unreconciled.

{% assign total_shareholders_interest = 0 %}

{% stripnewlines %}
<table class="usr-width-100 usr-bordered">
  <thead>
    <tr>
      <th class="usr-width-60 usr-line-bottom"><b>Shareholder name</b></th>
      <th class="usr-width-40 usr-line-bottom usr-align-center"><b>Shareholder interest</b></th>
    </tr>
  </thead>
  <tbody>
    {% fori shareholder in custom.shareholders %}
      <tr>
        <td class="usr-align-left">
          {% input shareholder.name placeholder:'Shareholder name' %}
        </td>
        <td class="usr-align-center">
          {% input shareholder.interest as:currency placeholder:'%' %}
          {% assign total_shareholders_interest = total_shareholders_interest+shareholder.interest %}
        </td>
      </tr>
      {% if forloop.last %}
        <tr>
          <td class="usr-align-left">Total shareholder interest</td>
          <td class="usr-align-center">
            {% if total_shareholders_interest != 100 %}
              {% unreconciled 100-total_shareholders_interest as:indicator unreconciled_text:'The total shareholder interest needs to be 100%'%}
            {% endif %} &nbsp; {{ INT(total_shareholders_interest) }} %
          </td>
        </tr>
      {% endif %}
    {% endfori %}
  </tbody>
</table>
{% endstripnewlines %}

In input view, the template will be unreconciled when you open it for the first time:

The reason is as follows: the fori will always create an empty row in input mode, which in this case is both the first and the last loop. The unreconciled check is thus being triggered. As the total shareholder interest is of course different from 100%, the template is unreconciled.

However, when looking at the template in preview mode, you will notice that the unreconciled indicator has disappeared and the template becomes reconciled. In preview mode, only the populated items will be considered, and since nothing has been inputted, the last row including the unreconciled check logic won’t be triggered. The result is a discrepancy between both input and preview mode; something that should be avoided.
:bulb:Tip: you can use the debug mode to spot differences in results and named results. For this use case you’ll even get a cache error, because the template’s reconciled status is different in input and preview mode!

One way to avoid this is by removing the forloop.last to print the total row, and rather do the check outside of the fori-loop.
Additionally, there are a lot of use-cases where you don’t even want to show the total line until information has been entered in the collection. Let’s combine both adjustments in the code:

{% assign total_shareholders_interest = 0 %}

{% stripnewlines %}
<table class="usr-width-100 usr-bordered">
  <thead>
    <tr>
      <th class="usr-width-60 usr-line-bottom"><b>Shareholder name</b></th>
      <th class="usr-width-40 usr-line-bottom usr-align-center"><b>Shareholder interest</b></th>
    </tr>
  </thead>
  <tbody>
    {% fori shareholder in custom.shareholders %}
      <tr>
        <td class="usr-align-left">
          {% input shareholder.name placeholder:'Shareholder name' %}
        </td>
        <td class="usr-align-center">
          {% input shareholder.interest as:currency placeholder:'%' %}
        </td>
      </tr>
      
      {% comment %}Get total shareholders interest{% endcomment %}
      {% assign total_shareholders_interest = total_shareholders_interest+shareholder.interest %}
    {% endfori %}

    {% comment %}Only show the total if the collection is not empty{% endcomment %}
    {% if custom.shareholders != empty %}
      <tr>
        <td class="usr-align-left">Total shareholder interest</td>
        <td class="usr-align-center">
          {% if total_shareholders_interest != 100 %}
            {% unreconciled 100-total_shareholders_interest as:indicator unreconciled_text:'The total shareholder interest needs to be 100%'%}
          {% endif %} &nbsp; {{ INT(total_shareholders_interest) }}%
        </td>
      </tr>    
    {% endif %}

  </tbody>
</table>
{% endstripnewlines %}

The result is that the template is reconciled in both input- and preview mode if there are no inputs. There will be no differences anymore, and this will thus not lead to caching issues in the background.

Example 2

This template is similar to the first one: users have to provide information about the active share classes in their company. Let’s now assume that it is not allowed for a company to have more than 3 active share classes at the same time.

The code will look like this:

{% assign total_share_classes = 0 %}

{% stripnewlines %}
<table class="usr-width-100">
  <thead>
    <tr>
      <th class="usr-width-60 usr-line-bottom"><b>Share class name</b></th>
      <th class="usr-width-40 usr-line-bottom"><b>Additional information</b></th>
    </tr>
  </thead>
  <tbody>
    {% fori share_class in custom.share_classes %}
      <tr>
        <td class="">
          {% input share_class.name %}
        </td>
        {% assign total_share_classes = total_share_classes+1 %}
        <td class="">
          {% input share_class.description as:text placeholder:'Additional information' %}
        </td>
      </tr>
      {% if forloop.last %}
        <tr>
          <td class="usr-align-left">Total number of active share classes:</td>
          <td class="usr-align-center">
            {% if total_share_classes > 3 %}
              {% unreconciled 1 as:indicator unreconciled_text:'There can only be three different share classes' %}
            {% endif %} &nbsp; {{ INT(total_share_classes) }}
          </td>
        </tr>
      {% endif %}
    {% endfori %}
  </tbody>
</table>
{% endstripnewlines %}

{% result 'total_shares' total_share_classes %}

And this is what the template looks like in input mode if you fill out three classes:

And in preview mode:

As you can see, in input view an additional class is counted because of the empty row that is always being presented.
Using the debug mode, you even get two warnings.

  1. Because the reconciled status is different between input and preview
  2. Because the result total_shares contains a different value

One way to avoid this issue is by taking the calculation of the classes outside of the fori-loop. You also need to move the unreconciled logic outside of the fori, similar to the first example.
You can calculate the total amount of classes by looping (with a for loop) over the items in the collection outside of the fori to calculate total_share_classes in a similar way.
For this use case however, the number of shares also equals the number of items in the collection. So you can just use the size of the collection instead.

The code then looks as follows:

{% assign total_share_classes = 0 %}

{% stripnewlines %}
<table class="usr-width-100">
  <thead>
    <tr>
      <th class="usr-width-60 usr-line-bottom"><b>Share class name</b></th>
      <th class="usr-width-40 usr-line-bottom"><b>Additional information</b></th>
    </tr>
  </thead>
  <tbody>
    {% fori share_class in custom.share_classes %}
      <tr>
        <td class="">
          {% input share_class.name %}
        </td>
        <td class="">
          {% input share_class.description as:text placeholder:'Additional information' %}
        </td>
      </tr>
    {% endfori %}
    
    {% comment %}Total line{% endcomment %}
    {% assign total_share_classes = custom.share_classes.size %}
    <tr>
      <td class="usr-align-left">Total number of active share classes:</td>
      <td class="usr-align-center">
        {% if total_share_classes > 3 %}
          {% unreconciled 1 as:indicator unreconciled_text:'There can only be three different share classes' %}
        {% endif %} &nbsp; {{ INT(total_share_classes) }}
      </td>
    </tr>
  </tbody>
</table>
{% endstripnewlines %}

{% result 'total_shares' total_share_classes %}

There are other ways to solve similar issues, but we prefer moving the unreconciled indicators outside of the fori above other solutions.