Skip to content

Commit 30b534f

Browse files
authored
Merge pull request #3445 from AtlasOfLivingAustralia/feature/issue3429
Keep RCS funding totals up to date #3429
2 parents 7dc8c3c + 5ee59cc commit 30b534f

File tree

10 files changed

+196
-23
lines changed

10 files changed

+196
-23
lines changed

grails-app/assets/javascripts/budget.js

+33-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
var BudgetConstants = {
2+
PERIODICALLY_REVISED_TOTAL : 'periodicallyRevisedTotal',
3+
PERIODIC_TOTAL : 'perPeriodBreakdown'
4+
};
5+
16
function BudgetViewModel(o, period) {
27
var self = this;
38
if (!o) o = {};
@@ -71,11 +76,15 @@ function BudgetTotalViewModel(rows, index) {
7176

7277
function BudgetRowViewModel(o, period) {
7378
var self = this;
79+
80+
7481
if (!o) o = {};
7582
if (!o.activities || !_.isArray(o.activities)) o.activities = [];
7683
self.shortLabel = ko.observable(o.shortLabel);
7784
self.description = ko.observable(o.description);
7885
self.activities = ko.observableArray(o.activities);
86+
self.type = o.type || BudgetConstants.PERIODIC_TOTAL;
87+
7988

8089
var arr = [];
8190
// Have at least one period to record, which will essentially be a project total.
@@ -94,10 +103,30 @@ function BudgetRowViewModel(o, period) {
94103

95104
self.rowTotal = ko.computed(function () {
96105
var total = 0.0;
97-
ko.utils.arrayForEach(this.costs(), function (cost) {
98-
if (cost.dollar())
99-
total += parseFloat(cost.dollar());
100-
});
106+
// The Periodically Revised Total is a special case used by the IPPRS system whereby each year they
107+
// revise the total contract value. For this case, the rowTotal is determined by starting in the
108+
// current year and working backwards until a non-zero value is found.
109+
if (self.type === BudgetConstants.PERIODICALLY_REVISED_TOTAL) {
110+
var currentDateString = convertToIsoDate(new Date());
111+
var i = 0;
112+
113+
// Find the current period.
114+
while (i<period.length-1 && period[i].value <= currentDateString) {
115+
i++;
116+
}
117+
total = parseFloat(self.costs()[i].dollar());
118+
while (i>0 && (isNaN(total) || total == 0)) {
119+
i--;
120+
total = parseFloat(self.costs()[i].dollar());
121+
}
122+
}
123+
else { //self.type === PERIODIC_TOTAL is the default - i.e. the default behaviour is to sum the columns
124+
ko.utils.arrayForEach(this.costs(), function (cost) {
125+
if (cost.dollar())
126+
total += parseFloat(cost.dollar());
127+
});
128+
}
129+
101130
return total;
102131
}, this).extend({currency: {}});
103132
};

grails-app/assets/javascripts/services.js

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ function OrganisationDetailsViewModel(o, organisation, budgetHeaders, allService
55
targets = o.services && o.services.targets || [];
66
self.areTargetsAndFundingEditable = config.areTargetsAndFundingEditable;
77
self.services = new OrganisationServicesViewModel(serviceIds, config.services, targets, budgetHeaders, {areTargetsEditable:config.areTargetsAndFundingEditable});
8+
9+
if (!o.funding) {
10+
o.funding = {
11+
rows: [
12+
{
13+
type:BudgetConstants.PERIODICALLY_REVISED_TOTAL,
14+
shortLabel: 'rcsContractedFunding',
15+
description: 'RCS Contracted Funding'
16+
}
17+
]
18+
}
19+
}
820
self.funding = new BudgetViewModel(o.funding, budgetHeaders);
921
self.funding.isEditable = config.areTargetsAndFundingEditable;
1022

grails-app/controllers/au/org/ala/merit/OrganisationController.groovy

+8-2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ class OrganisationController {
8484
dashboardData = organisationService.scoresForOrganisation(organisation, scores?.collect{it.scoreId}, !hasEditorAccess)
8585
}
8686
boolean showTargets = userService.userIsSiteAdmin() && services && targetPeriods
87+
// This call is used to ensure the organisation funding total is kept up to date as the algorithm
88+
// for selecting the current total is based on the current date. The funding total is used when
89+
// calculating data for the dashboard.
90+
if (showTargets) {
91+
organisationService.checkAndUpdateFundingTotal(organisation)
92+
}
8793
boolean targetsEditable = userService.userIsAlaOrFcAdmin()
8894
List reportOrder = null
8995
if (reportingVisible) {
@@ -100,7 +106,7 @@ class OrganisationController {
100106
}
101107
}
102108

103-
boolean showEditAnnoucements = organisation.projects?.find{Status.isActive(it.status)}
109+
boolean showEditAnnouncements = organisation.projects?.find{Status.isActive(it.status)}
104110

105111
List adHocReportTypes =[ [type: ReportService.PERFORMANCE_MANAGEMENT_REPORT]]
106112

@@ -114,7 +120,7 @@ class OrganisationController {
114120
projects : [label: 'Reporting', template:"/shared/projectListByProgram", visible: reportingVisible, stopBinding:true, default:reportingVisible, type: 'tab', reports:organisation.reports, adHocReportTypes:adHocReportTypes, reportOrder:reportOrder, hideDueDate:true, displayedPrograms:projectGroups.displayedPrograms, reportsFirst:true, declarationType:SettingPageType.RDP_REPORT_DECLARATION],
115121
sites : [label: 'Sites', visible: reportingVisible, type: 'tab', stopBinding:true, projectCount:organisation.projects?.size()?:0, showShapefileDownload:adminVisible],
116122
dashboard : [label: 'Dashboard', visible: reportingVisible, stopBinding:true, type: 'tab', template:'dashboard', reports:dashboardReports, dashboardData:dashboardData],
117-
admin : [label: 'Admin', visible: adminVisible, type: 'tab', template:'admin', showEditAnnoucements:showEditAnnoucements, availableReportCategories:availableReportCategories, targetPeriods:targetPeriods, services: services, showTargets:showTargets, targetsEditable:targetsEditable]]
123+
admin : [label: 'Admin', visible: adminVisible, type: 'tab', template:'admin', showEditAnnoucements:showEditAnnouncements, availableReportCategories:availableReportCategories, targetPeriods:targetPeriods, services: services, showTargets:showTargets, targetsEditable:targetsEditable]]
118124

119125
}
120126

grails-app/services/au/org/ala/merit/OrganisationService.groovy

+68-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import org.joda.time.Period
1313
class OrganisationService {
1414

1515

16-
def grailsApplication,webService, metadataService, projectService, userService, searchService, activityService, emailService, reportService, documentService
16+
public static final String RCS_CONTRACTED_FUNDING = 'rcsContractedFunding'
17+
def grailsApplication, webService, metadataService, projectService, userService, searchService, activityService, emailService, reportService, documentService
1718
AbnLookupService abnLookupService
1819

1920
private static def APPROVAL_STATUS = ['unpublished', 'pendingApproval', 'published']
@@ -146,6 +147,72 @@ class OrganisationService {
146147
reportService.generateTargetPeriods(targetsReportConfig, owner, targetsConfig.periodLabelFormat)
147148
}
148149

150+
double getRcsFundingForPeriod(Map organisation, String periodEndDate) {
151+
152+
int index = findIndexOfPeriod(organisation, periodEndDate)
153+
def result = 0
154+
if (index >= 0) {
155+
Map rcsFunding = getRcsFunding(organisation)
156+
result = rcsFunding?.costs[index]?.dollar ?: 0
157+
}
158+
result
159+
}
160+
161+
private static int findIndexOfPeriod(Map organisation, String periodEndDate) {
162+
List fundingHeaders = organisation.custom?.details?.funding?.headers
163+
String previousPeriod = ''
164+
fundingHeaders.findIndexOf {
165+
String period = it.data.value
166+
boolean result = previousPeriod < periodEndDate && period >= periodEndDate
167+
previousPeriod = period
168+
result
169+
}
170+
171+
}
172+
173+
/** Returns the funding row used to collect RCS funding data */
174+
private static Map getRcsFunding(Map organisation) {
175+
// The funding recorded for an organisation is specific to RCS reporting.
176+
// Instead of being a "funding per financial year" it is a annually revised total funding amount.
177+
// This is used in calculations alongside data reported in the RCS report.
178+
List fundingRows = organisation.custom?.details?.funding?.rows
179+
fundingRows?.find{it.shortLabel == RCS_CONTRACTED_FUNDING }
180+
181+
}
182+
183+
void checkAndUpdateFundingTotal(Map organisation) {
184+
185+
String today = DateUtils.formatAsISOStringNoMillis(new Date())
186+
187+
Map rcsFunding = getRcsFunding(organisation)
188+
if (!rcsFunding) {
189+
return
190+
}
191+
double funding = 0
192+
int index = findIndexOfPeriod(organisation, today)
193+
194+
while (index >= 0 && funding == 0) {
195+
def fundingStr = rcsFunding.costs[index]?.dollar
196+
if (fundingStr) {
197+
try {
198+
funding = Double.parseDouble(fundingStr)
199+
} catch (NumberFormatException e) {
200+
log.error("Error parsing funding amount for organisation ${organisation.organisationId} at index $index")
201+
}
202+
}
203+
index--
204+
205+
}
206+
207+
if (funding != rcsFunding.rowTotal) {
208+
rcsFunding.rowTotal = funding
209+
organisation.custom.details.funding.overallTotal = rcsFunding.rowTotal
210+
log.info("Updating the funding information for organisation ${organisation.organisationId} to $funding")
211+
update(organisation.organisationId, organisation.custom)
212+
}
213+
214+
}
215+
149216

150217
private void regenerateOrganisationReports(Map organisation, List<String> reportCategories = null) {
151218

grails-app/views/organisation/_funding.gsp

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
<div class="funding">
33

44
<label><b>Funding</b><fc:iconHelp title="Funding">${budgetHelpText?:"Enter the total value of contracts at the date of the review"}</fc:iconHelp></label>
5-
<g:if test="${explanation}">
6-
<p>${explanation}</p>
7-
</g:if>
5+
<p>The 'Current funding' in the table below is used to calculate the Indigenous supply chain performance metric on the dashboard and is determined by the most recent non-zero funding amount starting from the current year.
6+
Future funding amounts will not affect the current funding.</p>
7+
88
<table class="table">
99
<thead>
1010
<tr>
11+
<th class="budget-amount">
12+
Current funding<fc:iconHelp>The current funding is used to calculate the Indigenous supply chain performance metric on the dashboard and is determined by the most recent non-zero funding amount starting from the current year</fc:iconHelp>
13+
</th>
1114
<!-- ko foreach: headers -->
1215
<th class="budget-amount"><div data-bind="text:data.label"></div>$</th>
1316
<!-- /ko -->
@@ -16,6 +19,9 @@
1619
</thead>
1720
<tbody data-bind="foreach : rows">
1821
<tr>
22+
<td class="budget-amount">
23+
<input type="number" readonly class="form-control form-control-sm" data-bind="value: rowTotal">
24+
</td>
1925
<!-- ko foreach: costs -->
2026
<td class="budget-amount">
2127
<input type="number" class="form-control form-control-sm" data-bind="value: dollar, numeric: $root.number, enable: $parents[1].isEditable" data-validation-engine="validate[custom[number],min[0]"/>

grails-app/views/organisation/_serviceTargets.gsp

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<!-- ko with:reportingTargetsAndFunding() -->
22
<h4>${title ?: "Organisation targets"}</h4>
3+
<p>The overall targets in the table below is calculated as the average of every non-blank target in the table. If future year targets are entered in to the table, they will be included in the overall target calculation.</p>
4+
<p>The overall target is display on the Organisation dashboard tab.</p>
35
<!-- ko with: services -->
46
<table class="table service-targets">
57
<thead>

grails-app/views/organisation/index.gsp

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
returnTo: '${g.createLink(action:'index', id:"${organisation.organisationId}")}',
4646
dashboardCategoryUrl: "${g.createLink(controller: 'report', action: 'activityOutputs', params: [fq:'organisationFacet:'+organisation.name])}",
4747
reportOwner: {organisationId:'${organisation.organisationId}'},
48+
i18nURL: "${g.createLink(controller: 'home', action: 'i18n')}",
4849
projects : <fc:modelAsJavascript model="${organisation.projects}"/>
4950
};
5051
</script>

src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy

+2-9
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,9 @@ class RegionalCapacityServicesReportLifecycleListener extends ReportLifecycleLis
2424
[periodTargets:periodTargets, totalContractValue:funding, reportedFundingExcludingThisReport:reportedFundingExcludingThisReport]
2525
}
2626

27-
private static def getFundingForPeriod(Map organisation, Map report) {
27+
private double getFundingForPeriod(Map organisation, Map report) {
2828
String endDate = report.toDate
29-
String previousPeriod = ''
30-
def index = organisation.custom?.details?.funding?.headers?.findIndexOf {
31-
String period = it.data.value
32-
boolean result = previousPeriod < endDate && period >= endDate
33-
previousPeriod = period
34-
result
35-
}
36-
index >= 0 ? organisation.custom?.details?.funding?.rows[0].costs[index].dollar : 0
29+
organisationService.getRcsFundingForPeriod(organisation, endDate)
3730

3831
}
3932

src/main/scripts/releases/4.2/updateIPPRSScores.js

+31-4
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,27 @@ var scores = [
1414
type: "filter"
1515
},
1616
"childAggregations": [{
17-
"property": "data.workforcePerformancePercentage",
18-
"type": "AVERAGE"
17+
"expression":"totalFirstNationsFteWorkforce/totalOrganisationFteWorkforce*100",
18+
"defaultValue": 0,
19+
"childAggregations": [
20+
{
21+
"label":"totalFirstNationsFteWorkforce",
22+
"property":"data.organisationFteIndigenousWorkforce",
23+
"type":"SUM"
24+
},
25+
{
26+
"label":"totalOrganisationFteWorkforce",
27+
"property":"data.organisationFteWorkforce",
28+
"type":"SUM"
29+
}
30+
]
1931
}]
2032
},
2133
"description": "",
2234
"displayType": "",
2335
"entity": "Activity",
2436
"entityTypes": [],
37+
"units":"percentage",
2538
"isOutputTarget": true,
2639
"label": "Indigenous workforce performance (% of Indigenous FTE achieved to date/% FTE target for Indigenous employment to date)",
2740
"outputType": "Regional capacity services - reporting",
@@ -38,14 +51,28 @@ var scores = [
3851
type: "filter"
3952
},
4053
"childAggregations": [{
41-
"property": "data.supplyChainPerformancePercentage",
42-
"type": "SUM"
54+
"expression":"totalFirstNationsProcurement/currentTotalProcurement*100",
55+
"defaultValue": 0,
56+
"childAggregations": [
57+
{
58+
"label": "totalFirstNationsProcurement",
59+
"property": "data.servicesContractedValueFirstNations",
60+
"type": "SUM"
61+
},
62+
{
63+
"label": "currentTotalProcurement",
64+
"property": "activity.organisation.custom.details.funding.overallTotal",
65+
"type": "DISTINCT_SUM",
66+
"keyProperty": "activity.organisation.organisationId"
67+
}
68+
]
4369
}]
4470
},
4571
"description": "",
4672
"displayType": "",
4773
"entity": "Activity",
4874
"entityTypes": [],
75+
"units": "$",
4976
"isOutputTarget": true,
5077
"label": "Indigenous supply chain performance (% of procurement from Indigenous suppliers achieved to date/% procurement target of procurement from Indigenous suppliers at end of Deed period)",
5178
"outputType": "Regional capacity services - reporting",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
load('../../utils/audit.js');
2+
const adminUserId = 'system';
3+
let organisations = db.organisation.find({'custom.details.funding':{$exists:true}});
4+
while (organisations.hasNext()) {
5+
let organisation = organisations.next();
6+
let funding = organisation.custom.details.funding;
7+
8+
if (!funding) {
9+
continue;
10+
}
11+
if (funding.rows.length != 1) {
12+
print("Organisation " + organisation.organisationId + " has " + funding.rows.length + " funding rows");
13+
}
14+
15+
const metadata = {
16+
type: 'periodicallyRevisedTotal',
17+
shortLabel: 'rcsContractedFunding',
18+
description: 'RCS Contracted Funding'
19+
};
20+
21+
if (funding.rows[0].type != metadata.type || funding.rows[0].shortLabel != metadata.shortLabel || funding.rows[0].description != metadata.description) {
22+
print("Updating funding metadata for organisation " + organisation.organisationId+", "+organisation.name);
23+
24+
funding.rows[0].type = metadata.type;
25+
funding.rows[0].shortLabel = metadata.shortLabel;
26+
funding.rows[0].description = metadata.description;
27+
db.organisation.replaceOne({organisationId: organisation.organisationId}, organisation);
28+
audit(organisation, organisation.organisationId, 'au.org.ala.ecodata.Organisation', adminUserId);
29+
}
30+
}

0 commit comments

Comments
 (0)