Sales statistics
[#if salesreport.dateIntervalStart??]
[#if salesreport.dateIntervalEnd??]
${salesreport.dateIntervalStart?date} - ${salesreport.dateIntervalEnd?date}
[#else]
After ${salesreport.dateIntervalStart?date}
[/#if]
[#else]
[#if salesreport.dateIntervalEnd??]
Before ${salesreport.dateIntervalEnd?date}
[#else]
All dates
[/#if]
[/#if]
[#if salesreport.invoices?size == 0]
There is no data available for the selected report settings.
[#else]
[#assign grandTotal=salesreport.calculateGrandTotal(salesreport.invoices)]
[#assign balance=salesreport.calculateBalance(salesreport.invoices)]
[#assign months=salesreport.groupDatesByMonth(salesreport.invoices)]
[#assign clients=salesreport.groupClients(salesreport.invoices)]
[#assign clientsMap = []]
[#list clients as group]
[#assign groupInvoices = salesreport.filterByClient(salesreport.invoices, group)]
[#assign groupGrandTotal = salesreport.calculateGrandTotal(groupInvoices)]
[#assign clientsMap = clientsMap + [{"client" : group, "percentage": (groupGrandTotal.contents()?first.amount / grandTotal.contents()?first.amount), "grandTotal": groupGrandTotal}]]
[/#list]
[#assign clientsBalanceMap = []]
[#list clients as group]
[#assign groupInvoices = salesreport.filterByClient(salesreport.invoices, group)]
[#assign groupBalance = salesreport.calculateBalance(groupInvoices)]
[#if !groupBalance.contents()?first.zero]
[#assign clientsBalanceMap = clientsBalanceMap + [{"client" : group, "percentage": (groupBalance.contents()?first.amount / balance.contents()?first.amount), "balance": groupBalance}]]
[/#if]
[/#list]
[#-- 1. Sales stats --]
Sales |
Months |
Avg. Sales / Month |
${grandTotal} |
${months?size?string("#")} |
${grandTotal.contents()?first.amount / months?size} |
[#-- 2. Unpaid invoices stats --]
[#assign dueInvoices = []]
[#assign overdueInvoices = []]
[#list salesreport.invoices as invoice]
[#if !invoice.paid]
[#if invoice.overdue]
[#assign overdueInvoices = overdueInvoices + [invoice]]
[#else]
[#assign dueInvoices = dueInvoices + [invoice]]
[/#if]
[/#if]
[/#list]
[#assign duePayments=salesreport.calculateBalance(dueInvoices)]
[#assign overduePayments=salesreport.calculateBalance(overdueInvoices)]
[#assign paymentsTotal=salesreport.calculatePaymentsTotal(salesreport.invoices)]
[#assign paymentsTotalChartProperties = {
'title.visible': false,
'legend.visible': true,
'legend.position': 'right',
'legend.verticalAlignment': 'top',
'legend.frame': {"insets": [0, 0, 0, 0]},
'legend.margin': [5, 0, 0, 0],
'legend.itemLabelPadding': [25, 15, 0, 5],
'legend.legendItemGraphicPadding': [25, 0, 0, 0],
'plot.legendLabelGenerator': '{0} (${grandTotal.contents()?first.currency.symbol}{1})',
'plot.backgroundPaint': '',
'plot.outlineVisible': false,
'plot.shadowPaint': '',
'plot.sectionOutlineStroke': [{'width': 2.0}, {'width': 2.0}, {'width': 2.0}],
'plot.sectionOutlinePaint': ['#109618', '#FF9900', '#DC3912'],
'plot.sectionPaint':['#109618', '#FF9900', '#DC3912'],
'plot.startAngle': 90,
'plot.labelGenerator': '',
'plot.centerTextMode': 'fixed',
'plot.centerText': '${(paymentsTotal.contents()?first.amount/grandTotal.contents()?first.amount)?string.percent}',
'plot.centerTextFont': {'size': 36},
'plot.centerTextColor': '#000000',
'plot.sectionDepth': 0.10,
'plot.separatorsVisible': false
} /]
[#outputformat "FTL"]
[#assign paymentsTotalDataSet]
[
['Paid', paymentsTotal.contents()?first.amount],
['Due', duePayments.contents()?first.amount],
['Overdue', overduePayments.contents()?first.amount]
]
[/#assign]
[#-- jfreechart work-around--]
[#-- make sure the first value in the dataset is not zero because the center text won't be displayed --]
[#if paymentsTotal.contents()?first.amount == 0]
[#if duePayments.contents()?first.amount != 0]
[#assign paymentsTotalDataSet]
[
['Due', duePayments.contents()?first.amount],
['Overdue', overduePayments.contents()?first.amount],
['Paid', paymentsTotal.contents()?first.amount]
]
[/#assign]
[#assign paymentsTotalChartProperties = paymentsTotalChartProperties + {
'plot.sectionOutlinePaint': ['#FF9900', '#DC3912', '#109618'],
'plot.sectionPaint':['#FF9900', '#DC3912', '#109618']
}]
[#else]
[#assign paymentsTotalDataSet]
[
['Overdue', overduePayments.contents()?first.amount],
['Due', duePayments.contents()?first.amount],
['Paid', paymentsTotal.contents()?first.amount]
]
[/#assign]
[#assign paymentsTotalChartProperties = paymentsTotalChartProperties + {
'plot.sectionOutlinePaint': ['#DC3912', '#FF9900', '#109618'],
'plot.sectionPaint':['#DC3912', '#FF9900', '#109618']
}]
[/#if]
[/#if]
[/#outputformat]
Paid amount |
 |
[#-- 3. The bar chart that summarize sales by month --]
[#-- Create a list with all the month names --]
[#assign monthNames = []]
[#list 1..12 as month]
[#assign monthNames = monthNames + ["2000-${month?string('00')}-01"?date("yyyy-MM-dd")?string("MMM")]]
[/#list]
[#-- Build the data set expression record by record --]
[#assign allInvoices=salesreport.invoices]
[#assign years=salesreport.groupDatesByYear(allInvoices)]
[#assign maxGrandTotal = 0]
[#outputformat "FTL"]
[#assign monthlySalesDataSet]
[
['Month'
[#list years?sort as year]
, '${year}'
[/#list]
],
[#list monthNames as monthName]
['${monthName}'
[#list years?sort as year]
[#assign yearInvoices=salesreport.filterByDate(allInvoices, year.toInterval())]
[#assign monthsYear=salesreport.groupDatesByMonth(yearInvoices)]
[#assign monthGrandTotal = 0]
[#list monthsYear as month]
[#if monthName == '${month.toString("MMM")}']
[#assign monthInvoices=salesreport.filterByDate(yearInvoices, month.toInterval())]
[#assign monthGrandTotal=salesreport.calculateGrandTotal(monthInvoices).contents()?first.amount]
[#if monthGrandTotal > maxGrandTotal][#assign maxGrandTotal = monthGrandTotal][/#if]
[/#if]
[/#list]
, ${monthGrandTotal?c}
[/#list]
] [#if monthName?has_next],[/#if]
[/#list]
]
[/#assign]
[/#outputformat]
[#assign monthlySalesChartProperties = {
'title.visible': false,
'legend.visible': true,
'legend.position': 'bottom',
'legend.verticalAlignment': 'top',
'legend.frame': {'insets': [0, 0, 0, 0]},
'legend.margin': [0, 0, 20, 0],
'legend.itemLabelPadding': [5, 5, 5, 20],
'legend.legendItemGraphicPadding': [0, 0, 0, 0],
'plot.backgroundPaint': '',
'plot.outlineVisible': false,
'plot.axisOffset': [0, 0, 0, 0],
'plot.rangeAxis.visible': true,
'plot.rangeAxis.axisLineVisible': false,
'plot.rangeAxis.tickMarksVisible': false,
'plot.rangeAxis.labelFont': {'size': 12},
'plot.rangeGridlinesVisible': true,
'plot.rangeGridlinePaint': '#aaaaaa',
'padding': [10, 10, 10, 10]
} /]
Sales by month |
 |
[#-- 4. The pie chart summarize sales by client --]
[#assign topPieChartProperties = {
'title.visible': false,
'legend.visible': false,
'plot.backgroundPaint': '',
'plot.outlineVisible': false,
'plot.shadowPaint': '',
'plot.startAngle': 90,
'plot.labelFont': {'size': 12},
'plot.labelGenerator': '{0} ({2})',
'plot.labelBackgroundPaint': '',
'plot.labelShadowPaint': '',
'plot.labelOutlinePaint': '',
'plot.labelLinkStyle': 'standard',
'plot.maximumLabelWidth': 0.3,
'plot.sectionDepth': 0.25,
'plot.separatorsVisible': false
} /]
[#-- create datasets for the top elements --]
[#outputformat "FTL"]
[#assign clientSalesDataSet]
[
[#if clients?size > 5]
[#assign others = grandTotal.contents()?first.amount]
[#list clientsMap?sort_by('percentage')?reverse?chunk(4)?first as client]
['${client['client'].name}', ${client['grandTotal'].contents()?first.amount?c}] ,
[#assign others = others - client['grandTotal'].contents()?first.amount]
[/#list]
['Others', ${others?c}]
[#else]
[#list clientsMap?sort_by('grandTotal')?reverse as client]
['${client['client'].name}', ${client['grandTotal'].contents()?first.amount?c}] [#if client?has_next],[/#if]
[/#list]
[/#if]
]
[/#assign]
[/#outputformat]
[#-- make sure the label of the last pie section is on the right so that the chart is less crowded --]
[#assign clientSalesChartProperties = topPieChartProperties + {
'plot.startAngle': ((90 - ((clientSalesDataSet?markup_string?eval[clientSalesDataSet?markup_string?eval?size - 1][1] / grandTotal.contents()?first.amount) * 360 / 2)) - 1)?int
}/]
Top Clients |
 |
[#-- 5. The table that summarizes sales by client --]
[#list clientsMap?sort_by('percentage')?reverse as client] |
${(client_index + 1)?string("#")}. |
${client['client'].name} |
${client['percentage']?string("0%")} |
|
${client['grandTotal']} |
[/#list] |
[#if clientsBalanceMap?size != 0]
[#-- 6. The bar chart that summarize outstanding payments by age --]
[#assign unpaid30days = []]
[#assign unpaid60days = []]
[#assign unpaid90days = []]
[#assign unpaid90daysplus = []]
[#list salesreport.invoices as invoice]
[#if !invoice.paid]
[#if invoice.ageInDays < 30]
[#assign unpaid30days = unpaid30days + [invoice]]
[#elseif invoice.ageInDays < 60]
[#assign unpaid60days = unpaid60days + [invoice]]
[#elseif invoice.ageInDays < 90]
[#assign unpaid90days = unpaid90days + [invoice]]
[#else]
[#assign unpaid90daysplus = unpaid90daysplus + [invoice]]
[/#if]
[/#if]
[/#list]
[#outputformat "FTL"]
[#assign invoiceAgingDataSet]
[
['Age', 'Balance'],
['0-30 days', ${salesreport.calculateBalance(unpaid30days).contents()?first.amount?c}],
['30-60 days', ${salesreport.calculateBalance(unpaid60days).contents()?first.amount?c}],
['60-90 days', ${salesreport.calculateBalance(unpaid90days).contents()?first.amount?c}],
['90 days or more', ${salesreport.calculateBalance(unpaid90daysplus).contents()?first.amount?c}]
]
[/#assign]
[/#outputformat]
[#assign chartProperties2 = {
'title.visible': false,
'legend.visible': false,
'plot.backgroundPaint': '',
'plot.outlineVisible': false,
'plot.axisOffset': [0, 0, 0, 0],
'plot.renderer.maximumBarWidth': 0.07,
'plot.rangeAxis.visible': true,
'plot.rangeAxis.axisLineVisible': false,
'plot.rangeAxis.tickMarksVisible': false,
'plot.rangeAxis.labelFont': {'size': 12},
'plot.rangeGridlinesVisible': true,
'plot.rangeGridlinePaint': '#aaaaaa',
'padding': [10, 10, 10, 10]
} /]
Unpaid amount by age |
 |
[#-- 7. The pie chart shows top outstanding clients --]
[#outputformat "FTL"]
[#assign topOutstandingClientsDataSet]
[
[#if clientsBalanceMap?size > 5]
[#assign others = balance.contents()?first.amount]
[#list clientsBalanceMap?sort_by('percentage')?reverse?chunk(4)?first as client]
['${client['client'].name}', ${client['balance'].contents()?first.amount?c}] ,
[#assign others = others - client['balance'].contents()?first.amount]
[/#list]
['Others', ${others?c}]
[#else]
[#list clientsBalanceMap?sort_by('balance')?reverse as client]
['${client['client'].name}', ${client['balance'].contents()?first.amount?c}] [#if client?has_next],[/#if]
[/#list]
[/#if]
]
[/#assign]
[/#outputformat]
[#-- make sure the label of the last pie section is on the right so that the chart is less crowded --]
[#assign topOutstandingClientsPieChartProperties = topPieChartProperties + {
'plot.startAngle': ((90 - ((topOutstandingClientsDataSet?markup_string?eval[topOutstandingClientsDataSet?markup_string?eval?size - 1][1] / balance.contents()?first.amount) * 360 / 2)) - 1)?int
}/]
Top Outstanding Clients |
 |
[#-- 8. The table that shows outstanding clients --]
[#list clientsBalanceMap?sort_by('percentage')?reverse as client] |
${(client_index + 1)?string("#")}. |
${client['client'].name} |
${client['percentage']?string("0%")} |
|
${client['balance']} |
[/#list] |
[/#if]
[/#if]