Skip to content

Updated Finance Forecasting Report

Original idea from Kevin Rutledge - Recipe Link
Rewritten by Michael Allen to add support for fiscal years, and to fix a few display bugs

This is a fairly complicated report. Read Kevin's recipe (linked above) for a more thorough explanation.

Dynamic Data Block

Query

/* BEGIN Configuration */
DECLARE @Years INT = 4; --How many years to pull? (Including the current year; Changing this will break the lava template)
DECLARE @StartMonth INT = 1; --What month does your FY start on?
DECLARE @AccountList VARCHAR(MAX) = '5,12,24,34,55'; --Comma seperated list of AccountIds to include
/* END Configuration */

DECLARE @StartDate DATE = DATEFROMPARTS( DATEPART( YEAR, GETDATE() ) - ( @Years - 1 ) , @StartMonth, 1 );
DECLARE @EndDate DATE = DATEADD( YEAR, ( @Years - 1 ), @StartDate );

/* If we haven't started this FY yet */
IF ( DATEPART( MONTH, GETDATE() ) < @StartMonth ) BEGIN
    SET @StartDate = DATEADD( YEAR, -1, @StartDate )
    SET @EndDate = DATEADD( YEAR, -1, @EndDate )
END;

DECLARE @Results TABLE( Total DECIMAL(20,2), Year INT, Month INT );

INSERT INTO @Results
SELECT
    SUM( ftd.Amount ) 'Total'
    ,DATEPART( YEAR, ft.TransactionDateTime ) 'Year'
    ,DATEPART( MONTH, ft.TransactionDateTime ) 'Month'
FROM
    [FinancialTransactionDetail] ftd
    JOIN [FinancialTransaction] ft ON ftd.TransactionId = ft.Id
    --Using an inner join to filter the accounts that are included
    INNER JOIN ufnUtility_CsvToTable( @AccountList ) acct ON ftd.AccountId = acct.Item
WHERE
    ft.TransactionDateTime >= @StartDate
    AND ft.TransactionDateTime < @EndDate
    AND ft.TransactionTypeValueId = 53 --Contribution
GROUP BY
    DATEPART( YEAR, ft.TransactionDateTime )
    ,DATEPART( MONTH, ft.TransactionDateTime )
;

/* table1 = current year */
SET @StartDate = @EndDate;
SET @EndDate = DATEFROMPARTS( DATEPART( YEAR, GETDATE() ), DATEPART( MONTH, GETDATE() ), 1 );

SELECT(
    SELECT
        SUM( ftd.Amount ) 'Total'
        ,DATEPART( YEAR, ft.TransactionDateTime ) 'Year'
        ,DATEPART( MONTH, ft.TransactionDateTime ) 'Month'
    FROM
        [FinancialTransactionDetail] ftd
        JOIN [FinancialTransaction] ft ON ftd.TransactionId = ft.Id
        --Using an inner join to filter the accounts that are included
        INNER JOIN ufnUtility_CsvToTable( @AccountList ) acct ON ftd.AccountId = acct.Item
    WHERE
        ft.TransactionDateTime >= @StartDate
        AND ft.TransactionDateTime < @EndDate
        AND ft.TransactionTypeValueId = 53 --Contribution
    GROUP BY
        DATEPART( YEAR, ft.TransactionDateTime )
        ,DATEPART( MONTH, ft.TransactionDateTime )
    ORDER BY 'Year', 'Month'
    FOR JSON PATH
) 'json';

/* table2, table3, table4 = previous 3 years*/
DECLARE @i INT = 0;

WHILE @i < ( @Years - 1 ) BEGIN
    SELECT (
        SELECT * FROM @Results
        ORDER BY 'Year', 'Month'
        OFFSET ( @i * 12 ) ROWS
        FETCH NEXT 12 ROWS ONLY
        FOR JSON PATH
    ) 'json';

    SET @i = @i + 1;
END;

/* table5 = totals */
SELECT(
    SELECT
        SUM( Total ) 'Total'
        ,Month
        ,MIN( Year ) 'Year'
    FROM @Results
    GROUP BY Month
    ORDER BY 'Year'
    FOR JSON PATH
) 'json';

Formatted Output

{% assign current   = table1.rows[0].json | FromJSON %}
{% assign 3yearsAgo = table2.rows[0].json | FromJSON %}
{% assign 2yearsAgo = table3.rows[0].json | FromJSON %}
{% assign 1yearsAgo = table4.rows[0].json | FromJSON %}
{% assign totals    = table5.rows[0].json | FromJSON %}

{% assign 3yearsAgoTotal = 0 %}
{% assign 2yearsAgoTotal = 0 %}
{% assign 1yearsAgoTotal = 0 %}
{% assign currentTotal   = 0 %}

