Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep RCS funding totals up to date #3429 #3445

Merged
merged 1 commit into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions grails-app/assets/javascripts/budget.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
var BudgetConstants = {
PERIODICALLY_REVISED_TOTAL : 'periodicallyRevisedTotal',
PERIODIC_TOTAL : 'perPeriodBreakdown'
};

function BudgetViewModel(o, period) {
var self = this;
if (!o) o = {};
Expand Down Expand Up @@ -71,11 +76,15 @@ function BudgetTotalViewModel(rows, index) {

function BudgetRowViewModel(o, period) {
var self = this;


if (!o) o = {};
if (!o.activities || !_.isArray(o.activities)) o.activities = [];
self.shortLabel = ko.observable(o.shortLabel);
self.description = ko.observable(o.description);
self.activities = ko.observableArray(o.activities);
self.type = o.type || BudgetConstants.PERIODIC_TOTAL;


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

self.rowTotal = ko.computed(function () {
var total = 0.0;
ko.utils.arrayForEach(this.costs(), function (cost) {
if (cost.dollar())
total += parseFloat(cost.dollar());
});
// The Periodically Revised Total is a special case used by the IPPRS system whereby each year they
// revise the total contract value. For this case, the rowTotal is determined by starting in the
// current year and working backwards until a non-zero value is found.
if (self.type === BudgetConstants.PERIODICALLY_REVISED_TOTAL) {
var currentDateString = convertToIsoDate(new Date());
var i = 0;

// Find the current period.
while (i<period.length-1 && period[i].value <= currentDateString) {
i++;
}
total = parseFloat(self.costs()[i].dollar());
while (i>0 && (isNaN(total) || total == 0)) {
i--;
total = parseFloat(self.costs()[i].dollar());
}
}
else { //self.type === PERIODIC_TOTAL is the default - i.e. the default behaviour is to sum the columns
ko.utils.arrayForEach(this.costs(), function (cost) {
if (cost.dollar())
total += parseFloat(cost.dollar());
});
}

return total;
}, this).extend({currency: {}});
};
Expand Down
12 changes: 12 additions & 0 deletions grails-app/assets/javascripts/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ function OrganisationDetailsViewModel(o, organisation, budgetHeaders, allService
targets = o.services && o.services.targets || [];
self.areTargetsAndFundingEditable = config.areTargetsAndFundingEditable;
self.services = new OrganisationServicesViewModel(serviceIds, config.services, targets, budgetHeaders, {areTargetsEditable:config.areTargetsAndFundingEditable});

if (!o.funding) {
o.funding = {
rows: [
{
type:BudgetConstants.PERIODICALLY_REVISED_TOTAL,
shortLabel: 'rcsContractedFunding',
description: 'RCS Contracted Funding'
}
]
}
}
self.funding = new BudgetViewModel(o.funding, budgetHeaders);
self.funding.isEditable = config.areTargetsAndFundingEditable;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ class OrganisationController {
dashboardData = organisationService.scoresForOrganisation(organisation, scores?.collect{it.scoreId}, !hasEditorAccess)
}
boolean showTargets = userService.userIsSiteAdmin() && services && targetPeriods
// This call is used to ensure the organisation funding total is kept up to date as the algorithm
// for selecting the current total is based on the current date. The funding total is used when
// calculating data for the dashboard.
if (showTargets) {
organisationService.checkAndUpdateFundingTotal(organisation)
}
boolean targetsEditable = userService.userIsAlaOrFcAdmin()
List reportOrder = null
if (reportingVisible) {
Expand All @@ -100,7 +106,7 @@ class OrganisationController {
}
}

boolean showEditAnnoucements = organisation.projects?.find{Status.isActive(it.status)}
boolean showEditAnnouncements = organisation.projects?.find{Status.isActive(it.status)}

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

Expand All @@ -114,7 +120,7 @@ class OrganisationController {
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],
sites : [label: 'Sites', visible: reportingVisible, type: 'tab', stopBinding:true, projectCount:organisation.projects?.size()?:0, showShapefileDownload:adminVisible],
dashboard : [label: 'Dashboard', visible: reportingVisible, stopBinding:true, type: 'tab', template:'dashboard', reports:dashboardReports, dashboardData:dashboardData],
admin : [label: 'Admin', visible: adminVisible, type: 'tab', template:'admin', showEditAnnoucements:showEditAnnoucements, availableReportCategories:availableReportCategories, targetPeriods:targetPeriods, services: services, showTargets:showTargets, targetsEditable:targetsEditable]]
admin : [label: 'Admin', visible: adminVisible, type: 'tab', template:'admin', showEditAnnoucements:showEditAnnouncements, availableReportCategories:availableReportCategories, targetPeriods:targetPeriods, services: services, showTargets:showTargets, targetsEditable:targetsEditable]]

}

Expand Down
69 changes: 68 additions & 1 deletion grails-app/services/au/org/ala/merit/OrganisationService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import org.joda.time.Period
class OrganisationService {


def grailsApplication,webService, metadataService, projectService, userService, searchService, activityService, emailService, reportService, documentService
public static final String RCS_CONTRACTED_FUNDING = 'rcsContractedFunding'
def grailsApplication, webService, metadataService, projectService, userService, searchService, activityService, emailService, reportService, documentService
AbnLookupService abnLookupService

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

double getRcsFundingForPeriod(Map organisation, String periodEndDate) {

int index = findIndexOfPeriod(organisation, periodEndDate)
def result = 0
if (index >= 0) {
Map rcsFunding = getRcsFunding(organisation)
result = rcsFunding?.costs[index]?.dollar ?: 0
}
result
}

private static int findIndexOfPeriod(Map organisation, String periodEndDate) {
List fundingHeaders = organisation.custom?.details?.funding?.headers
String previousPeriod = ''
fundingHeaders.findIndexOf {
String period = it.data.value
boolean result = previousPeriod < periodEndDate && period >= periodEndDate
previousPeriod = period
result
}

}

/** Returns the funding row used to collect RCS funding data */
private static Map getRcsFunding(Map organisation) {
// The funding recorded for an organisation is specific to RCS reporting.
// Instead of being a "funding per financial year" it is a annually revised total funding amount.
// This is used in calculations alongside data reported in the RCS report.
List fundingRows = organisation.custom?.details?.funding?.rows
fundingRows?.find{it.shortLabel == RCS_CONTRACTED_FUNDING }

}

void checkAndUpdateFundingTotal(Map organisation) {

String today = DateUtils.formatAsISOStringNoMillis(new Date())

Map rcsFunding = getRcsFunding(organisation)
if (!rcsFunding) {
return
}
double funding = 0
int index = findIndexOfPeriod(organisation, today)

while (index >= 0 && funding == 0) {
def fundingStr = rcsFunding.costs[index]?.dollar
if (fundingStr) {
try {
funding = Double.parseDouble(fundingStr)
} catch (NumberFormatException e) {
log.error("Error parsing funding amount for organisation ${organisation.organisationId} at index $index")
}
}
index--

}

if (funding != rcsFunding.rowTotal) {
rcsFunding.rowTotal = funding
organisation.custom.details.funding.overallTotal = rcsFunding.rowTotal
log.info("Updating the funding information for organisation ${organisation.organisationId} to $funding")
update(organisation.organisationId, organisation.custom)
}

}


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

Expand Down
12 changes: 9 additions & 3 deletions grails-app/views/organisation/_funding.gsp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
<div class="funding">

<label><b>Funding</b><fc:iconHelp title="Funding">${budgetHelpText?:"Enter the total value of contracts at the date of the review"}</fc:iconHelp></label>
<g:if test="${explanation}">
<p>${explanation}</p>
</g:if>
<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.
Future funding amounts will not affect the current funding.</p>

<table class="table">
<thead>
<tr>
<th class="budget-amount">
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>
</th>
<!-- ko foreach: headers -->
<th class="budget-amount"><div data-bind="text:data.label"></div>$</th>
<!-- /ko -->
Expand All @@ -16,6 +19,9 @@
</thead>
<tbody data-bind="foreach : rows">
<tr>
<td class="budget-amount">
<input type="number" readonly class="form-control form-control-sm" data-bind="value: rowTotal">
</td>
<!-- ko foreach: costs -->
<td class="budget-amount">
<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]"/>
Expand Down
2 changes: 2 additions & 0 deletions grails-app/views/organisation/_serviceTargets.gsp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<!-- ko with:reportingTargetsAndFunding() -->
<h4>${title ?: "Organisation targets"}</h4>
<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>
<p>The overall target is display on the Organisation dashboard tab.</p>
<!-- ko with: services -->
<table class="table service-targets">
<thead>
Expand Down
1 change: 1 addition & 0 deletions grails-app/views/organisation/index.gsp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
returnTo: '${g.createLink(action:'index', id:"${organisation.organisationId}")}',
dashboardCategoryUrl: "${g.createLink(controller: 'report', action: 'activityOutputs', params: [fq:'organisationFacet:'+organisation.name])}",
reportOwner: {organisationId:'${organisation.organisationId}'},
i18nURL: "${g.createLink(controller: 'home', action: 'i18n')}",
projects : <fc:modelAsJavascript model="${organisation.projects}"/>
};
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,9 @@ class RegionalCapacityServicesReportLifecycleListener extends ReportLifecycleLis
[periodTargets:periodTargets, totalContractValue:funding, reportedFundingExcludingThisReport:reportedFundingExcludingThisReport]
}