{% for row in 3yearsAgo %}
    {% assign 3yearsAgoTotal = 3yearsAgoTotal | Plus:row.Total %}
{% endfor %}
{% for row in 2yearsAgo %}
    {% assign 2yearsAgoTotal = 2yearsAgoTotal | Plus:row.Total %}
{% endfor %}
{% for row in 1yearsAgo %}
    {% assign 1yearsAgoTotal = 1yearsAgoTotal | Plus:row.Total %}
{% endfor %}
{% for row in current %}
    {% assign currentTotal = currentTotal | Plus:row.Total %}
{% endfor %}
{% assign grandTotal = 3yearsAgoTotal | Plus:2yearsAgoTotal | Plus:1yearsAgoTotal %}

{% capture multipliers %}
    [
    {% for row in totals %}
        {{ row.Total | DividedBy:grandTotal,4 }}
        {% unless forloop.last %},{% endunless %}
    {% endfor %}
    ]
{% endcapture %}
{% assign multipliers = multipliers | FromJSON %}

<style>
.year {
    text-align:center;
    font-weight: bold;
}
.text-right {
    font-family: monospace;
    text-align: right;
}
.table {
    font-size: 0.9em;
}
</style>

<div class="row">
    <div class="col-lg-3 col-md-6">
        <h5 class="year">{{ 3yearsAgo | Select:'Year' | Uniq | Join:'-' }}</h5>
        <table class="text-right table">
            <tr>
                <th class="text-right">Month</th>
                <th class="text-right">Total Given</th>
                <th class="text-right">% of Total</th>
            </tr>
{% for month in 3yearsAgo %}
            <tr>
                <td>{{ month.Month | Append:'/01/' | Append:month.Year | Date:'MMM. yy' }}</td>
                <td>{{ month.Total | FormatAsCurrency }}</td>
                <td>{{ month.Total | DividedBy:3yearsAgoTotal,4 | Format:'p' }}</td>
            </tr>
{% endfor %}
        </table>
    </div>
    <div class="col-lg-3 col-md-6">
        <h5 class="year">{{ 2yearsAgo | Select:'Year' | Uniq | Join:'-' }}</h5>
        <table class="text-right table">
            <tr>
                <th class="text-right">Month</th>
                <th class="text-right">Total Given</th>
                <th class="text-right">% of Total</th>
            </tr>
{% for month in 2yearsAgo %}
            <tr>
                <td>{{ month.Month | Append:'/01/' | Append:month.Year | Date:'MMM. yy' }}</td>
                <td>{{ month.Total | FormatAsCurrency }}</td>
                <td>{{ month.Total | DividedBy:2yearsAgoTotal,4 | Format:'p' }}</td>
            </tr>
{% endfor %}
        </table>
    </div>
    <div class="col-lg-3 col-md-6">
        <h5 class="year">{{ 1yearsAgo | Select:'Year' | Uniq | Join:'-' }}</h5>
        <table class="text-right table">
            <tr>
                <th class="text-right">Month</th>
                <th class="text-right">Total Given</th>
                <th class="text-right">% of Total</th>
            </tr>
{% for month in 1yearsAgo %}
            <tr>
                <td>{{ month.Month | Append:'/01/' | Append:month.Year | Date:'MMM. yy' }}</td>
                <td>{{ month.Total | FormatAsCurrency }}</td>
                <td>{{ month.Total | DividedBy:1yearsAgoTotal,4 | Format:'p' }}</td>
            </tr>
{% endfor %}
        </table>
    </div>
    <div class="col-lg-3 col-md-6">
        <h5 class="year">3 Year Total</h5>
        <table class="text-right table">
            <tr>
                <th class="text-right">Month</th>
                <th class="text-right">Total Given</th>
                <th class="text-right">% of Total</th>
            </tr>
{% for month in totals %}
            <tr>
                <td>{{ month.Month | Append:'/01/2000' | Date:'MMM.' }}</td>
                <td>{{ month.Total | FormatAsCurrency }}</td>
                <td>{{ multipliers[forloop.index0] | Format:'p' }}</td>
            </tr>
{% endfor %}
        </table>
    </div>