private static def getFundingForPeriod(Map organisation, Map report) {
private double getFundingForPeriod(Map organisation, Map report) {
String endDate = report.toDate
String previousPeriod = ''
def index = organisation.custom?.details?.funding?.headers?.findIndexOf {
String period = it.data.value
boolean result = previousPeriod < endDate && period >= endDate
previousPeriod = period
result
}
index >= 0 ? organisation.custom?.details?.funding?.rows[0].costs[index].dollar : 0
organisationService.getRcsFundingForPeriod(organisation, endDate)

}

Expand Down
35 changes: 31 additions & 4 deletions src/main/scripts/releases/4.2/updateIPPRSScores.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,27 @@ var scores = [
type: "filter"
},
"childAggregations": [{
"property": "data.workforcePerformancePercentage",
"type": "AVERAGE"
"expression":"totalFirstNationsFteWorkforce/totalOrganisationFteWorkforce*100",
"defaultValue": 0,
"childAggregations": [
{
"label":"totalFirstNationsFteWorkforce",
"property":"data.organisationFteIndigenousWorkforce",
"type":"SUM"
},
{
"label":"totalOrganisationFteWorkforce",
"property":"data.organisationFteWorkforce",
"type":"SUM"
}
]
}]
},
"description": "",
"displayType": "",
"entity": "Activity",
"entityTypes": [],
"units":"percentage",
"isOutputTarget": true,
"label": "Indigenous workforce performance (% of Indigenous FTE achieved to date/% FTE target for Indigenous employment to date)",
"outputType": "Regional capacity services - reporting",
Expand All @@ -38,14 +51,28 @@ var scores = [
type: "filter"
},
"childAggregations": [{
"property": "data.supplyChainPerformancePercentage",
"type": "SUM"
"expression":"totalFirstNationsProcurement/currentTotalProcurement*100",
"defaultValue": 0,
"childAggregations": [
{
"label": "totalFirstNationsProcurement",
"property": "data.servicesContractedValueFirstNations",
"type": "SUM"
},
{
"label": "currentTotalProcurement",
"property": "activity.organisation.custom.details.funding.overallTotal",
"type": "DISTINCT_SUM",
"keyProperty": "activity.organisation.organisationId"
}
]
}]
},
"description": "",
"displayType": "",
"entity": "Activity",
"entityTypes": [],
"units": "$",
"isOutputTarget": true,
"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)",
"outputType": "Regional capacity services - reporting",
Expand Down
30 changes: 30 additions & 0 deletions src/main/scripts/releases/4.2/updateOrganisationFundingMetadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
load('../../utils/audit.js');
const adminUserId = 'system';
let organisations = db.organisation.find({'custom.details.funding':{$exists:true}});
while (organisations.hasNext()) {
let organisation = organisations.next();
let funding = organisation.custom.details.funding;

if (!funding) {
continue;
}
if (funding.rows.length != 1) {
print("Organisation " + organisation.organisationId + " has " + funding.rows.length + " funding rows");
}

const metadata = {
type: 'periodicallyRevisedTotal',
shortLabel: 'rcsContractedFunding',
description: 'RCS Contracted Funding'
};

if (funding.rows[0].type != metadata.type || funding.rows[0].shortLabel != metadata.shortLabel || funding.rows[0].description != metadata.description) {
print("Updating funding metadata for organisation " + organisation.organisationId+", "+organisation.name);

funding.rows[0].type = metadata.type;
funding.rows[0].shortLabel = metadata.shortLabel;
funding.rows[0].description = metadata.description;
db.organisation.replaceOne({organisationId: organisation.organisationId}, organisation);
audit(organisation, organisation.organisationId, 'au.org.ala.ecodata.Organisation', adminUserId);
}
}
Loading