</div>
<div class="row">
    <div class="col-md-6">
        <h5 class="year">Current Year: {{ current | Select:'Year' | Uniq | Join:'-' }} </h5>
{% if currentTotal == 0 %}
        <p>At least 1 month must be complete.</p>
{% else %}
        <table class="text-right table">
            <tr>
                <th class="text-right">Month</th>
                <th class="text-right">Total Given</th>
                <th class="text-right">% Multiplier</th>
                <th class="text-right">Year End Projection</th>
            </tr>
    {% assign cumulativePercent = 0 %}
    {% for month in current %}
        {% assign cumulativePercent = cumulativePercent | Plus:multipliers[forloop.index0] %}
            <tr>
                <td>{{ month.Month | Append:'/01/' | Append:month.Year | Date:'MMM. yy' }}</td>
                <td>{{ month.Total | FormatAsCurrency }}</td>
                <td>{{ multipliers[forloop.index0] | Format:'p' }}</td>
                <td>{{ month.Total | DividedBy:multipliers[forloop.index0] | FormatAsCurrency }}</td>
            </tr>
    {% endfor %}
        </table>
{% endif %}
    </div>
    <div class="col-lg-3 col-md-6">
        <h5 class="year">Yearly Totals</h5>
        <table class="text-right table">
            <tr>
                <th class="text-right">Year</th>
                <th class="text-right">Total</th>
            </tr>
            <tr>
                <td>{{ 3yearsAgo | Select:'Year' | Uniq | Join:'-' }}</td>
                <td>{{ 3yearsAgoTotal | FormatAsCurrency }}</td>
            </tr>
            <tr>
                <td>{{ 2yearsAgo | Select:'Year' | Uniq | Join:'-' }}</td>
                <td>{{ 2yearsAgoTotal | FormatAsCurrency }}</td>
            </tr>
            <tr>
                <td>{{ 1yearsAgo | Select:'Year' | Uniq | Join:'-' }}</td>
                <td>{{ 1yearsAgoTotal | FormatAsCurrency }}</td>
            </tr>
{% if currentTotal > 0 %}
            <tr>
                <td>{{ current | Select:'Year' | Uniq | Join:'-' }}</td>
                <td>{{ currentTotal | FormatAsCurrency }}</td>
            </tr>
{% endif %}
        </table>
        <div class="alert-success alert"  style="margin-top:20px;">
            <h5>{{ current | Select:'Year' | Uniq | Join:'-' }} Year End Projection:</h5>
{% if currentTotal == 0 %}
            <p>At least 1 month must be complete.</p>
{% else %}
            <p>{{ currentTotal | DividedBy:cumulativePercent | FormatAsCurrency }}</p>
{% endif %}
        </div>
    </div>
</div>
<div class="row">
{% assign currentSize = current | Size | Minus:1 %}
{% capture chartData %}
    [
    {% for i in (0..11) %}
        {
            name: '{{ 3yearsAgo[i].Month | Append:'/01/' | Append:3yearsAgo[i].Year | Date:'MMM.' }}'
            ,3year: '{{ 3yearsAgo[i].Total }}'
            ,2year: '{{ 2yearsAgo[i].Total }}'
            ,1year: '{{ 1yearsAgo[i].Total }}'
        {% unless i > currentSize %}
            ,current: '{{ current[i].Total }}'
        {% endunless %}
        }
        {% unless forloop.last %},{% endunless %}
    {% endfor %}
    ]
{% endcapture %}
{% assign chartData = chartData | FromJSON %}

    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js"> </script>
    <div class="chart-container" style="position:relative;height:300px;width:100%;margin-bottom:50px;">
        <h5>Giving History By Month</h5>
        <canvas id="myChart"></canvas>
    </div>
</div>

<script>
    var ctx = document.getElementById("myChart").getContext('2d');
    var myChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: [ "{{ chartData | Select:'name' | Join:'","' }}" ],
            datasets: [
                {
                    label: '{{ 3yearsAgo | Select:'Year' | Uniq | Join:'-' }}',
                    fill: false,
                    data:  [ {{ chartData | Select:'3year' | Join:',' }} ],
                    backgroundColor: [ "rgba(54,162,235,1)" ],
                    borderColor: [ "rgba(54,162,235,1)" ],
                    borderWidth: 1
                },
                {
                    label: '{{ 2yearsAgo | Select:'Year' | Uniq | Join:'-' }}',
                    fill: false,
                    data:  [ {{ chartData | Select:'2year' | Join:',' }} ],
                    backgroundColor: [ "rgba(75,192,192,1)" ],
                    borderColor: [ "rgba(75,192,192,1)" ],
                    borderWidth: 1
                },
                {
                    label: '{{ 1yearsAgo | Select:'Year' | Uniq | Join:'-' }}',
                    fill: false,
                    data:  [ {{ chartData | Select:'1year' | Join:',' }} ],
                    backgroundColor: [ "rgba(255,159,64,1)" ],
                    borderColor: [ "rgba(255,159,64,1)" ],
                    borderWidth: 1
                },
                {
                    label: '{{ current | Select:'Year' | Uniq | Join:'-' }}',
                    fill: false,
                    data: [ {{ chartData | Select:'current' | Join:',' }} ],
                    backgroundColor: [ "rgba(153,102,255,1)" ],
                    borderColor: [ "rgba(153,102,255,1)" ],
                    borderWidth: 1
                },
            ]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            animation: {
                duration: 2500,
            },
            legend: {
                position: 'right'
            },
            scales: {
                yAxes: [{
                    ticks: {
                        beginAtZero:false,
                        callback: function(value, index, values) {
                            return '$' + value/1000 + 'k';
                        }
                    }
                }]
            },
            tooltips: {
                callbacks: {
                    label: function(tooltipItem, data) {
                        var label = data.datasets[tooltipItem.datasetIndex].label || '';

                        if (label) {
                            label += ': ';
                        }
                        label += '$' + Math.round(tooltipItem.yLabel / 100) / 10 + 'k';
                        return label;
                    }
                }
            }
        }
    });
</script>