From aafa04863f5e2af038a202eae07ffda70ab48f03 Mon Sep 17 00:00:00 2001
From: temi <temi.varghese@csiro.au>
Date: Fri, 22 Nov 2024 16:38:35 +1100
Subject: [PATCH 01/30] #3369 added service targets to organisation

---
 .../javascripts/organisation-manifest.js      |   2 +
 grails-app/assets/javascripts/organisation.js |  55 ++++
 .../assets/javascripts/organisationService.js |  36 ++
 grails-app/assets/javascripts/services.js     | 309 ++++++++++++++++++
 .../assets/stylesheets/organisation.css       |  29 ++
 .../ala/merit/OrganisationController.groovy   |   3 +-
 .../org/ala/merit/OrganisationService.groovy  |  16 +-
 grails-app/views/organisation/_admin.gsp      |   6 +-
 .../views/organisation/_serviceTargets.gsp    |  81 +++++
 grails-app/views/organisation/index.gsp       |   4 +-
 10 files changed, 535 insertions(+), 6 deletions(-)
 create mode 100644 grails-app/assets/javascripts/organisationService.js
 create mode 100644 grails-app/assets/javascripts/services.js
 create mode 100644 grails-app/views/organisation/_serviceTargets.gsp

diff --git a/grails-app/assets/javascripts/organisation-manifest.js b/grails-app/assets/javascripts/organisation-manifest.js
index 868bce68d..5a86c776e 100644
--- a/grails-app/assets/javascripts/organisation-manifest.js
+++ b/grails-app/assets/javascripts/organisation-manifest.js
@@ -6,4 +6,6 @@
 //= require sites.js
 //= require reporting.js
 //= require document.js
+//= require organisationService.js
+//= require services.js
 //= require organisation.js
diff --git a/grails-app/assets/javascripts/organisation.js b/grails-app/assets/javascripts/organisation.js
index 9c35c2de1..4871cee15 100644
--- a/grails-app/assets/javascripts/organisation.js
+++ b/grails-app/assets/javascripts/organisation.js
@@ -548,6 +548,8 @@ OrganisationPageViewModel = function (props, options) {
             }
         }
     };
+    var organisationService = new OrganisationService(options);
+    self.periods = organisationService.getBudgetHeaders();
 
     self.initialise = function() {
         $.fn.dataTable.moment( 'dd-MM-yyyy' );
@@ -634,6 +636,32 @@ OrganisationPageViewModel = function (props, options) {
     self.reportingEnabled = ko.observable();
     self.selectedOrganisationReportCategories = ko.observableArray();
 
+    // List of service / target measure
+    self.allTargetMeasures = [];
+    var services = options.services || [];
+    for (var i=0; i<services.length; i++) {
+        if (services[i].scores) {
+            for (var j=0; j<services[i].scores.length; j++) {
+                self.allTargetMeasures.push( {
+                    label:services[i].name+' - '+services[i].scores[j].label,
+                    serviceId:services[i].id,
+                    scoreId:services[i].scores[j].scoreId,
+                    service:services[i],
+                    score:services[i].scores[j],
+                    value:services[i].scores[j].scoreId
+                });
+            }
+        }
+    }
+
+    self.allTargetMeasures = _.sortBy(self.allTargetMeasures, 'label');
+    var propDetails = props && props.custom && props.custom.details || {};
+    self.selectedTargetMeasures = ko.observableArray();
+    var details = new DetailsViewModel(propDetails, props, self.periods, self.allTargetMeasures, options);
+    updatedTargetMeasures(details);
+    self.reportingTargets = ko.observable(details);
+    self.isProjectDetailsLocked = ko.observable(false);
+
     var setStartAndEndDateDefaults = function() {
         var currentConfig = parsedConfig();
         if (!currentConfig || !currentConfig.organisationReports || currentConfig.organisationReports.length == 0) {
@@ -692,6 +720,26 @@ OrganisationPageViewModel = function (props, options) {
         reportService.regenerateReports(data,options.regenerateOrganisationReportsUrl);
     };
 
+    function updatedTargetMeasures (details) {
+        var reportingTargets = details,
+            selectedServices = reportingTargets.services.services(),
+            allServices = self.allTargetMeasures;
+
+        _.each(allServices, function (service) {
+            var found = _.find(selectedServices, function (selectedService) {
+                return selectedService.scoreId() === service.scoreId;
+            });
+
+            if (!found) {
+                reportingTargets.services.addServiceTarget(service);
+            }
+        })
+    }
+
+    self.attachValidation = function() {
+        $("#organisation-targets").validationEngine('attach', {validationAttribute: "data-validation-engine"});
+    };
+
     self.saveOrganisationConfiguration = function() {
         var currentConfig = parsedConfig();
         if (!currentConfig) {
@@ -713,6 +761,13 @@ OrganisationPageViewModel = function (props, options) {
         return saveOrganisation(json);
     };
 
+    self.saveCustomFields = function() {
+        if ($("#organisation-targets form").validationEngine('validate')) {
+            var json = JSON.parse(self.reportingTargets().modelAsJSON());
+            return saveOrganisation(json);
+        }
+    };
+
 
     var saveOrganisation = function(json) {
         return $.ajax({
diff --git a/grails-app/assets/javascripts/organisationService.js b/grails-app/assets/javascripts/organisationService.js
new file mode 100644
index 000000000..b3cd88cf5
--- /dev/null
+++ b/grails-app/assets/javascripts/organisationService.js
@@ -0,0 +1,36 @@
+function OrganisationService(organisation, options) {
+    var self = this;
+    var defaults = {
+        excludeFinancialYearData : false
+    };
+
+    var config = _.defaults(options, defaults);
+
+    self.getBudgetHeaders = function() {
+        if (config.excludeFinancialYearData) {
+            return []; // Return a single period header for the organisation
+        }
+        var headers = [];
+        var startYr = moment(organisation.plannedStartDate).format('YYYY');
+        var endYr = moment(organisation.plannedEndDate).format('YYYY');
+        var startMonth = moment(organisation.plannedStartDate).format('M');
+        var endMonth = moment(organisation.plannedEndDate).format('M');
+
+        //Is startYr is between jan to june?
+        if(startMonth >= 1 &&  startMonth <= 6 ){
+            startYr--;
+        }
+
+        //Is the end year is between july to dec?
+        if(endMonth >= 7 &&  endMonth <= 12 ){
+            endYr++;
+        }
+
+        var count = endYr - startYr;
+        for (i = 0; i < count; i++){
+            headers.push(startYr + '/' + ++startYr);
+        }
+
+        return headers;
+    };
+}
\ No newline at end of file
diff --git a/grails-app/assets/javascripts/services.js b/grails-app/assets/javascripts/services.js
new file mode 100644
index 000000000..34b867392
--- /dev/null
+++ b/grails-app/assets/javascripts/services.js
@@ -0,0 +1,309 @@
+function DetailsViewModel(o, organisation, budgetHeaders, allServices, config) {
+    var self = this;
+    var period = budgetHeaders,
+        serviceIds = o.services && o.services.serviceIds || [],
+        targets = o.services && o.services.targets || [];
+    self.services = new ServicesViewModel(serviceIds, config.services, targets, budgetHeaders);
+    function clearHiddenFields(jsData) {
+
+    };
+
+    self.modelAsJSON = function () {
+        var tmp = {};
+        tmp.details = ko.mapping.toJS(self);
+        var jsData = {"custom": tmp};
+        clearHiddenFields(jsData);
+
+        var json = JSON.stringify(jsData, function (key, value) {
+            return value === undefined ? "" : value;
+        });
+        return json;
+    };
+
+    if (config.locked) {
+        autoSaveConfig.lockedEntity = organisation.organisationId;
+    }
+};
+
+/**
+ * The view model responsible for managing the selection of project services and their output targets.
+ *
+ * @param serviceIds Array of the ids of the current services being used by the organisation
+ * @param allServices Array containing the full list of available services
+ * @param outputTargets The current organisation targets
+ * @param periods An array of periods, each of which require a target to be set
+ */
+function ServicesViewModel(serviceIds, allServices, outputTargets, periods) {
+    var self = this,
+        OPERATION_SUM = "SUM",
+        OPERATION_AVG = "AVG",
+        operation = OPERATION_AVG;
+
+    self.isProjectDetailsLocked = ko.observable(false);
+
+    allServices = _.sortBy(allServices || [], function (service) {
+        return service.name
+    });
+
+    outputTargets = outputTargets || [];
+
+    /**
+     * This function is invoked when a selected service is changed or
+     * a service added or deleted.
+     * It keeps the list of selected service names up to date for use
+     * by the budget table.
+     */
+    function updateSelectedServices() {
+        var array =  _.map(self.services(), function(serviceTarget) {
+            var service = serviceTarget.service();
+            if (service) {
+                return service.name;
+            }
+        });
+        array = _.filter(array, function(val) { return val; });
+        array = _.unique(array);
+        self.selectedServices(array);
+    }
+    var ServiceTarget = function (service, score) {
+        var target = this,
+            subscribed = false;
+
+        target.serviceId = ko.observable(service ? service.id : null);
+        target.scoreId = ko.observable(score ? score.scoreId : null);
+
+        target.target = ko.observable();
+        target.targetDate = ko.observable().extend({simpleDate:false});
+
+        target.periodTargets = _.map(periods, function (period) {
+            return {period: period, target: ko.observable(0)};
+        });
+
+        function evaluateAndAssignAverage() {
+            if (periods.length === 0)
+                return;
+
+            var sum = sumOfPeriodTargets();
+            var avg = sum / periods.length;
+            target.target(avg);
+        }
+        function sumOfPeriodTargets() {
+            var sum = 0;
+            _.each(target.periodTargets, function (periodTarget) {
+                if (Number(periodTarget.target())) {
+                    sum += Number(periodTarget.target());
+                }
+            });
+
+            return sum;
+        }
+
+        target.minimumTargetsValid = ko.pureComputed(function () {
+            var sum = sumOfPeriodTargets();
+            return sum <= (target.target() || 0);
+        });
+
+        target.updateTargets = function () {
+
+            // Don't auto-update the target if one has already been specified.
+            if (target.target()) {
+                return;
+            }
+            var currentTarget = _.find(outputTargets, function (outputTarget) {
+                return target.scoreId() == outputTarget.scoreId;
+            });
+            _.each(periods, function (period, i) {
+                var periodTarget = 0;
+                if (currentTarget) {
+                    var currentPeriodTarget = _.find(currentTarget.periodTargets || [], function (periodTarget) {
+                        return periodTarget.period == period;
+                    }) || {};
+                    periodTarget = currentPeriodTarget.target;
+                }
+                target.periodTargets[i].target(periodTarget || 0);
+                // subscribe after setting the initial value
+                !subscribed && target.periodTargets[i].target.subscribe(evaluateAndAssignAverage);
+            });
+            target.target(currentTarget ? currentTarget.target || 0 : 0);
+            target.targetDate(currentTarget ? currentTarget.targetDate : '');
+            // prevent multiple subscriptions to period targets observable
+            subscribed = true;
+        };
+
+        target.toJSON = function () {
+            return {
+                target: target.target(),
+                targetDate: target.targetDate(),
+                scoreId: target.scoreId(),
+                periodTargets: ko.toJS(target.periodTargets)
+            };
+        };
+
+        target.service = function () {
+            return _.find(allServices, function (service) {
+                return service.id == target.serviceId();
+            })
+        };
+
+        target.score = function () {
+            var score = null;
+            var service = target.service();
+            if (service) {
+                score = _.find(service.scores, function (score) {
+                    return score.scoreId == target.scoreId();
+                });
+            }
+            return score;
+        };
+
+        target.selectableScores = ko.pureComputed(function () {
+            if (!target.serviceId()) {
+                return [];
+            }
+            var availableScores = self.availableScoresForService(target.service());
+            if (target.scoreId()) {
+                availableScores.push(target.score());
+            }
+
+            return _.sortBy(availableScores, function (score) {
+                return score.label
+            });
+        });
+        target.selectableServices = ko.pureComputed(function () {
+            var services = self.availableServices();
+            if (target.serviceId()) {
+                var found = _.find(services, function (service) {
+                    return service.id == target.serviceId();
+                });
+                if (!found) {
+                    services.push(target.service());
+                }
+
+            }
+            return services;
+
+        });
+
+        target.serviceId.subscribe(function () {
+            target.scoreId(null);
+            updateSelectedServices();
+        });
+
+        target.scoreId.subscribe(function () {
+            target.updateTargets();
+        });
+
+        target.updateTargets();
+    };
+
+    self.periods = periods;
+
+    self.services = ko.observableArray();
+    self.addService = function () {
+        self.services.push(new ServiceTarget());
+    };
+
+    /**
+     * Method to programatically add a pre-populated service target - used for the MERI plan load.
+     * @param serviceTarget example:
+     *  {
+     serviceId:1,
+     scoreId:1,
+     target:100,
+     periodTargets:[
+     {period:'2018/2019', target:1},
+     {period:'2019/2020', target:2},
+     {period:'2020/2021', target:2}
+     ]
+     }
+     * @returns {ServiceTarget}
+     */
+    self.addServiceTarget = function(serviceTarget) {
+        var serviceTargetRow = new ServiceTarget();
+        serviceTargetRow.serviceId(serviceTarget.serviceId);
+        serviceTargetRow.scoreId(serviceTarget.scoreId);
+        serviceTargetRow.target(serviceTarget.target);
+        _.each(periods || [], function(period) {
+
+            var periodTarget = _.find(serviceTargetRow.periodTargets || [], function(pt) {
+                return pt.period == period;
+            });
+            var periodTargetValue = _.find(serviceTarget.periodTargets || [], function(pt) {
+                return pt.period == period;
+            });
+            if (periodTarget && periodTargetValue) {
+                periodTarget.target(periodTargetValue.target);
+            }
+
+        });
+
+        self.services.push(serviceTargetRow);
+        return serviceTargetRow;
+    };
+
+    self.removeService = function (service) {
+        self.services.remove(service);
+    };
+
+
+    self.selectedServices = ko.observableArray();
+    self.services.subscribe(updateSelectedServices);
+
+    /**
+     * Once all of the scores for a service have been assigned targets, don't allow new rows to select that score.
+     */
+    self.availableServices = function () {
+        return _.reject(allServices, function (service) {
+            return self.availableScoresForService(service).length == 0;
+        });
+    };
+
+    self.availableScoresForService = function (service) {
+        if (!service || !service.scores) {
+            return [];
+        }
+        return _.reject(service.scores, function (score) {
+            return _.find(self.services(), function (target) {
+                return target.score() ? target.score().scoreId == score.scoreId : false;
+            })
+        });
+    };
+
+    // Populate the model from existing data.
+    for (var i = 0; i < outputTargets.length; i++) {
+
+        var score = null;
+        var service = _.find(allServices, function (service) {
+            return _.find(service.scores, function (serviceScore) {
+                if (serviceScore.scoreId == outputTargets[i].scoreId) {
+                    score = serviceScore;
+                }
+                return score;
+            })
+        });
+
+        self.services.push(new ServiceTarget(service, score));
+    }
+    // if (!outputTargets || outputTargets.length == 0) {
+    //     self.addService();
+    // }
+    self.outputTargets = function () {
+        var outputTargets = [];
+        _.each(self.services(), function (target) {
+            outputTargets.push(target.toJSON());
+        });
+        return outputTargets;
+    };
+
+    self.toJSON = function () {
+        var serviceIds = _.unique(_.map(self.services(), function (service) {
+            return service.serviceId();
+        }));
+        serviceIds = _.filter(serviceIds, function(id) {
+            return id != null;
+        });
+        return {
+            serviceIds: serviceIds,
+            targets: self.outputTargets()
+        }
+    };
+};
\ No newline at end of file
diff --git a/grails-app/assets/stylesheets/organisation.css b/grails-app/assets/stylesheets/organisation.css
index 459c90d91..923090fde 100644
--- a/grails-app/assets/stylesheets/organisation.css
+++ b/grails-app/assets/stylesheets/organisation.css
@@ -108,3 +108,32 @@ ul.ui-autocomplete {
     background: red;
     color: white;
 }
+
+.service {
+    width: 25%
+}
+
+.service input {
+    width: 100%;
+}
+
+.service-targets td.service {
+    width: 30%;
+}
+.service-targets td.score {
+    width: 40%;
+}
+.service-targets td.target {
+    width: 10%;
+}
+
+.service-targets td.target-date {
+    width:7em;
+}
+.service-targets-view .target-date {
+    min-width:6em;
+}
+.service-targets td.target-date input {
+    width:7em;
+}
+
diff --git a/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy b/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
index 6b88b68ea..5c08c82e0 100644
--- a/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
+++ b/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
@@ -7,8 +7,6 @@ import au.org.ala.merit.command.ViewOrganisationReportCommand
 import au.org.ala.merit.util.ProjectGroupingHelper
 import grails.converters.JSON
 import org.apache.http.HttpStatus
-import org.grails.web.json.JSONObject
-
 /**
  * Extends the plugin OrganisationController to support Green Army project reporting.
  */
@@ -53,6 +51,7 @@ class OrganisationController {
              dashboard: dashboard,
              roles:roles,
              user:user,
+             services: organisationService.findApplicableServices(organisation, metadataService.getProjectServices()),
              isAdmin:orgRole?.role == RoleService.PROJECT_ADMIN_ROLE,
              isGrantManager:orgRole?.role == RoleService.GRANT_MANAGER_ROLE,
              content:content(organisation)]
diff --git a/grails-app/services/au/org/ala/merit/OrganisationService.groovy b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
index 19847623e..bd8b67e93 100644
--- a/grails-app/services/au/org/ala/merit/OrganisationService.groovy
+++ b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
@@ -4,11 +4,9 @@ import au.org.ala.merit.config.EmailTemplate
 import au.org.ala.merit.config.ReportConfig
 import au.org.ala.merit.reports.ReportOwner
 import org.grails.web.json.JSONArray
-import org.grails.web.json.JSONObject
 import org.joda.time.DateTime
 import org.joda.time.DateTimeZone
 import org.joda.time.Period
-
 /**
  * Extends the plugin OrganisationService to provide Green Army reporting capability.
  */
@@ -334,4 +332,18 @@ class OrganisationService {
         scoreIds.collectEntries{ String scoreId ->[(scoreId):result.results?.find{it.scoreId == scoreId}?.result?.result ?: 0]}
     }
 
+
+    /**
+     * Filter services to those supported by organisation.
+     */
+    def findApplicableServices(Map organisation, List allServices) {
+
+        List supportedServices = organisation.config?.organisationReports?.collect{it.activityType}?.findAll{it}
+        List result = allServices
+        if (supportedServices) {
+            result = allServices.findAll{ supportedServices.intersect(it.outputs.formName) }
+        }
+
+        result
+    }
 }
diff --git a/grails-app/views/organisation/_admin.gsp b/grails-app/views/organisation/_admin.gsp
index 558c251d7..d219ecc6a 100644
--- a/grails-app/views/organisation/_admin.gsp
+++ b/grails-app/views/organisation/_admin.gsp
@@ -9,7 +9,7 @@
             <a class="nav-link" data-toggle="pill" href="#reporting-config" id="reporting-config-tab" role="tab">Reporting</a>
             <a class="nav-link" data-toggle="pill" href="#config" id="config-tab" role="tab">Configuration</a>
         </g:if>
-
+        <a class="nav-link" data-toggle="pill" href="#organisation-targets" id="organisation-targets-tab" role="tab">Targets</a>
 
     </div>
 
@@ -153,5 +153,9 @@
             </div>
             <!-- /ko -->
         </g:else>
+        <div id="organisation-targets" class="tab-pane">
+            <h3>Service Targets</h3>
+            <g:render template="/organisation/serviceTargets" model="[services:organisation.services, periods:organisation.periods, showTargetDate:organisation.showTargetDate]"/>
+        </div>
     </div>
 </div>
diff --git a/grails-app/views/organisation/_serviceTargets.gsp b/grails-app/views/organisation/_serviceTargets.gsp
new file mode 100644
index 000000000..b3ce6edda
--- /dev/null
+++ b/grails-app/views/organisation/_serviceTargets.gsp
@@ -0,0 +1,81 @@
+<!-- ko with:reportingTargets() -->
+<h4>${title ?: "Organisation services and minimum targets"}</h4>
+<!-- ko with: services -->
+<form>
+<table class="table service-targets validationEngineContainer">
+    <thead>
+    <tr>
+        <th class="index" rowspan="2"></th>
+        <th class="service required" rowspan="2">${serviceName ?: "Service"}</th>
+        <th class="score required" rowspan="2" style="width: 20px">Target measure</th>
+        <th class="budget-cell required" rowspan="2">Total to be delivered <g:if test="${totalHelpText}"> <fc:iconHelp> ${totalHelpText} </fc:iconHelp></g:if> <g:else><fc:iconHelp html="true">The overall total of Organsiation Services to be delivered during the organisation delivery period.
+            <b>Note: this total is not necessarily the sum of the minimum annual targets set out for the service.</b></fc:iconHelp></g:else> </th>
+        <g:if test="${showTargetDate}">
+            <th class="target-date required" rowspan="2">
+                Delivery date <g:if test="${deliveryHelpText}"> <fc:iconHelp> ${deliveryHelpText} </fc:iconHelp> </g:if>
+            </th>
+        </g:if>
+        <!-- ko if: periods && periods.length -->
+        <th data-bind="attr:{colspan:periods.length+1}">Minimum annual targets <fc:iconHelp>${minHelptext ?:"Specify the minimum total target for each Project Service to be delivered each financial year. Note: the sum of these targets will not necessarily equal the total services to be delivered."}</fc:iconHelp></th>
+        <!-- /ko -->
+    </tr>
+    <tr>
+
+        <!-- ko foreach: periods -->
+        <th class="budget-cell"><div data-bind="text:$data"></div></th>
+        <!-- /ko -->
+    </tr>
+    </thead>
+    <tbody data-bind="foreach : services">
+    <tr>
+        <td class="index"><span data-bind="text:$index()+1"></span></td>
+        <td class="service">
+            <select class="form-control form-control-sm" data-bind="options: selectableServices, optionsText:'name', optionsValue:'id', optionsCaption: 'Please select', value:serviceId, disable: $root.isProjectDetailsLocked()"
+                    data-validation-engine="validate[required]"></select>
+        </td>
+        <td class="score">
+            <select class="form-control form-control-sm" data-bind="options: selectableScores, optionsText:'label', optionsValue:'scoreId', optionsCaption: 'Please select', value:scoreId, disable: $root.isProjectDetailsLocked()"
+                    data-validation-engine="validate[required]"></select>
+        </td>
+        <td class="budget-cell">
+            <input class="form-control form-control-sm" type="number" disabled data-bind="value: target"
+                   data-validation-engine="validate[min[0.01]]"  data-warningmessage="The sum of the minimum targets must be less than or equal to the overall target">
+        </td>
+
+        <g:if test="${showTargetDate}">
+            <td class="target-date">
+                <div class="input-group">
+                    <input class="form-control form-control-sm" data-bind="datepicker:targetDate.date, disable: $root.isProjectDetailsLocked()" type="text" size="16" data-validation-engine="validate[required]">
+                    <div class="input-group-append open-datepicker">
+                        <span class="input-group-text">
+                            <i class="fa fa-th ">&nbsp;</i>
+                        </span>
+                    </div>
+                </div>
+            </td>
+        </g:if>
+
+        <!-- ko foreach: periodTargets -->
+        <td class="budget-cell">
+            <input class="form-control form-control-sm" type="number"
+                   data-bind="value: target, disable: $root.isProjectDetailsLocked()"
+                   data-validation-engine="validate[custom[number],min[0]]"/>
+        </td>
+        <!-- /ko -->
+    </tr>
+    </tbody>
+%{--    <tfoot>--}%
+
+%{--    <tr>--}%
+%{--        <td data-bind="attr:{colspan:periods.length+${showTargetDate ? 6 : 5}}">--}%
+%{--            <button type="button" class="btn btn-sm"--}%
+%{--                    data-bind="disable: $parent.isProjectDetailsLocked(), click: addService">--}%
+%{--                <i class="fa fa-plus"></i> Add a row</button>--}%
+%{--        </td>--}%
+%{--    </tr>--}%
+%{--    </tfoot>--}%
+</table>
+</form>
+<!-- /ko -->
+<!-- /ko -->
+<button class="btn btn-sm btn-primary" data-bind="click: saveCustomFields">Save changes</button>
\ No newline at end of file
diff --git a/grails-app/views/organisation/index.gsp b/grails-app/views/organisation/index.gsp
index cb78a6b6b..6d31d8426 100644
--- a/grails-app/views/organisation/index.gsp
+++ b/grails-app/views/organisation/index.gsp
@@ -44,7 +44,8 @@
             returnTo: '${g.createLink(action:'index', id:"${organisation.organisationId}")}',
             dashboardCategoryUrl: "${g.createLink(controller: 'report', action: 'activityOutputs', params: [fq:'organisationFacet:'+organisation.name])}",
             reportOwner: {organisationId:'${organisation.organisationId}'},
-            projects : <fc:modelAsJavascript model="${organisation.projects}"/>
+            projects : <fc:modelAsJavascript model="${organisation.projects}"/>,
+            services: <fc:modelAsJavascript model="${services}"/>
 
         };
     </script>
@@ -88,6 +89,7 @@
 
         ko.applyBindings(organisationViewModel);
         organisationViewModel.initialise();
+        organisationViewModel.attachValidation();
         $('#loading').hide();
         $('#organisationDetails').show();
     });

From 7fb8879a626e529c3f2846d86a009ffb02228800 Mon Sep 17 00:00:00 2001
From: temi <temi.varghese@csiro.au>
Date: Mon, 25 Nov 2024 17:28:50 +1100
Subject: [PATCH 02/30] #3369 feedback from client for RCS form fixed issue
 with validation triggering

---
 .../other/regionalCapacityServicesReport.json | 20 ++++++-------------
 grails-app/assets/javascripts/organisation.js |  4 ++--
 .../views/organisation/_serviceTargets.gsp    |  2 --
 3 files changed, 8 insertions(+), 18 deletions(-)

diff --git a/forms/other/regionalCapacityServicesReport.json b/forms/other/regionalCapacityServicesReport.json
index 645016b99..b77e6506e 100644
--- a/forms/other/regionalCapacityServicesReport.json
+++ b/forms/other/regionalCapacityServicesReport.json
@@ -150,28 +150,20 @@
           {
             "dataType": "number",
             "name": "organisationFteWorkforce",
-            "description": "Enter total RDP staff (full time equivalent) deployed on Services for the Deed and all Contracts (including non-DCCEEW Contracts) during this reporting period.",
+            "description": "Enter total RDP staff (full time equivalent) deployed on Services for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts) during this reporting period.",
             "validate": [
               {
                 "rule": "required"
               },
               {
                 "rule": "min[0]"
-              },
-              {
-                "param": {
-                  "expression": "organisationFteIndigenousWorkforce",
-                  "type": "computed"
-                },
-                "rule": "min",
-                "message": "numeric value must be higher or equal to the numeric value entered for 'What was your organisation’s full time equivalent Indigenous workforce deployed on the Services this reporting period?'"
               }
             ]
           },
           {
             "dataType": "number",
             "name": "organisationFteIndigenousWorkforce",
-            "description": "Enter total RDP Indigenous staff (full time equivalent) deployed on Services for the Deed and all Contracts (including non-DCCEEW Contracts) during this reporting period.",
+            "description": "Enter total RDP Indigenous staff (full time equivalent) deployed on Services for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts) during this reporting period.",
             "validate": [
               {
                 "rule": "required"
@@ -185,14 +177,14 @@
                   "type": "computed"
                 },
                 "rule": "max",
-                "message": "numeric value must be less than or equal to the numeric value entered for 'What was your organisation’s full time equivalent workforce deployed on the Services this reporting period?'"
+                "message": "Numeric value must be less than or equal to the numeric value entered for 'What was your organisation’s full time equivalent workforce deployed on the Services this reporting period?'"
               }
             ]
           },
           {
             "dataType": "number",
             "name": "servicesContractedValueFirstNations",
-            "description": "Enter total dollar value (GST inclusive) of goods and services contracted to First Nations people/Indigenous enterprises, during this reporting period, for the Deed and all Contracts (including non-DCCEEW Contracts).",
+            "description": "Enter total dollar value (GST inclusive) of goods and services contracted to First Nations people/Indigenous enterprises, during this reporting period, for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
             "validate": [
               {
                 "rule": "required"
@@ -544,7 +536,7 @@
             "items": [
               {
                 "type": "literal",
-                "source": "<b>Q5(c). Please complete workforce and supply chain data (Deed of Standing Offer Clause 38 Indigenous Procurement Policy). Please do not provide personal or commercial-in-confidence information when answering this question. Please retain documentation as evidence to support assurance activities undertaken to verify the reported information.</b>"
+                "source": "<b>Q5(c). Please complete workforce and supply chain data (Deed of Standing Offer Clause 38 Indigenous Procurement Policy). Please do not provide personal or <span style='white-space: nowrap;'>commercial-in-confidence</span> information when answering this question. Please retain documentation as evidence to support assurance activities undertaken to verify the reported information.</b>"
               }
             ]
           },
@@ -577,7 +569,7 @@
                 "css": "span7",
                 "preLabel": "What was the dollar value of Services contracted to First Nations people/Indigenous enterprises during this reporting period?",
                 "source": "servicesContractedValueFirstNations",
-                "type": "currency"
+                "type": "number"
               }
             ]
           },
diff --git a/grails-app/assets/javascripts/organisation.js b/grails-app/assets/javascripts/organisation.js
index 4871cee15..34071310b 100644
--- a/grails-app/assets/javascripts/organisation.js
+++ b/grails-app/assets/javascripts/organisation.js
@@ -737,7 +737,7 @@ OrganisationPageViewModel = function (props, options) {
     }
 
     self.attachValidation = function() {
-        $("#organisation-targets").validationEngine('attach', {validationAttribute: "data-validation-engine"});
+        $("#organisation-targets > table").validationEngine('attach');
     };
 
     self.saveOrganisationConfiguration = function() {
@@ -762,7 +762,7 @@ OrganisationPageViewModel = function (props, options) {
     };
 
     self.saveCustomFields = function() {
-        if ($("#organisation-targets form").validationEngine('validate')) {
+        if ($("#organisation-targets > table").validationEngine('validate')) {
             var json = JSON.parse(self.reportingTargets().modelAsJSON());
             return saveOrganisation(json);
         }
diff --git a/grails-app/views/organisation/_serviceTargets.gsp b/grails-app/views/organisation/_serviceTargets.gsp
index b3ce6edda..d593e65ac 100644
--- a/grails-app/views/organisation/_serviceTargets.gsp
+++ b/grails-app/views/organisation/_serviceTargets.gsp
@@ -1,7 +1,6 @@
 <!-- ko with:reportingTargets() -->
 <h4>${title ?: "Organisation services and minimum targets"}</h4>
 <!-- ko with: services -->
-<form>
 <table class="table service-targets validationEngineContainer">
     <thead>
     <tr>
@@ -75,7 +74,6 @@
 %{--    </tr>--}%
 %{--    </tfoot>--}%
 </table>
-</form>
 <!-- /ko -->
 <!-- /ko -->
 <button class="btn btn-sm btn-primary" data-bind="click: saveCustomFields">Save changes</button>
\ No newline at end of file

From c8cc478c941e3ab188b9ce1cc344048bd727fa29 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Mon, 2 Dec 2024 09:57:23 +1100
Subject: [PATCH 03/30] WIP for org targets #3369

---
 grails-app/assets/javascripts/organisation.js |  2 +-
 grails-app/assets/javascripts/services.js     |  6 +++++-
 .../ala/merit/OrganisationController.groovy   | 13 +++++++++---
 .../org/ala/merit/OrganisationService.groovy  | 20 +++++++++++++++++++
 .../au/org/ala/merit/ReportService.groovy     | 15 ++++++++++++++
 .../views/organisation/_serviceTargets.gsp    |  2 +-
 grails-app/views/organisation/index.gsp       |  2 ++
 7 files changed, 54 insertions(+), 6 deletions(-)

diff --git a/grails-app/assets/javascripts/organisation.js b/grails-app/assets/javascripts/organisation.js
index d0826b8fc..b34198c78 100644
--- a/grails-app/assets/javascripts/organisation.js
+++ b/grails-app/assets/javascripts/organisation.js
@@ -548,7 +548,7 @@ OrganisationPageViewModel = function (props, options) {
         }
     };
     var organisationService = new OrganisationService(options);
-    self.periods = organisationService.getBudgetHeaders();
+    self.periods = options.targetPeriods || [];
 
     self.initialise = function() {
         $.fn.dataTable.moment( 'dd-MM-yyyy' );
diff --git a/grails-app/assets/javascripts/services.js b/grails-app/assets/javascripts/services.js
index 34b867392..20c5b271d 100644
--- a/grails-app/assets/javascripts/services.js
+++ b/grails-app/assets/javascripts/services.js
@@ -75,7 +75,7 @@ function ServicesViewModel(serviceIds, allServices, outputTargets, periods) {
         target.targetDate = ko.observable().extend({simpleDate:false});
 
         target.periodTargets = _.map(periods, function (period) {
-            return {period: period, target: ko.observable(0)};
+            return {period: period.value, target: ko.observable(0)};
         });
 
         function evaluateAndAssignAverage() {
@@ -196,6 +196,10 @@ function ServicesViewModel(serviceIds, allServices, outputTargets, periods) {
     };
 
     self.periods = periods;
+    self.periodLabel = function (period) {
+
+        return period;
+    };
 
     self.services = ko.observableArray();
     self.addService = function () {
diff --git a/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy b/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
index 5c08c82e0..0109f6935 100644
--- a/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
+++ b/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
@@ -26,7 +26,7 @@ class OrganisationController {
     def list() {}
 
     def index(String id) {
-        def organisation = organisationService.get(id, 'all')
+        Map organisation = organisationService.get(id, 'all')
 
         if (!organisation || organisation.error) {
             organisationNotFound(id, organisation)
@@ -60,7 +60,7 @@ class OrganisationController {
 
 
 
-    protected Map content(organisation) {
+    protected Map content(Map organisation) {
 
         def user = userService.getUser()
         def members = userService.getMembersOfOrganisation(organisation.organisationId)
@@ -102,12 +102,13 @@ class OrganisationController {
         List projects = organisation.projects ?: []
         List programGroups = organisation.config?.programGroups ?: []
         Map projectGroups = projectGroupingHelper.groupProjectsByProgram(projects, programGroups, ["organisationId:"+organisation.organisationId], true)
+        List targetPeriods = organisationService.generateTargetPeriods(organisation)
 
         [about     : [label: 'About', visible: true, stopBinding: false, type:'tab', default:!reportingVisible, displayedPrograms:projectGroups.displayedPrograms, servicesDashboard:[visible:true]],
          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:'/shared/dashboard', reports:dashboardReports],
-         admin     : [label: 'Admin', visible: adminVisible, type: 'tab', template:'admin', showEditAnnoucements:showEditAnnoucements, availableReportCategories:availableReportCategories]]
+         admin     : [label: 'Admin', visible: adminVisible, type: 'tab', template:'admin', showEditAnnoucements:showEditAnnoucements, availableReportCategories:availableReportCategories, targetPeriods:targetPeriods]]
 
     }
 
@@ -689,4 +690,10 @@ class OrganisationController {
 
         render result as JSON
     }
+
+    @PreAuthorise(accessLevel = 'admin')
+    def generateTargetPeriods(String id) {
+        List<Map> result = organisationService.generateTargetPeriods(id)
+        render result as JSON
+    }
 }
diff --git a/grails-app/services/au/org/ala/merit/OrganisationService.groovy b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
index bd8b67e93..3cb455599 100644
--- a/grails-app/services/au/org/ala/merit/OrganisationService.groovy
+++ b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
@@ -127,6 +127,26 @@ class OrganisationService {
         regenerateOrganisationReports(organisation, organisationReportCategories)
     }
 
+    List<String> generateTargetPeriods(String id) {
+        Map organisation = get(id)
+        generateTargetPeriods(organisation)
+    }
+
+    List<Map> generateTargetPeriods(Map organisation) {
+        Map targetsConfig = organisation.config?.targets
+        if (!targetsConfig) {
+            log.info("No target configuration defined for organisation ${organisation.organisationId}")
+            return null
+        }
+        ReportConfig targetsReportConfig = new ReportConfig(targetsConfig.periodGenerationConfig)
+        ReportOwner owner = new ReportOwner(
+                id:[organisationId:organisation.organisationId],
+                name:organisation.name
+        )
+        reportService.generateTargetPeriods(targetsReportConfig, owner, targetsConfig.periodLabelFormat)
+    }
+
+
     private void regenerateOrganisationReports(Map organisation, List<String> reportCategories = null) {
 
         ReportOwner owner = new ReportOwner(
diff --git a/grails-app/services/au/org/ala/merit/ReportService.groovy b/grails-app/services/au/org/ala/merit/ReportService.groovy
index ba8fafc32..25b5c5f79 100644
--- a/grails-app/services/au/org/ala/merit/ReportService.groovy
+++ b/grails-app/services/au/org/ala/merit/ReportService.groovy
@@ -143,6 +143,21 @@ class ReportService {
 
     }
 
+    /**
+     * This is to support progress targets using the same
+     * configuration as we use to generate reports such that
+     * the targets can be aligned to reports if required.
+     * (Previously MERIT only supported targets per financial year)
+     */
+    List<Map> generateTargetPeriods(ReportConfig reportConfig, ReportOwner reportOwner, String formatString = null) {
+        List<Map> reports = new ReportGenerator().generateReports(
+                reportConfig, reportOwner, 0, null)
+        Closure dateFormatter = {
+            formatString ? DateUtils.format(DateUtils.parse(it), formatString) : it
+        }
+        reports.collect{[label:dateFormatter(it.toDate), value:it.toDate]}
+    }
+
     boolean needsRegeneration(Map report1, Map report2) {
         return report1.fromDate != report2.fromDate ||
                report1.toDate != report2.toDate ||
diff --git a/grails-app/views/organisation/_serviceTargets.gsp b/grails-app/views/organisation/_serviceTargets.gsp
index d593e65ac..b2b540a81 100644
--- a/grails-app/views/organisation/_serviceTargets.gsp
+++ b/grails-app/views/organisation/_serviceTargets.gsp
@@ -21,7 +21,7 @@
     <tr>
 
         <!-- ko foreach: periods -->
-        <th class="budget-cell"><div data-bind="text:$data"></div></th>
+        <th class="budget-cell"><div data-bind="text:$data.label"></div></th>
         <!-- /ko -->
     </tr>
     </thead>
diff --git a/grails-app/views/organisation/index.gsp b/grails-app/views/organisation/index.gsp
index 6d31d8426..2f57cbd25 100644
--- a/grails-app/views/organisation/index.gsp
+++ b/grails-app/views/organisation/index.gsp
@@ -41,6 +41,7 @@
             cancelReportUrl: "${createLink(action:'ajaxCancelReport', id:organisation.organisationId)}/",
             unCancelReportUrl: "${createLink(action:'ajaxUnCancelReport', id:organisation.organisationId)}/",
             reportsHistoryUrl:"${createLink(controller: 'report', action:'reportingHistory')}",
+            targetPeriodsUrl:"${createLink(controller: 'organisation', action:'getTargetPeriods',  id:organisation.organisationId)}",
             returnTo: '${g.createLink(action:'index', id:"${organisation.organisationId}")}',
             dashboardCategoryUrl: "${g.createLink(controller: 'report', action: 'activityOutputs', params: [fq:'organisationFacet:'+organisation.name])}",
             reportOwner: {organisationId:'${organisation.organisationId}'},
@@ -84,6 +85,7 @@
         var config = _.extend({
                 reportingConfigSelector:'#reporting-config form',
                 availableReportCategories:availableReportCategories,
+                targetPeriods: <fc:modelAsJavascript model="${content.admin?.targetPeriods}"/>
             }, fcConfig);
         var organisationViewModel = new OrganisationPageViewModel(organisation, config);
 

From 4eb5c56b626a798486a7cafb50ee5d687749d0cb Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Fri, 6 Dec 2024 13:22:34 +1100
Subject: [PATCH 04/30] IPPRS / RCS reporting WIP #3369

---
 .../ala/merit/OrganisationController.groovy   |   9 +-
 .../org/ala/merit/OrganisationService.groovy  |   2 +-
 grails-app/views/organisation/_admin.gsp      |   5 +-
 .../views/organisation/_serviceTargets.gsp    |  16 +--
 grails-app/views/organisation/index.gsp       |  11 +-
 .../scripts/releases/4.1/configureIPPRS.js    | 105 ++++++++++++++++++
 src/main/scripts/utils/addService.js          |   3 +-
 7 files changed, 128 insertions(+), 23 deletions(-)
 create mode 100644 src/main/scripts/releases/4.1/configureIPPRS.js

diff --git a/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy b/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
index 0109f6935..0b3c5f519 100644
--- a/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
+++ b/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
@@ -51,7 +51,6 @@ class OrganisationController {
              dashboard: dashboard,
              roles:roles,
              user:user,
-             services: organisationService.findApplicableServices(organisation, metadataService.getProjectServices()),
              isAdmin:orgRole?.role == RoleService.PROJECT_ADMIN_ROLE,
              isGrantManager:orgRole?.role == RoleService.GRANT_MANAGER_ROLE,
              content:content(organisation)]
@@ -73,10 +72,15 @@ class OrganisationController {
         def dashboardReports = [[name:'dashboard', label:'Activity Outputs']]
 
         Map availableReportCategories = null
+        List services = null
+        List targetPeriods = null
         if (adminVisible) {
             dashboardReports += [name:'announcements', label:'Announcements']
             availableReportCategories = settingService.getJson(SettingPageType.ORGANISATION_REPORT_CONFIG)
+            services = organisationService.findApplicableServices(organisation, metadataService.getProjectServices())
+            targetPeriods = organisationService.generateTargetPeriods(organisation)
         }
+        boolean showTargets = services != null
 
         List reportOrder = null
         if (reportingVisible) {
@@ -102,13 +106,12 @@ class OrganisationController {
         List projects = organisation.projects ?: []
         List programGroups = organisation.config?.programGroups ?: []
         Map projectGroups = projectGroupingHelper.groupProjectsByProgram(projects, programGroups, ["organisationId:"+organisation.organisationId], true)
-        List targetPeriods = organisationService.generateTargetPeriods(organisation)
 
         [about     : [label: 'About', visible: true, stopBinding: false, type:'tab', default:!reportingVisible, displayedPrograms:projectGroups.displayedPrograms, servicesDashboard:[visible:true]],
          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:'/shared/dashboard', reports:dashboardReports],
-         admin     : [label: 'Admin', visible: adminVisible, type: 'tab', template:'admin', showEditAnnoucements:showEditAnnoucements, availableReportCategories:availableReportCategories, targetPeriods:targetPeriods]]
+         admin     : [label: 'Admin', visible: adminVisible, type: 'tab', template:'admin', showEditAnnoucements:showEditAnnoucements, availableReportCategories:availableReportCategories, targetPeriods:targetPeriods, services: services, showTargets:showTargets]]
 
     }
 
diff --git a/grails-app/services/au/org/ala/merit/OrganisationService.groovy b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
index 3cb455599..f17932c0c 100644
--- a/grails-app/services/au/org/ala/merit/OrganisationService.groovy
+++ b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
@@ -359,7 +359,7 @@ class OrganisationService {
     def findApplicableServices(Map organisation, List allServices) {
 
         List supportedServices = organisation.config?.organisationReports?.collect{it.activityType}?.findAll{it}
-        List result = allServices
+        List result = []
         if (supportedServices) {
             result = allServices.findAll{ supportedServices.intersect(it.outputs.formName) }
         }
diff --git a/grails-app/views/organisation/_admin.gsp b/grails-app/views/organisation/_admin.gsp
index d219ecc6a..607fff012 100644
--- a/grails-app/views/organisation/_admin.gsp
+++ b/grails-app/views/organisation/_admin.gsp
@@ -9,8 +9,9 @@
             <a class="nav-link" data-toggle="pill" href="#reporting-config" id="reporting-config-tab" role="tab">Reporting</a>
             <a class="nav-link" data-toggle="pill" href="#config" id="config-tab" role="tab">Configuration</a>
         </g:if>
+        <g:if test="${showTargets}">
         <a class="nav-link" data-toggle="pill" href="#organisation-targets" id="organisation-targets-tab" role="tab">Targets</a>
-
+        </g:if>
     </div>
 
     <div class="tab-content col-9">
@@ -153,9 +154,11 @@
             </div>
             <!-- /ko -->
         </g:else>
+        <g:if test="${showTargets}">
         <div id="organisation-targets" class="tab-pane">
             <h3>Service Targets</h3>
             <g:render template="/organisation/serviceTargets" model="[services:organisation.services, periods:organisation.periods, showTargetDate:organisation.showTargetDate]"/>
         </div>
+        </g:if>
     </div>
 </div>
diff --git a/grails-app/views/organisation/_serviceTargets.gsp b/grails-app/views/organisation/_serviceTargets.gsp
index b2b540a81..9f5dc2714 100644
--- a/grails-app/views/organisation/_serviceTargets.gsp
+++ b/grails-app/views/organisation/_serviceTargets.gsp
@@ -7,23 +7,24 @@
         <th class="index" rowspan="2"></th>
         <th class="service required" rowspan="2">${serviceName ?: "Service"}</th>
         <th class="score required" rowspan="2" style="width: 20px">Target measure</th>
-        <th class="budget-cell required" rowspan="2">Total to be delivered <g:if test="${totalHelpText}"> <fc:iconHelp> ${totalHelpText} </fc:iconHelp></g:if> <g:else><fc:iconHelp html="true">The overall total of Organsiation Services to be delivered during the organisation delivery period.
-            <b>Note: this total is not necessarily the sum of the minimum annual targets set out for the service.</b></fc:iconHelp></g:else> </th>
+        <th class="budget-cell required" rowspan="2">Overall target <g:if test="${totalHelpText}"> <fc:iconHelp> ${totalHelpText} </fc:iconHelp></g:if></th>
         <g:if test="${showTargetDate}">
             <th class="target-date required" rowspan="2">
                 Delivery date <g:if test="${deliveryHelpText}"> <fc:iconHelp> ${deliveryHelpText} </fc:iconHelp> </g:if>
             </th>
         </g:if>
         <!-- ko if: periods && periods.length -->
-        <th data-bind="attr:{colspan:periods.length+1}">Minimum annual targets <fc:iconHelp>${minHelptext ?:"Specify the minimum total target for each Project Service to be delivered each financial year. Note: the sum of these targets will not necessarily equal the total services to be delivered."}</fc:iconHelp></th>
+        <th data-bind="attr:{colspan:periods.length+1}">${periodTargetsLabel ?: "Targets by date"}</th>
         <!-- /ko -->
     </tr>
+    <!-- ko if: periods && periods.length -->
     <tr>
 
         <!-- ko foreach: periods -->
         <th class="budget-cell"><div data-bind="text:$data.label"></div></th>
         <!-- /ko -->
     </tr>
+    <!-- /ko -->
     </thead>
     <tbody data-bind="foreach : services">
     <tr>
@@ -63,16 +64,7 @@
         <!-- /ko -->
     </tr>
     </tbody>
-%{--    <tfoot>--}%
 
-%{--    <tr>--}%
-%{--        <td data-bind="attr:{colspan:periods.length+${showTargetDate ? 6 : 5}}">--}%
-%{--            <button type="button" class="btn btn-sm"--}%
-%{--                    data-bind="disable: $parent.isProjectDetailsLocked(), click: addService">--}%
-%{--                <i class="fa fa-plus"></i> Add a row</button>--}%
-%{--        </td>--}%
-%{--    </tr>--}%
-%{--    </tfoot>--}%
 </table>
 <!-- /ko -->
 <!-- /ko -->
diff --git a/grails-app/views/organisation/index.gsp b/grails-app/views/organisation/index.gsp
index 2f57cbd25..526a86b1a 100644
--- a/grails-app/views/organisation/index.gsp
+++ b/grails-app/views/organisation/index.gsp
@@ -45,9 +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}'},
-            projects : <fc:modelAsJavascript model="${organisation.projects}"/>,
-            services: <fc:modelAsJavascript model="${services}"/>
-
+            projects : <fc:modelAsJavascript model="${organisation.projects}"/>
         };
     </script>
     <asset:stylesheet src="common-bs4.css"/>
@@ -82,11 +80,16 @@
 
         var organisation =<fc:modelAsJavascript model="${organisation}"/>;
         var availableReportCategories = <fc:modelAsJavascript model="${content.admin?.availableReportCategories}"/>;
+        var services = <fc:modelAsJavascript model="${content.admin?.services}"/>;
+        var targetPeriods = <fc:modelAsJavascript model="${content.admin?.targetPeriods}"/>;
         var config = _.extend({
                 reportingConfigSelector:'#reporting-config form',
                 availableReportCategories:availableReportCategories,
-                targetPeriods: <fc:modelAsJavascript model="${content.admin?.targetPeriods}"/>
+                targetPeriods: targetPeriods,
+                services: services
+
             }, fcConfig);
+
         var organisationViewModel = new OrganisationPageViewModel(organisation, config);
 
         ko.applyBindings(organisationViewModel);
diff --git a/src/main/scripts/releases/4.1/configureIPPRS.js b/src/main/scripts/releases/4.1/configureIPPRS.js
new file mode 100644
index 000000000..d1c126a8e
--- /dev/null
+++ b/src/main/scripts/releases/4.1/configureIPPRS.js
@@ -0,0 +1,105 @@
+load("../../utils/uuid.js");
+load( "../../utils/audit.js");
+load( "../../utils/addService.js");
+
+let adminUserId = '<system>'
+addService("Indigenous Procurement", NumberInt(47), 'Regional Capacity Services Report', 'Regional capacity services - reporting', undefined, adminUserId);
+
+
+// These have been pre-generated as there is value in having the same id in all environments
+var indigenousWorkforcePerformanceScoreId = '5d28e47f-5182-4ad7-91f7-074908fb66e4';
+var indigenousSupplyChainPerformanceScoreId = 'f7a537fd-cb38-4392-a0db-5e02beec6aa0';
+
+var scores = [
+    {
+        "category": "Indigenous Procurement",
+        "configuration": {
+            "childAggregations": [{
+                "property": "data.workforcePerformancePercentage",
+                    "type": "AVERAGE"
+            }]
+        },
+        "description": "",
+        "displayType": "",
+        "entity": "Activity",
+        "entityTypes": [],
+        "isOutputTarget": true,
+        "label": "Indigenous workforce performance",
+        "outputType": "Regional capacity services - reporting",
+        "scoreId": indigenousWorkforcePerformanceScoreId,
+        "status": "active"
+    },
+    {
+        "category": "Indigenous Procurement",
+        "configuration": {
+            "childAggregations": [{
+                "property": "data.workforcePerformancePercentage",
+                "type": "AVERAGE"
+            }]
+        },
+        "description": "",
+        "displayType": "",
+        "entity": "Activity",
+        "entityTypes": [],
+        "isOutputTarget": true,
+        "label": "Indigenous supply chain performance",
+        "outputType": "Regional capacity services - reporting",
+        "scoreId": indigenousSupplyChainPerformanceScoreId,
+        "status": "active"
+    }
+];
+
+for (var i=0; i<scores.length; i++) {
+    var existing = db.score.find({label:scores[i].label});
+    if (existing.hasNext()) {
+        var existingScore = existing.next();
+        existingScore.configuration = scores[i].configuration;
+        db.score.replaceOne({label:scores[i].label}, existingScore);
+    }
+    else {
+        db.score.insert(scores[i]);
+    }
+    audit(scores[i], scores[i].scoreId, 'au.org.ala.ecodata.Score', adminUserId, undefined, 'Update');
+
+}
+
+const targetsConfig = {
+    "periodGenerationConfig": {
+        "reportType": "Administrative",
+        "reportDescriptionFormat": "Regional capacity services report %d for %4$s",
+        "reportNameFormat": "Regional capacity report %d",
+        "reportingPeriodInMonths": 12,
+        "minimumReportDurationInDays": 1,
+        "label": "Annual",
+        "category": "Regional Capacity Services Reporting",
+        "activityType": "Regional Capacity Services Report",
+        "periodStart": "2022-06-30T14:00:00Z",
+        "periodEnd": "2027-06-30T13:59:59Z"
+    },
+    "periodLabelFormat": "MMM YYYY"
+};
+
+function findRcsReportConfig(reports) {
+    for (let i=0; i<reports.length; i++) {
+        if (reports[i].activityType == 'Regional Capacity Services Report') {
+            return reports[i];
+        }
+    }
+    return null;
+}
+db.organisation.find({'config.organisationReports':{$exists:true}}).forEach(function(org){
+    let rcsReportConfig = findRcsReportConfig(org.config.organisationReports);
+    if (!rcsReportConfig) {
+        return;
+    }
+    // Adjust the target config to match the period in which the RCS reporting is happening.
+    targetsConfig.periodGenerationConfig.periodStart = rcsReportConfig.periodStart;
+    targetsConfig.periodGenerationConfig.periodEnd = rcsReportConfig.periodEnd;
+
+    org.config.targets = targetsConfig;
+
+    //org.config.serviceTargets = serviceTargetsConfig;
+    print("updating organisation "+org.name+' , id: '+org.organisationId);
+    db.organisation.replaceOne({organisationId:org.organisationId}, org);
+    audit(org, org.organisationId, 'au.org.ala.ecodata.Organisation', adminUserId, undefined, 'Update');
+});
\ No newline at end of file
diff --git a/src/main/scripts/utils/addService.js b/src/main/scripts/utils/addService.js
index cc9811287..1dff8519d 100644
--- a/src/main/scripts/utils/addService.js
+++ b/src/main/scripts/utils/addService.js
@@ -1,5 +1,4 @@
-load("../../../utils/uuid.js");
-load( "../../../utils/audit.js");
+
 function addService (newServiceName, legacyId,  serviceFormName, sectionName, outputs, userId) {
     var eventType;
     legacyId = NumberInt(legacyId);

From 72f92d1243093a835a3f1f4d62d5051f42365333 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Fri, 6 Dec 2024 15:43:16 +1100
Subject: [PATCH 05/30] IPPRS / RCS reporting WIP #3369

---
 grails-app/conf/spring/resources.groovy       |  2 ++
 .../command/EditOrViewReportCommand.groovy    |  4 ++++
 ...cityServicesReportLifecycleListener.groovy | 19 +++++++++++++++
 .../reports/ReportLifecycleListener.groovy    | 13 +++++++++++
 .../ReportLifecycleListenerSpec.groovy        | 23 +++++++++++++++++++
 5 files changed, 61 insertions(+)
 create mode 100644 src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy

diff --git a/grails-app/conf/spring/resources.groovy b/grails-app/conf/spring/resources.groovy
index 1c368eb98..21b145a08 100644
--- a/grails-app/conf/spring/resources.groovy
+++ b/grails-app/conf/spring/resources.groovy
@@ -3,6 +3,7 @@ import au.org.ala.merit.MeritServletContextConfig
 import au.org.ala.merit.StatisticsFactory
 import au.org.ala.merit.hub.HubAwareLinkGenerator
 import au.org.ala.merit.reports.NHTOutputReportLifecycleListener
+import au.org.ala.merit.reports.RegionalCapacityServicesReportLifecycleListener
 import au.org.ala.merit.util.ProjectGroupingHelper
 
 // Place your Spring DSL code here
@@ -23,6 +24,7 @@ beans = {
     NHTOutputReport(NHTOutputReportLifecycleListener)
     GrantsandOthersProgressReport(NHTOutputReportLifecycleListener)
     ProcurementOutputReport(NHTOutputReportLifecycleListener)
+    RegionalCapacityServicesReport(RegionalCapacityServicesReportLifecycleListener)
 
     meritServletContextConfig(MeritServletContextConfig)
 }
diff --git a/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy b/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy
index 2e777a494..4efd62cca 100644
--- a/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy
+++ b/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy
@@ -1,6 +1,7 @@
 package au.org.ala.merit.command
 
 import au.org.ala.merit.ReportService
+import au.org.ala.merit.reports.ReportLifecycleListener
 import grails.artefact.Controller
 import grails.core.GrailsApplication
 import grails.validation.Validateable
@@ -40,6 +41,9 @@ abstract class EditOrViewReportCommand implements Validateable {
             model.saveReportUrl = linkGenerator.link(controller:entityType, action:'saveReport', id:id)
             model.documentOwner = [activityId:model.activity?.activityId, reportId:reportId]
             model.documentOwner[getEntityIdField()] = id
+
+            ReportLifecycleListener listener = reportService.reportLifeCycleListener(model.report)
+            model.putAll(listener.getContextData(entity, model.report))
         }
     }
 
diff --git a/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy b/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy
new file mode 100644
index 000000000..17bee091c
--- /dev/null
+++ b/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy
@@ -0,0 +1,19 @@
+package au.org.ala.merit.reports
+
+import au.org.ala.merit.OrganisationService
+import groovy.util.logging.Slf4j
+import org.springframework.beans.factory.annotation.Autowired
+
+@Slf4j
+class RegionalCapacityServicesReportLifecycleListener extends ReportLifecycleListener {
+
+    @Autowired
+    OrganisationService organisationService
+
+    Map getContextData(Map organisation, Map report) {
+        List outputTargets = organisation.custom?.details?.services?.targets
+        Map periodTargets = getTargetsForReportPeriod(report, outputTargets)
+        BigDecimal budget = new BigDecimal(0) // TODO get budget from the correct period of the budget table
+        [periodTargets:periodTargets, budget:budget]
+    }
+}
diff --git a/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy b/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy
index 20f29f82f..0e2662e5c 100644
--- a/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy
+++ b/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy
@@ -91,5 +91,18 @@ class ReportLifecycleListener {
         result
     }
 
+    static Map getTargetsForReportPeriod(Map report, List<Map> outputTargets) {
+        String endDate = report.toDate
+
+        outputTargets?.collectEntries { Map outputTarget ->
+            String scoreId = outputTarget.scoreId
+            String previousPeriod = ''
+            Map matchingPeriodTarget = outputTarget?.periodTargets?.find { Map periodTarget ->
+                previousPeriod < endDate && periodTarget.period >= endDate
+            }
+            [(scoreId): matchingPeriodTarget?.target]
+        }
+    }
+
 
 }
diff --git a/src/test/groovy/au/org/ala/merit/reports/ReportLifecycleListenerSpec.groovy b/src/test/groovy/au/org/ala/merit/reports/ReportLifecycleListenerSpec.groovy
index 53b8d4734..0529d0819 100644
--- a/src/test/groovy/au/org/ala/merit/reports/ReportLifecycleListenerSpec.groovy
+++ b/src/test/groovy/au/org/ala/merit/reports/ReportLifecycleListenerSpec.groovy
@@ -59,6 +59,29 @@ class ReportLifecycleListenerSpec extends Specification {
                 [entityType:"au.org.ala.ecodata.DataSetSummary", entityIds:["5b1255a4-3b91-4fae-a243-02bd4d163898"]]]
     }
 
+    def "getTargetForReportPeriod should return the correct target for the report period"() {
+        setup:
+        Map report = [toDate: '2023-12-31']
+        String scoreId = 'score1'
+        List<Map> values = [
+                [scoreId: 'score1', periodTargets: [
+                        [period: '2023-01-01', target: 10],
+                        [period: '2023-12-31', target: 20],
+                        [period: '2024-01-01', target: 30]
+                ]],
+                [scoreId: 'score2', periodTargets: [
+                        [period: '2023-01-01', target: 5],
+                        [period: '2023-12-31', target: 15]
+                ]]
+        ]
+
+        when:
+        def result = ReportLifecycleListener.getTargetForReportPeriod(report, scoreId, values)
+
+        then:
+        result == [period: '2023-12-31', target: 20]
+    }
+
     private static Map nhtActivityForm() {
         File file = new File('forms/nht/nhtOutputReport.json')
 

From 134dec58078d848eb86b74f60ebf775b5350416f Mon Sep 17 00:00:00 2001
From: temi <temi.varghese@csiro.au>
Date: Fri, 6 Dec 2024 16:07:34 +1100
Subject: [PATCH 06/30] #3369 added readonly fields read from context

---
 .../other/regionalCapacityServicesReport.json | 127 ++++++++++++++----
 1 file changed, 104 insertions(+), 23 deletions(-)

diff --git a/forms/other/regionalCapacityServicesReport.json b/forms/other/regionalCapacityServicesReport.json
index b77e6506e..f09f23862 100644
--- a/forms/other/regionalCapacityServicesReport.json
+++ b/forms/other/regionalCapacityServicesReport.json
@@ -137,16 +137,6 @@
               }
             ]
           },
-          {
-            "dataType": "boolean",
-            "name": "reportedToIpprs",
-            "description": ""
-          },
-          {
-            "dataType": "document",
-            "name": "ipprsReport",
-            "description": ""
-          },
           {
             "dataType": "number",
             "name": "organisationFteWorkforce",
@@ -157,6 +147,14 @@
               },
               {
                 "rule": "min[0]"
+              },
+              {
+                "param": {
+                  "expression": "organisationFteIndigenousWorkforce",
+                  "type": "computed"
+                },
+                "rule": "min",
+                "message": "Numeric value must be higher or equal to the numeric value entered for 'What was your organisation’s full time equivalent Indigenous workforce deployed on the Services this reporting period?'"
               }
             ]
           },
@@ -191,9 +189,58 @@
               },
               {
                 "rule": "min[0]"
+              },
+              {
+                "param": {
+                  "expression": "organisationPanelProjectValue",
+                  "type": "computed"
+                },
+                "rule": "max",
+                "message": "The sum of the value of Services contracted to First Nations people/Indigenous enterprises for all reporting periods can not exceed the total $ value (GST inclusive) of the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts)"
               }
             ]
           },
+          {
+            "dataType": "number",
+            "name": "organisationPanelProjectValue",
+            "description": "Enter the total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
+            "validate": "required,min[0]",
+            "computed": {
+              "expression": "owner.totalContractValue"
+            }
+          },
+          {
+            "dataType": "number",
+            "name": "supplyChainPerformancePercentage",
+            "description": "Calculated using value of Services contracted to First Nations people/Indigenous enterprises ÷ stored Total Organisation Panel/Project Value $ x 100",
+            "computed": {
+              "expression": "servicesContractedValueFirstNations / organisationPanelProjectValue * 100"
+            }
+          },
+          {
+            "dataType": "number",
+            "name": "workforcePerformancePercentage",
+            "description": "Calculated using FTE Indigenous workforce ÷ FTE workforce x 100",
+            "computed": {
+                "expression": "organisationFteIndigenousWorkforce / organisationFteWorkforce * 100"
+            }
+          },
+          {
+            "dataType": "number",
+            "name": "targetIndigenousParticipationPercentage",
+            "description": "Target Indigenous Participation Percentage for this reporting period",
+            "computed": {
+              "expression": "$context.owner.targetIndigenousParticipationPercentage"
+            }
+          },
+          {
+            "dataType": "number",
+            "name": "targetIndigenousProcurementPercentage",
+            "description": "Target Indigenous Procurement Percentage for this reporting period",
+            "computed": {
+              "expression": "owner.targetIndigenousProcurementPercentage"
+            }
+          },
           {
             "name": "staffDevelopmentOpportunities",
             "description": "This includes on-the-job, informal, and formal training. If no activities were undertaken, enter ‘Nil’.",
@@ -510,14 +557,23 @@
               }
             ]
           },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<b>Q5(b). Please complete workforce and supply chain data (Deed of Standing Offer Clause 38 Indigenous Procurement Policy). Please do not provide personal or <span style='white-space: nowrap;'>commercial-in-confidence</span> information when answering this question. Please retain documentation as evidence to support assurance activities undertaken to verify the reported information.</b>"
+              }
+            ]
+          },
           {
             "type": "row",
             "items": [
               {
                 "css": "span7",
-                "preLabel": "Q5(b). Please confirm that your organisation has reported via the IPPRS this period (Clause 38 Indigenous Procurement Policy), and that this report has been uploaded to the documents tab in MERIT.",
-                "source": "reportedToIpprs",
-                "type": "boolean"
+                "preLabel": "What was your organisation’s full time equivalent workforce deployed on the Services this reporting period?",
+                "source": "organisationFteWorkforce",
+                "type": "number"
               }
             ]
           },
@@ -525,9 +581,10 @@
             "type": "row",
             "items": [
               {
-                "type": "document",
-                "source": "ipprsReport",
-                "preLabel": "Please attach the document here."
+                "css": "span7",
+                "preLabel": "What was your organisation’s full time equivalent Indigenous workforce deployed on the Services this reporting period?",
+                "source": "organisationFteIndigenousWorkforce",
+                "type": "number"
               }
             ]
           },
@@ -535,8 +592,10 @@
             "type": "row",
             "items": [
               {
-                "type": "literal",
-                "source": "<b>Q5(c). Please complete workforce and supply chain data (Deed of Standing Offer Clause 38 Indigenous Procurement Policy). Please do not provide personal or <span style='white-space: nowrap;'>commercial-in-confidence</span> information when answering this question. Please retain documentation as evidence to support assurance activities undertaken to verify the reported information.</b>"
+                "css": "span7",
+                "preLabel": "Workforce Performance % this reporting period",
+                "source": "workforcePerformancePercentage",
+                "type": "number"
               }
             ]
           },
@@ -545,8 +604,8 @@
             "items": [
               {
                 "css": "span7",
-                "preLabel": "What was your organisation’s full time equivalent workforce deployed on the Services this reporting period?",
-                "source": "organisationFteWorkforce",
+                "preLabel": "Target Workforce Performance % this reporting period",
+                "source": "targetIndigenousParticipationPercentage",
                 "type": "number"
               }
             ]
@@ -556,8 +615,8 @@
             "items": [
               {
                 "css": "span7",
-                "preLabel": "What was your organisation’s full time equivalent Indigenous workforce deployed on the Services this reporting period?",
-                "source": "organisationFteIndigenousWorkforce",
+                "preLabel": "What is your total organisation panel/project $ value (GST inclusive)?",
+                "source": "organisationPanelProjectValue",
                 "type": "number"
               }
             ]
@@ -567,12 +626,34 @@
             "items": [
               {
                 "css": "span7",
-                "preLabel": "What was the dollar value of Services contracted to First Nations people/Indigenous enterprises during this reporting period?",
+                "preLabel": "What was the dollar value (GST inclusive) of Services contracted to First Nations people/Indigenous enterprises during this reporting period?",
                 "source": "servicesContractedValueFirstNations",
                 "type": "number"
               }
             ]
           },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Supply Chain Performance % this reporting period",
+                "source": "supplyChainPerformancePercentage",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Target Supply Chain Performance % this reporting period",
+                "source": "targetIndigenousProcurementPercentage",
+                "type": "number"
+              }
+            ]
+          },
           {
             "type": "row",
             "items": [

From 93fa773b98fd59837e116453b3ed5ab7b57c814b Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Mon, 9 Dec 2024 12:48:15 +1100
Subject: [PATCH 07/30] Support score names #3369

---
 src/main/groovy/au/org/ala/merit/Score.groovy   |  1 +
 .../reports/ReportLifecycleListener.groovy      | 13 +++++++++++--
 src/main/scripts/releases/4.1/configureIPPRS.js | 17 +++++++++++++++--
 3 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/src/main/groovy/au/org/ala/merit/Score.groovy b/src/main/groovy/au/org/ala/merit/Score.groovy
index a89d94a53..1a6b9ec89 100644
--- a/src/main/groovy/au/org/ala/merit/Score.groovy
+++ b/src/main/groovy/au/org/ala/merit/Score.groovy
@@ -32,6 +32,7 @@ class Score {
     String label
     String units
     String category
+    String name
     boolean isOutputTarget
 
     int overDeliveryThreshold = OVER_DELIVERY_PERCENTAGE_THRESHOLD
diff --git a/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy b/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy
index 0e2662e5c..c606e7536 100644
--- a/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy
+++ b/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy
@@ -1,6 +1,7 @@
 package au.org.ala.merit.reports
 
 import au.org.ala.ecodata.forms.ActivityFormService
+import au.org.ala.merit.MetadataService
 import groovy.util.logging.Slf4j
 import org.springframework.beans.factory.annotation.Autowired
 /**
@@ -13,6 +14,8 @@ class ReportLifecycleListener {
     boolean deleteSiteOnReset = true
     @Autowired
     ActivityFormService activityFormService
+    @Autowired
+    MetadataService metadataService
 
     Map getContextData(Map context, Map report) { [:] }
     Map getOutputData(Map context, Map outputConfig, Map report) { [:] }
@@ -91,18 +94,24 @@ class ReportLifecycleListener {
         result
     }
 
-    static Map getTargetsForReportPeriod(Map report, List<Map> outputTargets) {
+    Map getTargetsForReportPeriod(Map report, List<Map> outputTargets) {
         String endDate = report.toDate
 
         outputTargets?.collectEntries { Map outputTarget ->
             String scoreId = outputTarget.scoreId
+            String name = scoreName(scoreId) ?: scoreId
             String previousPeriod = ''
             Map matchingPeriodTarget = outputTarget?.periodTargets?.find { Map periodTarget ->
                 previousPeriod < endDate && periodTarget.period >= endDate
             }
-            [(scoreId): matchingPeriodTarget?.target]
+            [(name): matchingPeriodTarget?.target]
         }
     }
 
+    String scoreName(String scoreId) {
+        List scores = metadataService.getScores(false)
+        scores.find { it.scoreId == scoreId }?.name
+    }
+
 
 }
diff --git a/src/main/scripts/releases/4.1/configureIPPRS.js b/src/main/scripts/releases/4.1/configureIPPRS.js
index d1c126a8e..b21023c9d 100644
--- a/src/main/scripts/releases/4.1/configureIPPRS.js
+++ b/src/main/scripts/releases/4.1/configureIPPRS.js
@@ -14,6 +14,11 @@ var scores = [
     {
         "category": "Indigenous Procurement",
         "configuration": {
+            "filter": {
+                filterValue: "Regional capacity services - reporting",
+                property: "name",
+                type: "filter"
+            },
             "childAggregations": [{
                 "property": "data.workforcePerformancePercentage",
                     "type": "AVERAGE"
@@ -27,11 +32,17 @@ var scores = [
         "label": "Indigenous workforce performance",
         "outputType": "Regional capacity services - reporting",
         "scoreId": indigenousWorkforcePerformanceScoreId,
-        "status": "active"
+        "status": "active",
+        "name":"targetIndigenousParticipationPercentage"
     },
     {
         "category": "Indigenous Procurement",
         "configuration": {
+            "filter": {
+                filterValue: "Regional capacity services - reporting",
+                property: "name",
+                type: "filter"
+            },
             "childAggregations": [{
                 "property": "data.workforcePerformancePercentage",
                 "type": "AVERAGE"
@@ -45,7 +56,8 @@ var scores = [
         "label": "Indigenous supply chain performance",
         "outputType": "Regional capacity services - reporting",
         "scoreId": indigenousSupplyChainPerformanceScoreId,
-        "status": "active"
+        "status": "active",
+        "name":"targetIndigenousProcurementPercentage"
     }
 ];
 
@@ -54,6 +66,7 @@ for (var i=0; i<scores.length; i++) {
     if (existing.hasNext()) {
         var existingScore = existing.next();
         existingScore.configuration = scores[i].configuration;
+        existingScore.name = scores[i].name;
         db.score.replaceOne({label:scores[i].label}, existingScore);
     }
     else {

From 3e9cb2910100ad80e0c87799af96a88507ea7684 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Mon, 9 Dec 2024 13:57:57 +1100
Subject: [PATCH 08/30] Updated tests #3369

---
 .../org/ala/merit/command/EditOrViewReportCommand.groovy   | 4 +++-
 .../au/org/ala/merit/ManagementUnitControllerSpec.groovy   | 1 +
 .../au/org/ala/merit/OrganisationControllerSpec.groovy     | 1 +
 .../ala/merit/reports/ReportLifecycleListenerSpec.groovy   | 7 +++++--
 4 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy b/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy
index 4efd62cca..62c719baf 100644
--- a/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy
+++ b/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy
@@ -43,7 +43,9 @@ abstract class EditOrViewReportCommand implements Validateable {
             model.documentOwner[getEntityIdField()] = id
 
             ReportLifecycleListener listener = reportService.reportLifeCycleListener(model.report)
-            model.putAll(listener.getContextData(entity, model.report))
+            if (listener) {
+                model.putAll(listener.getContextData(entity, model.report))
+            }
         }
     }
 
diff --git a/src/test/groovy/au/org/ala/merit/ManagementUnitControllerSpec.groovy b/src/test/groovy/au/org/ala/merit/ManagementUnitControllerSpec.groovy
index 2092ce925..778fe38fa 100644
--- a/src/test/groovy/au/org/ala/merit/ManagementUnitControllerSpec.groovy
+++ b/src/test/groovy/au/org/ala/merit/ManagementUnitControllerSpec.groovy
@@ -17,6 +17,7 @@ class ManagementUnitControllerSpec extends Specification implements ControllerUn
     ProjectService projectService = Mock(ProjectService)
     MetadataService metadataService = Mock(MetadataService)
 
+
     String adminUserId = 'admin'
     String editorUserId = 'editor'
     String grantManagerUserId = 'grantManager'
diff --git a/src/test/groovy/au/org/ala/merit/OrganisationControllerSpec.groovy b/src/test/groovy/au/org/ala/merit/OrganisationControllerSpec.groovy
index 5256c00df..fb25b07f4 100644
--- a/src/test/groovy/au/org/ala/merit/OrganisationControllerSpec.groovy
+++ b/src/test/groovy/au/org/ala/merit/OrganisationControllerSpec.groovy
@@ -46,6 +46,7 @@ class OrganisationControllerSpec extends Specification implements ControllerUnit
         controller.projectService = projectService
         controller.reportService = reportService
         controller.settingService = settingService
+        controller.metadataService = metadataService
 
         grails.converters.JSON.registerObjectMarshaller(new MapMarshaller())
         grails.converters.JSON.registerObjectMarshaller(new CollectionMarshaller())
diff --git a/src/test/groovy/au/org/ala/merit/reports/ReportLifecycleListenerSpec.groovy b/src/test/groovy/au/org/ala/merit/reports/ReportLifecycleListenerSpec.groovy
index 0529d0819..1baeb3ea6 100644
--- a/src/test/groovy/au/org/ala/merit/reports/ReportLifecycleListenerSpec.groovy
+++ b/src/test/groovy/au/org/ala/merit/reports/ReportLifecycleListenerSpec.groovy
@@ -1,6 +1,7 @@
 package au.org.ala.merit.reports
 
 import au.org.ala.ecodata.forms.ActivityFormService
+import au.org.ala.merit.MetadataService
 import groovy.json.JsonSlurper
 import spock.lang.Specification
 
@@ -8,9 +9,11 @@ class ReportLifecycleListenerSpec extends Specification {
 
     ReportLifecycleListener reportData = new ReportLifecycleListener()
     ActivityFormService activityFormService = Mock(ActivityFormService)
+    MetadataService metadataService = Mock(MetadataService)
 
     def setup() {
         reportData.activityFormService = activityFormService
+        reportData.metadataService = metadataService
     }
 
 
@@ -76,10 +79,10 @@ class ReportLifecycleListenerSpec extends Specification {
         ]
 
         when:
-        def result = ReportLifecycleListener.getTargetForReportPeriod(report, scoreId, values)
+        def result = reportData.getTargetsForReportPeriod(report, values)
 
         then:
-        result == [period: '2023-12-31', target: 20]
+        result == [score1:20, score2: 15]
     }
 
     private static Map nhtActivityForm() {

From be014dd0a5109cc1a6739c73c341b4b7747407ea Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Mon, 9 Dec 2024 15:31:14 +1100
Subject: [PATCH 09/30] Renamed model to fix tests #3369

---
 grails-app/assets/javascripts/organisation.js | 2 +-
 grails-app/assets/javascripts/services.js     | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/grails-app/assets/javascripts/organisation.js b/grails-app/assets/javascripts/organisation.js
index b34198c78..2cc2fec48 100644
--- a/grails-app/assets/javascripts/organisation.js
+++ b/grails-app/assets/javascripts/organisation.js
@@ -656,7 +656,7 @@ OrganisationPageViewModel = function (props, options) {
     self.allTargetMeasures = _.sortBy(self.allTargetMeasures, 'label');
     var propDetails = props && props.custom && props.custom.details || {};
     self.selectedTargetMeasures = ko.observableArray();
-    var details = new DetailsViewModel(propDetails, props, self.periods, self.allTargetMeasures, options);
+    var details = new OrganisationDetailsViewModel(propDetails, props, self.periods, self.allTargetMeasures, options);
     updatedTargetMeasures(details);
     self.reportingTargets = ko.observable(details);
     self.isProjectDetailsLocked = ko.observable(false);
diff --git a/grails-app/assets/javascripts/services.js b/grails-app/assets/javascripts/services.js
index 20c5b271d..0e498fe6d 100644
--- a/grails-app/assets/javascripts/services.js
+++ b/grails-app/assets/javascripts/services.js
@@ -1,9 +1,9 @@
-function DetailsViewModel(o, organisation, budgetHeaders, allServices, config) {
+function OrganisationDetailsViewModel(o, organisation, budgetHeaders, allServices, config) {
     var self = this;
     var period = budgetHeaders,
         serviceIds = o.services && o.services.serviceIds || [],
         targets = o.services && o.services.targets || [];
-    self.services = new ServicesViewModel(serviceIds, config.services, targets, budgetHeaders);
+    self.services = new OrganisationServicesViewModel(serviceIds, config.services, targets, budgetHeaders);
     function clearHiddenFields(jsData) {
 
     };
@@ -33,7 +33,7 @@ function DetailsViewModel(o, organisation, budgetHeaders, allServices, config) {
  * @param outputTargets The current organisation targets
  * @param periods An array of periods, each of which require a target to be set
  */
-function ServicesViewModel(serviceIds, allServices, outputTargets, periods) {
+function OrganisationServicesViewModel(serviceIds, allServices, outputTargets, periods) {
     var self = this,
         OPERATION_SUM = "SUM",
         OPERATION_AVG = "AVG",

From 6936cb3f3b16fb583725149833144168f1dc651a Mon Sep 17 00:00:00 2001
From: temi <temi.varghese@csiro.au>
Date: Mon, 9 Dec 2024 16:57:36 +1100
Subject: [PATCH 10/30] #3369 initial commit of pre-pop config

---
 .../other/regionalCapacityServicesReport.json  | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/forms/other/regionalCapacityServicesReport.json b/forms/other/regionalCapacityServicesReport.json
index f09f23862..a1143d939 100644
--- a/forms/other/regionalCapacityServicesReport.json
+++ b/forms/other/regionalCapacityServicesReport.json
@@ -9,6 +9,19 @@
       "collapsedByDefault": false,
       "templateName": "regionalCapacityServicesReport",
       "template": {
+          "pre-populate": [
+            {
+              "source": {
+                "context-path": ""
+              },
+              "mapping": [
+                {
+                  "target": "targetIndigenousParticipationPercentage",
+                  "source-path": "targetIndigenousParticipationPercentage"
+                }
+              ]
+            }
+          ],
         "dataModel": [
           {
             "name": "governanceAndFinancialFrameworksOnTrack",
@@ -228,10 +241,7 @@
           {
             "dataType": "number",
             "name": "targetIndigenousParticipationPercentage",
-            "description": "Target Indigenous Participation Percentage for this reporting period",
-            "computed": {
-              "expression": "$context.owner.targetIndigenousParticipationPercentage"
-            }
+            "description": "Target Indigenous Participation Percentage for this reporting period"
           },
           {
             "dataType": "number",

From 3dac5bec48301a3120c7e93919b497c64197bec9 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Mon, 9 Dec 2024 17:16:05 +1100
Subject: [PATCH 11/30] Fixed paths for context data #3369

---
 forms/other/regionalCapacityServicesReport.json               | 4 ++--
 .../au/org/ala/merit/command/EditOrViewReportCommand.groovy   | 2 +-
 .../RegionalCapacityServicesReportLifecycleListener.groovy    | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/forms/other/regionalCapacityServicesReport.json b/forms/other/regionalCapacityServicesReport.json
index a1143d939..d1c7b9f5f 100644
--- a/forms/other/regionalCapacityServicesReport.json
+++ b/forms/other/regionalCapacityServicesReport.json
@@ -12,12 +12,12 @@
           "pre-populate": [
             {
               "source": {
-                "context-path": ""
+                "context-path": "owner"
               },
               "mapping": [
                 {
                   "target": "targetIndigenousParticipationPercentage",
-                  "source-path": "targetIndigenousParticipationPercentage"
+                  "source-path": "periodTargets.targetIndigenousParticipationPercentage"
                 }
               ]
             }
diff --git a/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy b/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy
index 62c719baf..20a401f08 100644
--- a/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy
+++ b/src/main/groovy/au/org/ala/merit/command/EditOrViewReportCommand.groovy
@@ -44,7 +44,7 @@ abstract class EditOrViewReportCommand implements Validateable {
 
             ReportLifecycleListener listener = reportService.reportLifeCycleListener(model.report)
             if (listener) {
-                model.putAll(listener.getContextData(entity, model.report))
+                model.context.putAll(listener.getContextData(entity, model.report))
             }
         }
     }
diff --git a/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy b/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy
index 17bee091c..1241eba5b 100644
--- a/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy
+++ b/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy
@@ -13,7 +13,7 @@ class RegionalCapacityServicesReportLifecycleListener extends ReportLifecycleLis
     Map getContextData(Map organisation, Map report) {
         List outputTargets = organisation.custom?.details?.services?.targets
         Map periodTargets = getTargetsForReportPeriod(report, outputTargets)
-        BigDecimal budget = new BigDecimal(0) // TODO get budget from the correct period of the budget table
-        [periodTargets:periodTargets, budget:budget]
+        BigDecimal funding = new BigDecimal(0) // TODO get budget from the correct period of the budget table
+        [periodTargets:periodTargets, totalContractValue:funding]
     }
 }

From f6173722ffa3531aceb98b7aa386a41ad5412b44 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Tue, 10 Dec 2024 08:20:46 +1100
Subject: [PATCH 12/30] Added funding table to UI #3369

---
 grails-app/assets/javascripts/budget.js       | 109 +++++++++++++++++
 grails-app/assets/javascripts/meriplan.js     | 110 ------------------
 grails-app/assets/javascripts/organisation.js |   5 +-
 grails-app/assets/javascripts/projects.js     |   1 +
 grails-app/assets/javascripts/services.js     |   1 +
 grails-app/views/organisation/_admin.gsp      |   2 +
 grails-app/views/organisation/_budget.gsp     |  29 +++++
 .../views/organisation/_serviceTargets.gsp    |   2 +-
 8 files changed, 146 insertions(+), 113 deletions(-)
 create mode 100644 grails-app/assets/javascripts/budget.js
 create mode 100644 grails-app/views/organisation/_budget.gsp

diff --git a/grails-app/assets/javascripts/budget.js b/grails-app/assets/javascripts/budget.js
new file mode 100644
index 000000000..706485eb7
--- /dev/null
+++ b/grails-app/assets/javascripts/budget.js
@@ -0,0 +1,109 @@
+function BudgetViewModel(o, period) {
+    var self = this;
+    if (!o) o = {};
+
+    self.overallTotal = ko.observable(0.0);
+
+    var headerArr = [];
+    for (i = 0; i < period.length; i++) {
+        headerArr.push({"data": period[i]});
+    }
+    self.headers = ko.observableArray(headerArr);
+
+    var row = [];
+    o.rows ? row = o.rows : row.push(ko.mapping.toJS(new BudgetRowViewModel({}, period)));
+    self.rows = ko.observableArray($.map(row, function (obj, i) {
+        // Headers don't match with previously stored headers, adjust rows accordingly.
+        if (o.headers && period && o.headers.length != period.length) {
+            var updatedRow = [];
+            for (i = 0; i < period.length; i++) {
+                var index = -1;
+
+                for (j = 0; j < o.headers.length; j++) {
+                    if (period[i] == o.headers[j].data) {
+                        index = j;
+                        break;
+                    }
+                }
+                updatedRow.push(index != -1 ? obj.costs[index] : 0.0)
+                index = -1;
+            }
+            obj.costs = updatedRow;
+        }
+
+        return new BudgetRowViewModel(obj, period);
+    }));
+
+    self.overallTotal = ko.computed(function () {
+        var total = 0.0;
+        ko.utils.arrayForEach(this.rows(), function (row) {
+            if (row.rowTotal()) {
+                total += parseFloat(row.rowTotal());
+            }
+        });
+        return total;
+    }, this).extend({currency: {}});
+
+    var allBudgetTotal = [];
+    for (i = 0; i < period.length; i++) {
+        allBudgetTotal.push(new BudgetTotalViewModel(this.rows, i));
+    }
+    self.columnTotal = ko.observableArray(allBudgetTotal);
+
+    self.addRow = function () {
+        self.rows.push(new BudgetRowViewModel({}, period));
+    }
+};
+
+function BudgetTotalViewModel(rows, index) {
+    var self = this;
+    self.data = ko.computed(function () {
+        var total = 0.0;
+        ko.utils.arrayForEach(rows(), function (row) {
+            if (row.costs()[index]) {
+                total += parseFloat(row.costs()[index].dollar());
+            }
+        });
+        return total;
+    }, this).extend({currency: {}});
+};
+
+
+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);
+
+    var arr = [];
+    // Have at least one period to record, which will essentially be a project total.
+    var minPeriods = _.max([1, period.length]);
+    for (var i = 0; i < minPeriods; i++) {
+        arr.push(ko.mapping.toJS(new FloatViewModel()));
+    }
+
+    if (o.costs && o.costs.length != arr.length) {
+        o.costs = arr;
+    }
+    o.costs ? arr = o.costs : arr;
+    self.costs = ko.observableArray($.map(arr, function (obj, i) {
+        return new FloatViewModel(obj);
+    }));
+
+    self.rowTotal = ko.computed(function () {
+        var total = 0.0;
+        ko.utils.arrayForEach(this.costs(), function (cost) {
+            if (cost.dollar())
+                total += parseFloat(cost.dollar());
+        });
+        return total;
+    }, this).extend({currency: {}});
+};
+
+function FloatViewModel(o) {
+    var self = this;
+    if (!o) o = {};
+    self.dollar = ko.observable(o.dollar ? o.dollar : 0.0).extend({numericString: 2}).extend({currency: {}});
+};
\ No newline at end of file
diff --git a/grails-app/assets/javascripts/meriplan.js b/grails-app/assets/javascripts/meriplan.js
index 31df32bfe..8ee87153f 100644
--- a/grails-app/assets/javascripts/meriplan.js
+++ b/grails-app/assets/javascripts/meriplan.js
@@ -1984,116 +1984,6 @@ function SingleAssetOutcomeViewModel(o, config) {
     };
 };
 
-function BudgetViewModel(o, period) {
-    var self = this;
-    if (!o) o = {};
-
-    self.overallTotal = ko.observable(0.0);
-
-    var headerArr = [];
-    for (i = 0; i < period.length; i++) {
-        headerArr.push({"data": period[i]});
-    }
-    self.headers = ko.observableArray(headerArr);
-
-    var row = [];
-    o.rows ? row = o.rows : row.push(ko.mapping.toJS(new BudgetRowViewModel({}, period)));
-    self.rows = ko.observableArray($.map(row, function (obj, i) {
-        // Headers don't match with previously stored headers, adjust rows accordingly.
-        if (o.headers && period && o.headers.length != period.length) {
-            var updatedRow = [];
-            for (i = 0; i < period.length; i++) {
-                var index = -1;
-
-                for (j = 0; j < o.headers.length; j++) {
-                    if (period[i] == o.headers[j].data) {
-                        index = j;
-                        break;
-                    }
-                }
-                updatedRow.push(index != -1 ? obj.costs[index] : 0.0)
-                index = -1;
-            }
-            obj.costs = updatedRow;
-        }
-
-        return new BudgetRowViewModel(obj, period);
-    }));
-
-    self.overallTotal = ko.computed(function () {
-        var total = 0.0;
-        ko.utils.arrayForEach(this.rows(), function (row) {
-            if (row.rowTotal()) {
-                total += parseFloat(row.rowTotal());
-            }
-        });
-        return total;
-    }, this).extend({currency: {}});
-
-    var allBudgetTotal = [];
-    for (i = 0; i < period.length; i++) {
-        allBudgetTotal.push(new BudgetTotalViewModel(this.rows, i));
-    }
-    self.columnTotal = ko.observableArray(allBudgetTotal);
-
-    self.addRow = function () {
-        self.rows.push(new BudgetRowViewModel({}, period));
-    }
-};
-
-function BudgetTotalViewModel(rows, index) {
-    var self = this;
-    self.data = ko.computed(function () {
-        var total = 0.0;
-        ko.utils.arrayForEach(rows(), function (row) {
-            if (row.costs()[index]) {
-                total += parseFloat(row.costs()[index].dollar());
-            }
-        });
-        return total;
-    }, this).extend({currency: {}});
-};
-
-
-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);
-
-    var arr = [];
-    // Have at least one period to record, which will essentially be a project total.
-    var minPeriods = _.max([1, period.length]);
-    for (var i = 0; i < minPeriods; i++) {
-        arr.push(ko.mapping.toJS(new FloatViewModel()));
-    }
-
-    if (o.costs && o.costs.length != arr.length) {
-        o.costs = arr;
-    }
-    o.costs ? arr = o.costs : arr;
-    self.costs = ko.observableArray($.map(arr, function (obj, i) {
-        return new FloatViewModel(obj);
-    }));
-
-    self.rowTotal = ko.computed(function () {
-        var total = 0.0;
-        ko.utils.arrayForEach(this.costs(), function (cost) {
-            if (cost.dollar())
-                total += parseFloat(cost.dollar());
-        });
-        return total;
-    }, this).extend({currency: {}});
-};
-
-function FloatViewModel(o) {
-    var self = this;
-    if (!o) o = {};
-    self.dollar = ko.observable(o.dollar ? o.dollar : 0.0).extend({numericString: 2}).extend({currency: {}});
-};
-
 function ActivitiesViewModel(activities, programActivities) {
     var self = this;
 
diff --git a/grails-app/assets/javascripts/organisation.js b/grails-app/assets/javascripts/organisation.js
index 2cc2fec48..508c71916 100644
--- a/grails-app/assets/javascripts/organisation.js
+++ b/grails-app/assets/javascripts/organisation.js
@@ -3,6 +3,7 @@
 //= require reportService
 //= require components.js
 //= require ecodata-components.js
+//= require budget.js
 /**
  * Knockout view model for organisation pages.
  * @param props JSON/javascript representation of the organisation.
@@ -658,7 +659,7 @@ OrganisationPageViewModel = function (props, options) {
     self.selectedTargetMeasures = ko.observableArray();
     var details = new OrganisationDetailsViewModel(propDetails, props, self.periods, self.allTargetMeasures, options);
     updatedTargetMeasures(details);
-    self.reportingTargets = ko.observable(details);
+    self.reportingTargetsAndFunding = ko.observable(details);
     self.isProjectDetailsLocked = ko.observable(false);
 
     var setStartAndEndDateDefaults = function() {
@@ -762,7 +763,7 @@ OrganisationPageViewModel = function (props, options) {
 
     self.saveCustomFields = function() {
         if ($("#organisation-targets > table").validationEngine('validate')) {
-            var json = JSON.parse(self.reportingTargets().modelAsJSON());
+            var json = JSON.parse(self.reportingTargetsAndFunding().modelAsJSON());
             return saveOrganisation(json);
         }
     };
diff --git a/grails-app/assets/javascripts/projects.js b/grails-app/assets/javascripts/projects.js
index 1569dc5c1..6a7b2a166 100644
--- a/grails-app/assets/javascripts/projects.js
+++ b/grails-app/assets/javascripts/projects.js
@@ -13,6 +13,7 @@
 //= require jquery-file-download/jquery.fileDownload.js
 //= require document.js
 //= require meriplan.js
+//= require budget.js
 //= require risks.js
 //= require sites.js
 //= require activity.js
diff --git a/grails-app/assets/javascripts/services.js b/grails-app/assets/javascripts/services.js
index 0e498fe6d..45d87c6fe 100644
--- a/grails-app/assets/javascripts/services.js
+++ b/grails-app/assets/javascripts/services.js
@@ -4,6 +4,7 @@ function OrganisationDetailsViewModel(o, organisation, budgetHeaders, allService
         serviceIds = o.services && o.services.serviceIds || [],
         targets = o.services && o.services.targets || [];
     self.services = new OrganisationServicesViewModel(serviceIds, config.services, targets, budgetHeaders);
+    self.funding = new BudgetViewModel(o.funding, budgetHeaders);
     function clearHiddenFields(jsData) {
 
     };
diff --git a/grails-app/views/organisation/_admin.gsp b/grails-app/views/organisation/_admin.gsp
index 607fff012..70b08edbf 100644
--- a/grails-app/views/organisation/_admin.gsp
+++ b/grails-app/views/organisation/_admin.gsp
@@ -156,6 +156,8 @@
         </g:else>
         <g:if test="${showTargets}">
         <div id="organisation-targets" class="tab-pane">
+            <h3>Total funding</h3>
+            <g:render template="/organisation/budget"/>
             <h3>Service Targets</h3>
             <g:render template="/organisation/serviceTargets" model="[services:organisation.services, periods:organisation.periods, showTargetDate:organisation.showTargetDate]"/>
         </div>
diff --git a/grails-app/views/organisation/_budget.gsp b/grails-app/views/organisation/_budget.gsp
new file mode 100644
index 000000000..da7a4dc4f
--- /dev/null
+++ b/grails-app/views/organisation/_budget.gsp
@@ -0,0 +1,29 @@
+<!-- ko with:reportingTargetsAndFunding().funding -->
+<div class="funding">
+
+    <label><b>Funding</b><fc:iconHelp title="Project Budget">${budgetHelpText?:"Enter the budget as reviewed each year"}</fc:iconHelp></label>
+    <g:if test="${explanation}">
+        <p>${explanation}</p>
+    </g:if>
+    <table class="table">
+        <thead>
+        <tr>
+            <!-- ko foreach: headers -->
+            <th class="budget-amount"><div data-bind="text:data.label"></div>$</th>
+            <!-- /ko -->
+
+        </tr>
+        </thead>
+        <tbody data-bind="foreach : rows">
+        <tr>
+            <!-- ko foreach: costs -->
+            <td class="budget-amount">
+                <input type="number" class="form-control form-control-sm" data-bind="value: dollar, numeric: $root.number, disable: $root.isProjectDetailsLocked()" data-validation-engine="validate[custom[number]]"/>
+            </td>
+            <!-- /ko -->
+        </tr>
+        </tbody>
+    </table>
+
+</div>
+<!-- /ko -->
diff --git a/grails-app/views/organisation/_serviceTargets.gsp b/grails-app/views/organisation/_serviceTargets.gsp
index 9f5dc2714..f8d6bdc79 100644
--- a/grails-app/views/organisation/_serviceTargets.gsp
+++ b/grails-app/views/organisation/_serviceTargets.gsp
@@ -1,4 +1,4 @@
-<!-- ko with:reportingTargets() -->
+<!-- ko with:reportingTargetsAndFunding() -->
 <h4>${title ?: "Organisation services and minimum targets"}</h4>
 <!-- ko with: services -->
 <table class="table service-targets validationEngineContainer">

From cfaa2c2ab4c9bd0f0347a0e1614a2ddf31ab618c Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Tue, 10 Dec 2024 14:58:06 +1100
Subject: [PATCH 13/30] Support blanks and allow them to be excluded from the
 average #3369

---
 grails-app/assets/javascripts/services.js     | 29 +++++++++++++++----
 grails-app/views/organisation/_admin.gsp      |  2 +-
 .../{_budget.gsp => _funding.gsp}             |  2 +-
 .../adhoc/disableRcsReportingTemporarily.js   | 27 +++++++++++++++++
 4 files changed, 53 insertions(+), 7 deletions(-)
 rename grails-app/views/organisation/{_budget.gsp => _funding.gsp} (84%)
 create mode 100644 src/main/scripts/releases/4.1/adhoc/disableRcsReportingTemporarily.js

diff --git a/grails-app/assets/javascripts/services.js b/grails-app/assets/javascripts/services.js
index 45d87c6fe..bf37e74ef 100644
--- a/grails-app/assets/javascripts/services.js
+++ b/grails-app/assets/javascripts/services.js
@@ -83,10 +83,29 @@ function OrganisationServicesViewModel(serviceIds, allServices, outputTargets, p
             if (periods.length === 0)
                 return;
 
-            var sum = sumOfPeriodTargets();
-            var avg = sum / periods.length;
+
+            // TODO make the behaviour of the total configurable so we can make this more reusable
+            var avg = averageOfPeriodTargets();
+
             target.target(avg);
         }
+
+        function averageOfPeriodTargets() {
+            // For RCS reporting, targets are evaluated each year so we don't want to include undefined
+            // targets in the average.
+            var sum = 0;
+            var count = 0;
+            _.each(target.periodTargets, function (periodTarget) {
+                var target = parseInt(periodTarget.target());
+                if (!_.isNaN(target)) {
+                    sum += target
+                    count++;
+                }
+            });
+
+            return sum/count;
+        }
+
         function sumOfPeriodTargets() {
             var sum = 0;
             _.each(target.periodTargets, function (periodTarget) {
@@ -113,14 +132,14 @@ function OrganisationServicesViewModel(serviceIds, allServices, outputTargets, p
                 return target.scoreId() == outputTarget.scoreId;
             });
             _.each(periods, function (period, i) {
-                var periodTarget = 0;
+                var periodTarget = null;
                 if (currentTarget) {
                     var currentPeriodTarget = _.find(currentTarget.periodTargets || [], function (periodTarget) {
-                        return periodTarget.period == period;
+                        return periodTarget.period == period.value;
                     }) || {};
                     periodTarget = currentPeriodTarget.target;
                 }
-                target.periodTargets[i].target(periodTarget || 0);
+                target.periodTargets[i].target(periodTarget);
                 // subscribe after setting the initial value
                 !subscribed && target.periodTargets[i].target.subscribe(evaluateAndAssignAverage);
             });
diff --git a/grails-app/views/organisation/_admin.gsp b/grails-app/views/organisation/_admin.gsp
index 70b08edbf..042795d82 100644
--- a/grails-app/views/organisation/_admin.gsp
+++ b/grails-app/views/organisation/_admin.gsp
@@ -157,7 +157,7 @@
         <g:if test="${showTargets}">
         <div id="organisation-targets" class="tab-pane">
             <h3>Total funding</h3>
-            <g:render template="/organisation/budget"/>
+            <g:render template="/organisation/funding"/>
             <h3>Service Targets</h3>
             <g:render template="/organisation/serviceTargets" model="[services:organisation.services, periods:organisation.periods, showTargetDate:organisation.showTargetDate]"/>
         </div>
diff --git a/grails-app/views/organisation/_budget.gsp b/grails-app/views/organisation/_funding.gsp
similarity index 84%
rename from grails-app/views/organisation/_budget.gsp
rename to grails-app/views/organisation/_funding.gsp
index da7a4dc4f..bb870c7d3 100644
--- a/grails-app/views/organisation/_budget.gsp
+++ b/grails-app/views/organisation/_funding.gsp
@@ -1,7 +1,7 @@
 <!-- ko with:reportingTargetsAndFunding().funding -->
 <div class="funding">
 
-    <label><b>Funding</b><fc:iconHelp title="Project Budget">${budgetHelpText?:"Enter the budget as reviewed each year"}</fc:iconHelp></label>
+    <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>
diff --git a/src/main/scripts/releases/4.1/adhoc/disableRcsReportingTemporarily.js b/src/main/scripts/releases/4.1/adhoc/disableRcsReportingTemporarily.js
new file mode 100644
index 000000000..4155b04dd
--- /dev/null
+++ b/src/main/scripts/releases/4.1/adhoc/disableRcsReportingTemporarily.js
@@ -0,0 +1,27 @@
+
+load( "../../../utils/audit.js");
+
+let adminUserId = '<system>'
+
+function findRcsReportConfig(reports) {
+    for (let i=0; i<reports.length; i++) {
+        if (reports[i].activityType == 'Regional Capacity Services Report') {
+            return reports[i];
+        }
+    }
+    return null;
+}
+db.organisation.find({'config.organisationReports':{$exists:true}}).forEach(function(org){
+    let rcsReportConfig = findRcsReportConfig(org.config.organisationReports);
+    if (!rcsReportConfig) {
+        return;
+    }
+    rcsReportConfig.description = "Changes are being made to the Regional Capacity Services Reporting template so editing has been temporarily disabled for the Q2 report.  Providers will be notified once the report is ready to be accessed again";
+
+    //org.config.serviceTargets = serviceTargetsConfig;
+    print("updating organisation "+org.name+' , id: '+org.organisationId);
+    db.organisation.replaceOne({organisationId:org.organisationId}, org);
+    audit(org, org.organisationId, 'au.org.ala.ecodata.Organisation', adminUserId, undefined, 'Update');
+});
+
+db.report.updateMany({activityType:'Regional Capacity Services Report', toDate:ISODate('2024-12-31T13:00:00Z')}, {$set:{status:'readonly'}});
\ No newline at end of file

From 952f90b0ee3ce218d0d5e97b70f832d8744036d6 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Tue, 10 Dec 2024 15:11:49 +1100
Subject: [PATCH 14/30] Add user feedback to save #3369

---
 grails-app/assets/javascripts/organisation.js | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/grails-app/assets/javascripts/organisation.js b/grails-app/assets/javascripts/organisation.js
index 508c71916..7dc1a673f 100644
--- a/grails-app/assets/javascripts/organisation.js
+++ b/grails-app/assets/javascripts/organisation.js
@@ -763,8 +763,14 @@ OrganisationPageViewModel = function (props, options) {
 
     self.saveCustomFields = function() {
         if ($("#organisation-targets > table").validationEngine('validate')) {
+            blockUIWithMessage("Saving organisation data...");
             var json = JSON.parse(self.reportingTargetsAndFunding().modelAsJSON());
-            return saveOrganisation(json);
+            saveOrganisation(json).done(function() {
+                blockUIWithMessage("Organisation data saved...");
+                setTimeout($.unblockUI, 1000);
+            }).fail(function(){
+                $.unblockUI();
+            });
         }
     };
 

From df6d1eb5a7452020faaaf3959fd98a9849d134a3 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Tue, 10 Dec 2024 16:07:24 +1100
Subject: [PATCH 15/30] Added funding for report year to form context #3369

---
 ...CapacityServicesReportLifecycleListener.groovy | 15 ++++++++++++++-
 .../merit/reports/ReportLifecycleListener.groovy  |  4 +++-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy b/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy
index 1241eba5b..69eeecdcc 100644
--- a/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy
+++ b/src/main/groovy/au/org/ala/merit/reports/RegionalCapacityServicesReportLifecycleListener.groovy
@@ -13,7 +13,20 @@ class RegionalCapacityServicesReportLifecycleListener extends ReportLifecycleLis
     Map getContextData(Map organisation, Map report) {
         List outputTargets = organisation.custom?.details?.services?.targets
         Map periodTargets = getTargetsForReportPeriod(report, outputTargets)
-        BigDecimal funding = new BigDecimal(0) // TODO get budget from the correct period of the budget table
+        def funding = getFundingForPeriod(organisation, report)
         [periodTargets:periodTargets, totalContractValue:funding]
     }
+
+    private static def 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
+
+    }
 }
diff --git a/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy b/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy
index c606e7536..546842b41 100644
--- a/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy
+++ b/src/main/groovy/au/org/ala/merit/reports/ReportLifecycleListener.groovy
@@ -102,7 +102,9 @@ class ReportLifecycleListener {
             String name = scoreName(scoreId) ?: scoreId
             String previousPeriod = ''
             Map matchingPeriodTarget = outputTarget?.periodTargets?.find { Map periodTarget ->
-                previousPeriod < endDate && periodTarget.period >= endDate
+                boolean result = previousPeriod < endDate && periodTarget.period >= endDate
+                previousPeriod = periodTarget.period
+                result
             }
             [(name): matchingPeriodTarget?.target]
         }

From 02516297f0ee577e3d366807b4c6398703f8dc02 Mon Sep 17 00:00:00 2001
From: temi <temi.varghese@csiro.au>
Date: Tue, 10 Dec 2024 16:18:12 +1100
Subject: [PATCH 16/30] #3369 RCS form change to pre-populate target and budget
 information

---
 .../other/regionalCapacityServicesReport.json | 41 ++++++++++++++-----
 1 file changed, 30 insertions(+), 11 deletions(-)

diff --git a/forms/other/regionalCapacityServicesReport.json b/forms/other/regionalCapacityServicesReport.json
index d1c7b9f5f..b68f48cc2 100644
--- a/forms/other/regionalCapacityServicesReport.json
+++ b/forms/other/regionalCapacityServicesReport.json
@@ -20,6 +20,28 @@
                   "source-path": "periodTargets.targetIndigenousParticipationPercentage"
                 }
               ]
+            },
+            {
+              "source": {
+                "context-path": "owner"
+              },
+              "mapping": [
+                {
+                  "target": "targetIndigenousProcurementPercentage",
+                  "source-path": "periodTargets.targetIndigenousProcurementPercentage"
+                }
+              ]
+            },
+            {
+              "source": {
+                "context-path": "owner"
+              },
+              "mapping": [
+                {
+                  "target": "organisationPanelProjectValue",
+                  "source-path": "totalContractValue"
+                }
+              ]
             }
           ],
         "dataModel": [
@@ -217,10 +239,7 @@
             "dataType": "number",
             "name": "organisationPanelProjectValue",
             "description": "Enter the total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
-            "validate": "required,min[0]",
-            "computed": {
-              "expression": "owner.totalContractValue"
-            }
+            "validate": "required,min[0]"
           },
           {
             "dataType": "number",
@@ -246,10 +265,7 @@
           {
             "dataType": "number",
             "name": "targetIndigenousProcurementPercentage",
-            "description": "Target Indigenous Procurement Percentage for this reporting period",
-            "computed": {
-              "expression": "owner.targetIndigenousProcurementPercentage"
-            }
+            "description": "Target Indigenous Procurement Percentage for this reporting period"
           },
           {
             "name": "staffDevelopmentOpportunities",
@@ -616,7 +632,8 @@
                 "css": "span7",
                 "preLabel": "Target Workforce Performance % this reporting period",
                 "source": "targetIndigenousParticipationPercentage",
-                "type": "number"
+                "type": "number",
+                "readonly": true
               }
             ]
           },
@@ -627,7 +644,8 @@
                 "css": "span7",
                 "preLabel": "What is your total organisation panel/project $ value (GST inclusive)?",
                 "source": "organisationPanelProjectValue",
-                "type": "number"
+                "type": "number",
+                "readonly": true
               }
             ]
           },
@@ -660,7 +678,8 @@
                 "css": "span7",
                 "preLabel": "Target Supply Chain Performance % this reporting period",
                 "source": "targetIndigenousProcurementPercentage",
-                "type": "number"
+                "type": "number",
+                "readonly": true
               }
             ]
           },

From 76f899c4f6a473ba0d790809fc6c67dc81fe968b Mon Sep 17 00:00:00 2001
From: temi <temi.varghese@csiro.au>
Date: Tue, 10 Dec 2024 16:43:18 +1100
Subject: [PATCH 17/30] #3369 updated RCS form version with indigenous
 participation questions

---
 .../regionalCapacityServicesReportV1.json     | 1037 +++++++++++++++++
 .../regionalCapacityServicesReportV2.json     |  953 +++++++++++++++
 ... => regionalCapacityServicesReportV3.json} |    0
 3 files changed, 1990 insertions(+)
 create mode 100644 forms/other/regionalCapacityServicesReportV1.json
 create mode 100644 forms/other/regionalCapacityServicesReportV2.json
 rename forms/other/{regionalCapacityServicesReport.json => regionalCapacityServicesReportV3.json} (100%)

diff --git a/forms/other/regionalCapacityServicesReportV1.json b/forms/other/regionalCapacityServicesReportV1.json
new file mode 100644
index 000000000..3615c860c
--- /dev/null
+++ b/forms/other/regionalCapacityServicesReportV1.json
@@ -0,0 +1,1037 @@
+{
+  "id": "651f661ecd13e43eb3f6d737",
+  "dateCreated": "2023-10-06T01:42:54Z",
+  "minOptionalSectionsCompleted": 1,
+  "supportsSites": false,
+  "tags": [],
+  "lastUpdated": "2024-04-19T03:58:53Z",
+  "createdUserId": "129333",
+  "external": false,
+  "activationDate": null,
+  "supportsPhotoPoints": false,
+  "publicationStatus": "published",
+  "externalIds": null,
+  "gmsId": null,
+  "name": "Regional Capacity Services Report",
+  "sections": [
+    {
+      "collapsedByDefault": false,
+      "template": {
+        "pre-populate": [
+          {
+            "source": {
+              "context-path": "owner"
+            },
+            "mapping": [
+              {
+                "target": "targetIndigenousParticipationPercentage",
+                "source-path": "periodTargets.targetIndigenousParticipationPercentage"
+              }
+            ]
+          },
+          {
+            "source": {
+              "context-path": "owner"
+            },
+            "mapping": [
+              {
+                "target": "targetIndigenousProcurementPercentage",
+                "source-path": "periodTargets.targetIndigenousProcurementPercentage"
+              }
+            ]
+          },
+          {
+            "source": {
+              "context-path": "owner"
+            },
+            "mapping": [
+              {
+                "target": "organisationPanelProjectValue",
+                "source-path": "totalContractValue"
+              }
+            ]
+          }
+        ],
+        "dataModel": [
+          {
+            "name": "governanceAndFinancialFrameworksOnTrack",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "governanceAndFinancialFrameworksActions",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "governanceAndFinancialFrameworksOnTrack == \"No\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "whsUndergoneReview",
+            "description": "Service Providers shall review the organisation's WHS management system (Manual/plan or policy) at planned intervals (not exceeding 3 years) to ensure its continuing suitability, adequacy, and effectiveness. Trigger to warrant a review may include:<ul><li>Change in Executive Staff</li><li>Changes in Legislation</li><li>As a result of an incident or event</li><li>As a result of an internal or external audit or assurance activity.</li></ul>",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "whsChangesDescription",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "whsUndergoneReview == \"Yes\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "whsRevisedSubmitted",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ],
+            "behaviour": [
+              {
+                "condition": "whsUndergoneReview == \"Yes\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "whsRevisedPlan",
+            "dataType": "text",
+            "behaviour": [
+              {
+                "condition": "whsRevisedSubmitted == \"No\"",
+                "type": "visible"
+              }
+            ]
+          },
+          {
+            "name": "whsIncidentsOccured",
+            "description": "A WHS Notifiable incident is defined within Work Health and Safety Act (Cth) 2011 Part 3 – Incident Notification.<br>Refer to Deed of Standing offer clause 42 Work Health and Safety for further information on these requirements.",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "whsIncidentReported",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ],
+            "behaviour": [
+              {
+                "condition": "whsIncidentsOccured == \"Yes\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "whsIncidentNotReported",
+            "dataType": "text",
+            "behaviour": [
+              {
+                "condition": "whsIncidentReported == \"No\"",
+                "type": "visible"
+              }
+            ]
+          },
+          {
+            "name": "deedOfStandingOfferActions",
+            "description": "This schedule relates to additional conditions relating to your Indigenous Participation Plan identified as part of your offer.",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]"
+          },
+          {
+            "name": "indigenousTargetMeasuresOnTrack",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "indigenousTargetMeasuresNotOnTrack",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "indigenousTargetMeasuresOnTrack == \"No\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "dataType": "boolean",
+            "name": "reportedToIpprs",
+            "description": "",
+            "validate": "required"
+          },
+          {
+            "dataType": "document",
+            "name": "ipprsReport",
+            "description": ""
+          },
+          {
+            "dataType": "number",
+            "name": "organisationFteWorkforce",
+            "description": "Enter total RDP staff (full time equivalent) deployed on Services for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts) during this reporting period.",
+            "validate": [
+              {
+                "rule": "required"
+              },
+              {
+                "rule": "min[0]"
+              },
+              {
+                "param": {
+                  "expression": "organisationFteIndigenousWorkforce",
+                  "type": "computed"
+                },
+                "rule": "min",
+                "message": "Numeric value must be higher or equal to the numeric value entered for 'What was your organisation’s full time equivalent Indigenous workforce deployed on the Services this reporting period?'"
+              }
+            ]
+          },
+          {
+            "dataType": "number",
+            "name": "organisationFteIndigenousWorkforce",
+            "description": "Enter total RDP Indigenous staff (full time equivalent) deployed on Services for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts) during this reporting period.",
+            "validate": [
+              {
+                "rule": "required"
+              },
+              {
+                "rule": "min[0]"
+              },
+              {
+                "param": {
+                  "expression": "organisationFteWorkforce",
+                  "type": "computed"
+                },
+                "rule": "max",
+                "message": "Numeric value must be less than or equal to the numeric value entered for 'What was your organisation’s full time equivalent workforce deployed on the Services this reporting period?'"
+              }
+            ]
+          },
+          {
+            "dataType": "number",
+            "name": "servicesContractedValueFirstNations",
+            "description": "Enter total dollar value (GST inclusive) of goods and services contracted to First Nations people/Indigenous enterprises, during this reporting period, for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
+            "validate": [
+              {
+                "rule": "required"
+              },
+              {
+                "rule": "min[0]"
+              },
+              {
+                "param": {
+                  "expression": "organisationPanelProjectValue",
+                  "type": "computed"
+                },
+                "rule": "max",
+                "message": "The sum of the value of Services contracted to First Nations people/Indigenous enterprises for all reporting periods can not exceed the total $ value (GST inclusive) of the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts)"
+              }
+            ]
+          },
+          {
+            "dataType": "number",
+            "name": "organisationPanelProjectValue",
+            "description": "Enter the total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
+            "validate": "required,min[0]"
+          },
+          {
+            "dataType": "number",
+            "name": "supplyChainPerformancePercentage",
+            "description": "Calculated using value of Services contracted to First Nations people/Indigenous enterprises ÷ stored Total Organisation Panel/Project Value $ x 100",
+            "computed": {
+              "expression": "servicesContractedValueFirstNations / organisationPanelProjectValue * 100"
+            }
+          },
+          {
+            "dataType": "number",
+            "name": "workforcePerformancePercentage",
+            "description": "Calculated using FTE Indigenous workforce ÷ FTE workforce x 100",
+            "computed": {
+                "expression": "organisationFteIndigenousWorkforce / organisationFteWorkforce * 100"
+            }
+          },
+          {
+            "dataType": "number",
+            "name": "targetIndigenousParticipationPercentage",
+            "description": "Target Indigenous Participation Percentage for this reporting period"
+          },
+          {
+            "dataType": "number",
+            "name": "targetIndigenousProcurementPercentage",
+            "description": "Target Indigenous Procurement Percentage for this reporting period"
+          },
+          {
+            "name": "staffDevelopmentOpportunities",
+            "description": "This includes on-the-job, informal and formal training.",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]"
+          },
+          {
+            "name": "communityTargetMeasuresOnTrack",
+            "dataType": "text",
+            "description": "If your plan is in development select ‘no’ and explain when the plan will be completed ",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "communityTargetMeasuresNotOnTrack",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "communityTargetMeasuresOnTrack == \"No\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "communityConductWorkshops",
+            "dataType": "text",
+            "description": "",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "columns": [
+              {
+                "name": "workshopEventType",
+                "dataType": "text",
+                "validate": "required,maxSize[500]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communityConductWorkshops == \"Yes\""
+                  }
+                ]
+              },
+              {
+                "name": "workshopTitle",
+                "dataType": "text",
+                "validate": "required,maxSize[500]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communityConductWorkshops == \"Yes\""
+                  }
+                ]
+              },
+              {
+                "name": "workshopPurpose",
+                "dataType": "text",
+                "validate": "required,maxSize[5000]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communityConductWorkshops == \"Yes\""
+                  }
+                ]
+              },
+              {
+                "name": "workshopDate",
+                "dataType": "date",
+                "validate": "required",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communityConductWorkshops == \"Yes\""
+                  }
+                ]
+              }
+            ],
+            "dataType": "list",
+            "name": "workshopList"
+          },
+          {
+            "name": "supportCommunityWorkshops",
+            "dataType": "text",
+            "description": "",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "columns": [
+              {
+                "name": "supportWorkshopEventType",
+                "dataType": "text",
+                "validate": "required,maxSize[500]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "supportCommunityWorkshops == \"Yes\""
+                  }
+                ]
+              },
+              {
+                "name": "supportWorkshopTitle",
+                "dataType": "text",
+                "validate": "required,maxSize[500]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "supportCommunityWorkshops == \"Yes\""
+                  }
+                ]
+              },
+              {
+                "name": "supportWorkshopPurpose",
+                "dataType": "text",
+                "validate": "required,maxSize[5000]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "supportCommunityWorkshops == \"Yes\""
+                  }
+                ]
+              },
+              {
+                "name": "supportWorkshopDate",
+                "dataType": "date",
+                "validate": "required",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "supportCommunityWorkshops == \"Yes\""
+                  }
+                ]
+              }
+            ],
+            "dataType": "list",
+            "name": "supportWorkshopList"
+          },
+          {
+            "name": "communicationsTargetMeasuresOnTrack",
+            "dataType": "text",
+            "description": "If your plan is in development select ‘no’ and explain when the plan will be completed ",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "communicationsTargetMeasuresNotOnTrack",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "communicationsTargetMeasuresOnTrack == \"No\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "communicationMaterialPublished",
+            "dataType": "text",
+            "description": "",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "columns": [
+              {
+                "name": "communicationMaterialLink",
+                "dataType": "text",
+                "validate": "required",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communicationMaterialPublished == \"Yes\""
+                  }
+                ]
+              }
+            ],
+            "dataType": "list",
+            "name": "communicationMaterialLinkList"
+          },
+          {
+            "name": "communicationsMaterialAttachments",
+            "dataType": "list",
+            "columns": [
+              {
+                "name": "communicationsMaterialAttachment",
+                "dataType": "document"
+              }
+            ]
+          }
+        ],
+        "modelName": "Regional capacity services - reporting",
+        "title": "Regional capacity services - reporting",
+        "viewModel": [
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Governance</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "governanceAndFinancialFrameworksOnTrack",
+                "preLabel": "Q1. As a Regional Delivery Partner, have you maintained appropriate governance and financial frameworks as per Regional Capacity Services RCS7?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "governanceAndFinancialFrameworksActions",
+                "preLabel": "a) If not, explain what actions are planned to get back on track."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Work Health and Safety</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "whsUndergoneReview",
+                "preLabel": "Q2. During the reporting period has the Work Health and Safety Management Manual/Plan or Policy undergone a review or amendment?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "whsChangesDescription",
+                "preLabel": "Q2a. Provide a brief description of the changes to the Work Health and Safety Management Manual/Policy or Plan."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span6",
+                "type": "selectOne",
+                "source": "whsRevisedSubmitted",
+                "preLabel": "Q2b. Has the revised WHS plan been submitted to the department?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "",
+                "source": "whsRevisedPlan",
+                "preLabel": "<i><b style=\"background-color:yellow\">Please submit the revised plan to your customer contract manager.</b></i>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "whsIncidentsOccured",
+                "preLabel": "Q3. During the reporting period, have any notifiable incidents or events occurred during the delivery of regional capacity services?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span6",
+                "type": "selectOne",
+                "source": "whsIncidentReported",
+                "preLabel": "Q3a. Has the incident/event been reported to the department and the relevant documentation provided"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "",
+                "source": "whsIncidentNotReported",
+                "preLabel": "<i><b style=\"background-color:yellow\">If not, provide notification as outlined in the Deed of Standing offer clause 42.3 Notifying the customer.</b></i>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Indigenous Participation</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "deedOfStandingOfferActions",
+                "preLabel": "Q4. As a Regional Delivery Partner, explain any actions you’ve taken to address the requirements in Schedule 10 of the Deed of Standing Offer."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "rows": 4,
+                "type": "selectOne",
+                "source": "indigenousTargetMeasuresOnTrack",
+                "preLabel": "Q5. Are you on track to deliver the target measures identified in your Indigenous Participation Plan?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "indigenousTargetMeasuresNotOnTrack",
+                "preLabel": "Q5a. If not, explain what actions are planned to get back on track."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "preLabel": "Q5b. Please confirm that you have reported via the IPPRS this period, and this report has been attached.",
+                "source": "reportedToIpprs",
+                "type": "boolean"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "document",
+                "source": "ipprsReport",
+                "preLabel": "Please attach the document here."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<b>Q5(c). Please complete workforce and supply chain data (Deed of Standing Offer Clause 38 Indigenous Procurement Policy). Please do not provide personal or <span style='white-space: nowrap;'>commercial-in-confidence</span> information when answering this question. Please retain documentation as evidence to support assurance activities undertaken to verify the reported information.</b>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "What was your organisation’s full time equivalent workforce deployed on the Services this reporting period?",
+                "source": "organisationFteWorkforce",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "What was your organisation’s full time equivalent Indigenous workforce deployed on the Services this reporting period?",
+                "source": "organisationFteIndigenousWorkforce",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Workforce Performance % this reporting period",
+                "source": "workforcePerformancePercentage",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Target Workforce Performance % this reporting period",
+                "source": "targetIndigenousParticipationPercentage",
+                "type": "number",
+                "readonly": true
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "What is your total organisation panel/project $ value (GST inclusive)?",
+                "source": "organisationPanelProjectValue",
+                "type": "number",
+                "readonly": true
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "What was the dollar value (GST inclusive) of Services contracted to First Nations people/Indigenous enterprises during this reporting period?",
+                "source": "servicesContractedValueFirstNations",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Supply Chain Performance % this reporting period",
+                "source": "supplyChainPerformancePercentage",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Target Supply Chain Performance % this reporting period",
+                "source": "targetIndigenousProcurementPercentage",
+                "type": "number",
+                "readonly": true
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Monitoring, Evaluation, Reporting and Improvement (MERI)</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "staffDevelopmentOpportunities",
+                "preLabel": "Q6. What MERI professional development opportunities (if any) have staff participated in?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Community Participation</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "communityTargetMeasuresOnTrack",
+                "preLabel": "Q7. As a Regional Delivery Partner are you on track to deliver the target measures identified in your community participation plan?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "communityTargetMeasuresNotOnTrack",
+                "preLabel": "Q7a. If not, explain what actions are planned to get back on track."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "communityConductWorkshops",
+                "preLabel": "Q8. If applicable, did you conduct workshops, or equivalent activities to engage researchers, industry and Community members to define innovative practices and approaches that would improve the condition of natural resources as they relate to the 5-year Outcomes?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "behaviour": [
+              {
+                "type": "if_expression",
+                "condition": "communityConductWorkshops == \"Yes\""
+              }
+            ],
+            "items": [
+              {
+                "disableTableUpload": true,
+                "fixedWidth": true,
+                "columns": [
+                  {
+                    "width": "25%",
+                    "source": "workshopEventType",
+                    "title": "Event type",
+                    "type": "text"
+                  },
+                  {
+                    "width": "25%",
+                    "source": "workshopTitle",
+                    "title": "Title",
+                    "type": "text"
+                  },
+                  {
+                    "width": "25%",
+                    "source": "workshopPurpose",
+                    "title": "Purpose",
+                    "type": "textarea"
+                  },
+                  {
+                    "width": "25%",
+                    "source": "workshopDate",
+                    "title": "Date",
+                    "type": "date"
+                  }
+                ],
+                "userAddedRows": true,
+                "source": "workshopList",
+                "title": "<b>Q8a. If yes, please provide details:<b style=\"color:red\">*</b></b>",
+                "type": "table"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "supportCommunityWorkshops",
+                "preLabel": "Q9. If applicable, did you support community participation workshops, or equivalent activities to engage researchers, industry and Community members to define innovative practices and approaches that would improve the condition of natural resources as they relate to the 5-year Outcomes? This does not include engagement activities covered by project support and overhead services"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "behaviour": [
+              {
+                "type": "if_expression",
+                "condition": "supportCommunityWorkshops == \"Yes\""
+              }
+            ],
+            "items": [
+              {
+                "disableTableUpload": true,
+                "fixedWidth": true,
+                "columns": [
+                  {
+                    "width": "25%",
+                    "source": "supportWorkshopEventType",
+                    "title": "Event type",
+                    "type": "text"
+                  },
+                  {
+                    "width": "25%",
+                    "source": "supportWorkshopTitle",
+                    "title": "Title",
+                    "type": "text"
+                  },
+                  {
+                    "width": "25%",
+                    "source": "supportWorkshopPurpose",
+                    "title": "Purpose",
+                    "type": "textarea"
+                  },
+                  {
+                    "width": "25%",
+                    "source": "supportWorkshopDate",
+                    "title": "Date",
+                    "type": "date"
+                  }
+                ],
+                "userAddedRows": true,
+                "source": "supportWorkshopList",
+                "title": "<b>Q9a. If yes, please provide details:<b style=\"color:red\">*</b></b>",
+                "type": "table"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Communications</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "communicationsTargetMeasuresOnTrack",
+                "preLabel": "Q10. As a Regional Delivery Partner, are you on track to deliver the target measures identified in your communications plan?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "communicationsTargetMeasuresNotOnTrack",
+                "preLabel": "Q10a. If not, explain what actions are planned to get back on track."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "communicationMaterialPublished",
+                "preLabel": "Q11. During this reporting period was any communication material published promoting organisational and project progress and performance, First Nations leadership and engagement and opportunities for community to participate in projects, including through websites and social media, as outlined in RCS3?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "behaviour": [
+              {
+                "type": "if_expression",
+                "condition": "communicationMaterialPublished == \"Yes\""
+              }
+            ],
+            "items": [
+              {
+                "disableTableUpload": true,
+                "fixedWidth": true,
+                "columns": [
+                  {
+                    "source": "communicationMaterialLink",
+                    "title": "Material",
+                    "type": "text"
+                  }
+                ],
+                "userAddedRows": true,
+                "source": "communicationMaterialLinkList",
+                "title": "<b>Q11a. If yes, please provide link or copy of the material (one row per item)<b style=\"color:red\">*</b></b>",
+                "type": "table"
+              },
+              {
+                "type": "table",
+                "source": "communicationsMaterialAttachments",
+                "userAddedRows": true,
+                "disableTableUpload": true,
+                "columns": [
+                  {
+                    "title": "Attached communication material",
+                    "source": "communicationsMaterialAttachment",
+                    "type": "document"
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      },
+      "modelName": null,
+      "templateName": "regionalCapacityServicesReport",
+      "optional": false,
+      "optionalQuestionText": null,
+      "title": null,
+      "collapsibleHeading": null,
+      "name": "Regional capacity services - reporting",
+      "description": null
+    }
+  ],
+  "type": "Report",
+  "category": null,
+  "status": "active",
+  "lastUpdatedUserId": "1493",
+  "description": null,
+  "formVersion": 1
+}
\ No newline at end of file
diff --git a/forms/other/regionalCapacityServicesReportV2.json b/forms/other/regionalCapacityServicesReportV2.json
new file mode 100644
index 000000000..40d6a2364
--- /dev/null
+++ b/forms/other/regionalCapacityServicesReportV2.json
@@ -0,0 +1,953 @@
+{
+  "id": "6683e3d1a31b681e3d2d304c",
+  "dateCreated": "2024-07-02T11:26:09Z",
+  "minOptionalSectionsCompleted": 1,
+  "supportsSites": false,
+  "tags": [],
+  "lastUpdated": "2024-07-02T22:39:07Z",
+  "createdUserId": "1493",
+  "external": false,
+  "activationDate": null,
+  "supportsPhotoPoints": false,
+  "publicationStatus": "unpublished",
+  "externalIds": null,
+  "gmsId": null,
+  "name": "Regional Capacity Services Report",
+  "sections": [
+    {
+      "collapsedByDefault": false,
+      "template": {
+        "pre-populate": [
+          {
+            "source": {
+              "context-path": "owner"
+            },
+            "mapping": [
+              {
+                "target": "targetIndigenousParticipationPercentage",
+                "source-path": "periodTargets.targetIndigenousParticipationPercentage"
+              }
+            ]
+          },
+          {
+            "source": {
+              "context-path": "owner"
+            },
+            "mapping": [
+              {
+                "target": "targetIndigenousProcurementPercentage",
+                "source-path": "periodTargets.targetIndigenousProcurementPercentage"
+              }
+            ]
+          },
+          {
+            "source": {
+              "context-path": "owner"
+            },
+            "mapping": [
+              {
+                "target": "organisationPanelProjectValue",
+                "source-path": "totalContractValue"
+              }
+            ]
+          }
+        ],
+        "dataModel": [
+          {
+            "name": "governanceAndFinancialFrameworksOnTrack",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "governanceAndFinancialFrameworksActions",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "governanceAndFinancialFrameworksOnTrack == \"No\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "whsUndergoneReview",
+            "description": "An RDP must ensure its WHS Plan is up to date and conduct regular reviews to ensure its continuing suitability, adequacy, and effectiveness. Triggers to warrant a review include:<ul><li>In the event of a new Contract or significant change to these scope of an existing Contract</li><li>Change in Executive Staff</li><li>Changes in Legislation</li><li>As a result of an incident or event</li><li>As a result of an internal or external audit or assurance activity.</li></ul>",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "whsChangesDescription",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "whsUndergoneReview == \"Yes\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "whsRevisedSubmitted",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ],
+            "behaviour": [
+              {
+                "condition": "whsUndergoneReview == \"Yes\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "whsRevisedPlan",
+            "dataType": "text",
+            "behaviour": [
+              {
+                "condition": "whsRevisedSubmitted == \"No\"",
+                "type": "visible"
+              }
+            ]
+          },
+          {
+            "name": "whsIncidentsOccured",
+            "description": "Refer to Deed of Standing Offer (Clause 42 Work Health and Safety) for further information on these requirements.",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "whsIncidentReported",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ],
+            "behaviour": [
+              {
+                "condition": "whsIncidentsOccured == \"Yes\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "whsIncidentNotReported",
+            "dataType": "text",
+            "behaviour": [
+              {
+                "condition": "whsIncidentReported == \"No\"",
+                "type": "visible"
+              }
+            ]
+          },
+          {
+            "name": "deedOfStandingOfferActions",
+            "description": "This Schedule relates to additional conditions that apply to your organisation’s Indigenous Participation Plan. If your organisation’s Deed of Standing Offer does not include a Schedule 10, enter ‘Not applicable’.",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]"
+          },
+          {
+            "name": "indigenousTargetMeasuresOnTrack",
+            "dataType": "text",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "indigenousTargetMeasuresNotOnTrack",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "indigenousTargetMeasuresOnTrack == \"No\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "dataType": "boolean",
+            "name": "reportedToIpprs",
+            "description": ""
+          },
+          {
+            "dataType": "document",
+            "name": "ipprsReport",
+            "description": ""
+          },
+          {
+            "dataType": "number",
+            "name": "organisationFteWorkforce",
+            "description": "Enter total RDP staff (full time equivalent) deployed on Services for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts) during this reporting period.",
+            "validate": [
+              {
+                "rule": "required"
+              },
+              {
+                "rule": "min[0]"
+              },
+              {
+                "param": {
+                  "expression": "organisationFteIndigenousWorkforce",
+                  "type": "computed"
+                },
+                "rule": "min",
+                "message": "Numeric value must be higher or equal to the numeric value entered for 'What was your organisation’s full time equivalent Indigenous workforce deployed on the Services this reporting period?'"
+              }
+            ]
+          },
+          {
+            "dataType": "number",
+            "name": "organisationFteIndigenousWorkforce",
+            "description": "Enter total RDP Indigenous staff (full time equivalent) deployed on Services for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts) during this reporting period.",
+            "validate": [
+              {
+                "rule": "required"
+              },
+              {
+                "rule": "min[0]"
+              },
+              {
+                "param": {
+                  "expression": "organisationFteWorkforce",
+                  "type": "computed"
+                },
+                "rule": "max",
+                "message": "Numeric value must be less than or equal to the numeric value entered for 'What was your organisation’s full time equivalent workforce deployed on the Services this reporting period?'"
+              }
+            ]
+          },
+          {
+            "dataType": "number",
+            "name": "servicesContractedValueFirstNations",
+            "description": "Enter total dollar value (GST inclusive) of goods and services contracted to First Nations people/Indigenous enterprises, during this reporting period, for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
+            "validate": [
+              {
+                "rule": "required"
+              },
+              {
+                "rule": "min[0]"
+              },
+              {
+                "param": {
+                  "expression": "organisationPanelProjectValue",
+                  "type": "computed"
+                },
+                "rule": "max",
+                "message": "The sum of the value of Services contracted to First Nations people/Indigenous enterprises for all reporting periods can not exceed the total $ value (GST inclusive) of the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts)"
+              }
+            ]
+          },
+          {
+            "dataType": "number",
+            "name": "organisationPanelProjectValue",
+            "description": "Enter the total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
+            "validate": "required,min[0]"
+          },
+          {
+            "dataType": "number",
+            "name": "supplyChainPerformancePercentage",
+            "description": "Calculated using value of Services contracted to First Nations people/Indigenous enterprises ÷ stored Total Organisation Panel/Project Value $ x 100",
+            "computed": {
+              "expression": "servicesContractedValueFirstNations / organisationPanelProjectValue * 100"
+            }
+          },
+          {
+            "dataType": "number",
+            "name": "workforcePerformancePercentage",
+            "description": "Calculated using FTE Indigenous workforce ÷ FTE workforce x 100",
+            "computed": {
+              "expression": "organisationFteIndigenousWorkforce / organisationFteWorkforce * 100"
+            }
+          },
+          {
+            "dataType": "number",
+            "name": "targetIndigenousParticipationPercentage",
+            "description": "Target Indigenous Participation Percentage for this reporting period"
+          },
+          {
+            "dataType": "number",
+            "name": "targetIndigenousProcurementPercentage",
+            "description": "Target Indigenous Procurement Percentage for this reporting period"
+          },
+          {
+            "name": "staffDevelopmentOpportunities",
+            "description": "This includes on-the-job, informal, and formal training. If no activities were undertaken, enter ‘Nil’.",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]"
+          },
+          {
+            "name": "communityTargetMeasuresOnTrack",
+            "dataType": "text",
+            "description": "If the plan is in development select ‘No’ and explain when the plan will be completed.",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "communityTargetMeasuresNotOnTrack",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "communityTargetMeasuresOnTrack == \"No\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "communityConductWorkshops",
+            "dataType": "text",
+            "description": "This question relates only to RCS 2.3. It does not include engagement activities covered by Project Support and Overhead Services.",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "columns": [
+              {
+                "name": "workshopEventType",
+                "dataType": "text",
+                "validate": "required,maxSize[500]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communityConductWorkshops == \"Yes\""
+                  }
+                ]
+              },
+              {
+                "name": "workshopTitle",
+                "dataType": "text",
+                "validate": "required,maxSize[500]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communityConductWorkshops == \"Yes\""
+                  }
+                ]
+              },
+              {
+                "name": "workshopPurpose",
+                "dataType": "text",
+                "validate": "required,maxSize[5000]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communityConductWorkshops == \"Yes\""
+                  }
+                ]
+              },
+              {
+                "name": "workshopDate",
+                "dataType": "date",
+                "validate": "required,past[now]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communityConductWorkshops == \"Yes\""
+                  }
+                ]
+              }
+            ],
+            "dataType": "list",
+            "name": "workshopList"
+          },
+          {
+            "name": "communicationsTargetMeasuresOnTrack",
+            "dataType": "text",
+            "description": "If the plan is in development select ‘No’ and explain when the plan will be completed.",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "name": "communicationsTargetMeasuresNotOnTrack",
+            "dataType": "text",
+            "validate": "required,maxSize[5000]",
+            "behaviour": [
+              {
+                "condition": "communicationsTargetMeasuresOnTrack == \"No\"",
+                "type": "enable_and_clear"
+              }
+            ]
+          },
+          {
+            "name": "communicationMaterialPublished",
+            "dataType": "text",
+            "description": "This does not include communication activities undertaken under the ‘Communication Materials’ Project Service or regional capacity service. All Communication Materials must correctly acknowledge Australian Government funding.",
+            "validate": "required",
+            "constraints": [
+              "Yes",
+              "No"
+            ]
+          },
+          {
+            "columns": [
+              {
+                "name": "communicationMaterialLink",
+                "dataType": "text",
+                "validate": "required,maxSize[500]",
+                "behaviour": [
+                  {
+                    "type": "enable_and_clear",
+                    "condition": "communicationMaterialPublished == \"Yes\""
+                  }
+                ]
+              }
+            ],
+            "dataType": "list",
+            "name": "communicationMaterialLinkList"
+          },
+          {
+            "name": "communicationsMaterialAttachments",
+            "dataType": "list",
+            "columns": [
+              {
+                "name": "communicationsMaterialAttachment",
+                "dataType": "document"
+              }
+            ]
+          },
+          {
+            "dataType": "number",
+            "name": "projectDesignRequested",
+            "validate": "integer"
+          },
+          {
+            "dataType": "number",
+            "name": "workOrderExecuted",
+            "validate": "integer"
+          }
+        ],
+        "modelName": "Regional capacity services - reporting",
+        "title": "Regional Capacity Services – Reporting",
+        "viewModel": [
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Governance</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "governanceAndFinancialFrameworksOnTrack",
+                "preLabel": "Q1. As a Regional Delivery Partner, has your organisation maintained appropriate governance and financial frameworks as per Regional Capacity Services RCS 7?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "governanceAndFinancialFrameworksActions",
+                "preLabel": "Q1(a). Explain what actions are planned to get back on track."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Work Health and Safety</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "whsUndergoneReview",
+                "preLabel": "Q2. During the reporting period has the WHS Plan undergone a revision or update? (Clause 42.5(b) of the Deed)"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "whsChangesDescription",
+                "preLabel": "Q2(a). Provide a brief description of the changes to the WHS Plan."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span6",
+                "type": "selectOne",
+                "source": "whsRevisedSubmitted",
+                "preLabel": "Q2(b). Has the revised WHS plan been submitted to the Customer Contract Manager?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "",
+                "source": "whsRevisedPlan",
+                "preLabel": "<i><b style=\"background-color:yellow\">Please submit the revised WHS Plan to the Customer Contract Manager.</b></i>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "whsIncidentsOccured",
+                "preLabel": "Q3. During the reporting period, have any WHS matters that are required to be notified under clause 42.3 of the Deed occurred during the delivery of Regional Capacity Services?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span6",
+                "type": "selectOne",
+                "source": "whsIncidentReported",
+                "preLabel": "Q3(a). Has the incident/event been reported to the Customer Contract Manager and the relevant documentation been provided?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "",
+                "source": "whsIncidentNotReported",
+                "preLabel": "<i><b style=\"background-color:yellow\">Please notify the Customer Contract Manager immediately, as outlined in the Deed of Standing Offer (clause 42.3 Notifying the Customer), to discuss.</b></i>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Indigenous Participation</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "deedOfStandingOfferActions",
+                "preLabel": "Q4. Outline any actions your organisation has taken to address the requirements in Schedule 10 of the Deed of Standing Offer."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "rows": 4,
+                "type": "selectOne",
+                "source": "indigenousTargetMeasuresOnTrack",
+                "preLabel": "Q5. Is your organisation on track to deliver the target measures identified in the Indigenous Participation Plan?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "indigenousTargetMeasuresNotOnTrack",
+                "preLabel": "Q5(a). Please outline what actions are planned to get back on track."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Q5(b). Please confirm that your organisation has reported via the IPPRS this period (Clause 38 Indigenous Procurement Policy), and that this report has been uploaded to the documents tab in MERIT.",
+                "source": "reportedToIpprs",
+                "type": "boolean"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "document",
+                "source": "ipprsReport",
+                "preLabel": "Please attach the document here."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<b>Q5(c). Please complete workforce and supply chain data (Deed of Standing Offer Clause 38 Indigenous Procurement Policy). Please do not provide personal or <span style='white-space: nowrap;'>commercial-in-confidence</span> information when answering this question. Please retain documentation as evidence to support assurance activities undertaken to verify the reported information.</b>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "What was your organisation’s full time equivalent workforce deployed on the Services this reporting period?",
+                "source": "organisationFteWorkforce",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "What was your organisation’s full time equivalent Indigenous workforce deployed on the Services this reporting period?",
+                "source": "organisationFteIndigenousWorkforce",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Workforce Performance % this reporting period",
+                "source": "workforcePerformancePercentage",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Target Workforce Performance % this reporting period",
+                "source": "targetIndigenousParticipationPercentage",
+                "type": "number",
+                "readonly": true
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "What is your total organisation panel/project $ value (GST inclusive)?",
+                "source": "organisationPanelProjectValue",
+                "type": "number",
+                "readonly": true
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "What was the dollar value (GST inclusive) of Services contracted to First Nations people/Indigenous enterprises during this reporting period?",
+                "source": "servicesContractedValueFirstNations",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Supply Chain Performance % this reporting period",
+                "source": "supplyChainPerformancePercentage",
+                "type": "number"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "preLabel": "Target Supply Chain Performance % this reporting period",
+                "source": "targetIndigenousProcurementPercentage",
+                "type": "number",
+                "readonly": true
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Monitoring, Evaluation, Reporting and Improvement (MERI)</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "staffDevelopmentOpportunities",
+                "preLabel": "Q6. What MERI professional development opportunities (if any) have staff participated in? (RCS 4)"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Community Participation</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "communityTargetMeasuresOnTrack",
+                "preLabel": "Q7. Is your organisation on track to deliver the target measures identified in the Community Participation Plan?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "communityTargetMeasuresNotOnTrack",
+                "preLabel": "Q7(a). Please outline what actions are planned to get back on track."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "communityConductWorkshops",
+                "preLabel": "Q8. Did your organisation conduct workshops (or equivalent activities) to engage researchers, industry and Community members to define innovative practices and approaches that would improve the condition of natural resources as they relate to the 5-year Outcomes? (RCS 2.3)"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "behaviour": [
+              {
+                "type": "if_expression",
+                "condition": "communityConductWorkshops == \"Yes\""
+              }
+            ],
+            "items": [
+              {
+                "disableTableUpload": true,
+                "fixedWidth": true,
+                "columns": [
+                  {
+                    "width": "25%",
+                    "source": "workshopEventType",
+                    "title": "Event type",
+                    "type": "text"
+                  },
+                  {
+                    "width": "25%",
+                    "source": "workshopTitle",
+                    "title": "Title",
+                    "type": "text"
+                  },
+                  {
+                    "width": "25%",
+                    "source": "workshopPurpose",
+                    "title": "Purpose",
+                    "type": "textarea"
+                  },
+                  {
+                    "width": "25%",
+                    "source": "workshopDate",
+                    "title": "Date",
+                    "type": "date"
+                  }
+                ],
+                "userAddedRows": true,
+                "source": "workshopList",
+                "title": "<b>Q8(a). Please provide details:<b style=\"color:red\">*</b></b>",
+                "type": "table"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "literal",
+                "source": "<h4>Communications</h4>"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "communicationsTargetMeasuresOnTrack",
+                "preLabel": "Q9. Is your organisation  on track to deliver the target measures identified in the Communications Plan?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "type": "textarea",
+                "rows": 4,
+                "source": "communicationsTargetMeasuresNotOnTrack",
+                "preLabel": "Q9(a). Please outline what actions are planned to get back on track."
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "selectOne",
+                "source": "communicationMaterialPublished",
+                "preLabel": "Q10. During this reporting period, was any communication material published promoting organisational and project progress and performance, First Nations leadership and engagement, and opportunities for the Community to participate in projects, including through websites and social media, as outlined in RCS 3?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "behaviour": [
+              {
+                "type": "if_expression",
+                "condition": "communicationMaterialPublished == \"Yes\""
+              }
+            ],
+            "items": [
+              {
+                "disableTableUpload": true,
+                "fixedWidth": true,
+                "columns": [
+                  {
+                    "source": "communicationMaterialLink",
+                    "title": "Material",
+                    "type": "text"
+                  }
+                ],
+                "userAddedRows": true,
+                "source": "communicationMaterialLinkList",
+                "title": "<b>Q10(a). Please provide a link to, or a copy of, the material (one row per item)<b style=\"color:red\">*</b></b>",
+                "type": "table"
+              },
+              {
+                "type": "table",
+                "source": "communicationsMaterialAttachments",
+                "userAddedRows": true,
+                "disableTableUpload": true,
+                "columns": [
+                  {
+                    "title": "Attached communication material",
+                    "source": "communicationsMaterialAttachment",
+                    "type": "document"
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "number",
+                "source": "projectDesignRequested",
+                "preLabel": "Q11. How many Requests for Project Design did your organisation receive in the last quarter from Customers other than DCCEEW and DAFF (RCS 9.1(c)(i))?"
+              }
+            ]
+          },
+          {
+            "type": "row",
+            "items": [
+              {
+                "css": "span7",
+                "type": "number",
+                "source": "workOrderExecuted",
+                "preLabel": "Q12. How many Work Orders did your organisation execute in the last quarter with Customers other than DCCEEW and DAFF (RCS 9.1(c)(ii))?"
+              }
+            ]
+          }
+        ]
+      },
+      "modelName": null,
+      "templateName": "regionalCapacityServicesReport",
+      "optional": false,
+      "optionalQuestionText": null,
+      "title": null,
+      "collapsibleHeading": null,
+      "name": "Regional capacity services - reporting",
+      "description": null
+    }
+  ],
+  "type": "Report",
+  "category": null,
+  "status": "active",
+  "lastUpdatedUserId": "1493",
+  "description": null,
+  "formVersion": 2
+}
\ No newline at end of file
diff --git a/forms/other/regionalCapacityServicesReport.json b/forms/other/regionalCapacityServicesReportV3.json
similarity index 100%
rename from forms/other/regionalCapacityServicesReport.json
rename to forms/other/regionalCapacityServicesReportV3.json

From 8bd1a2f65418be3c398748d0d3572aa925cc4fda Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Wed, 11 Dec 2024 07:42:39 +1100
Subject: [PATCH 18/30] Only include scores that are output targets #3369

---
 .../services/au/org/ala/merit/OrganisationService.groovy      | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/grails-app/services/au/org/ala/merit/OrganisationService.groovy b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
index f17932c0c..ff01193ad 100644
--- a/grails-app/services/au/org/ala/merit/OrganisationService.groovy
+++ b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
@@ -362,6 +362,10 @@ class OrganisationService {
         List result = []
         if (supportedServices) {
             result = allServices.findAll{ supportedServices.intersect(it.outputs.formName) }
+            result.each {
+                List scores = it.scores?.findAll{Map score -> score.isOutputTarget}
+                it.scores = scores
+            }
         }
 
         result

From 9a9caa5ea9685f475b719b28c1790f884dfac0f9 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Wed, 11 Dec 2024 10:02:53 +1100
Subject: [PATCH 19/30] Validation and label format changes #338

---
 grails-app/assets/javascripts/organisation.js        |  4 ++--
 grails-app/assets/stylesheets/organisation.css       |  6 ++++++
 .../au/org/ala/merit/OrganisationService.groovy      |  2 +-
 .../services/au/org/ala/merit/ReportService.groovy   | 12 +++++++++---
 grails-app/views/organisation/_admin.gsp             |  4 ++--
 grails-app/views/organisation/_funding.gsp           |  2 +-
 grails-app/views/organisation/_serviceTargets.gsp    | 12 ++++++------
 grails-app/views/organisation/index.gsp              |  3 ++-
 src/main/groovy/au/org/ala/merit/DateUtils.groovy    |  5 +++--
 9 files changed, 32 insertions(+), 18 deletions(-)

diff --git a/grails-app/assets/javascripts/organisation.js b/grails-app/assets/javascripts/organisation.js
index 7dc1a673f..114f11367 100644
--- a/grails-app/assets/javascripts/organisation.js
+++ b/grails-app/assets/javascripts/organisation.js
@@ -737,7 +737,7 @@ OrganisationPageViewModel = function (props, options) {
     }
 
     self.attachValidation = function() {
-        $("#organisation-targets > table").validationEngine('attach');
+        $(options.organisationDetailsSelector).validationEngine('attach');
     };
 
     self.saveOrganisationConfiguration = function() {
@@ -762,7 +762,7 @@ OrganisationPageViewModel = function (props, options) {
     };
 
     self.saveCustomFields = function() {
-        if ($("#organisation-targets > table").validationEngine('validate')) {
+        if ($(options.organisationDetailsSelector).validationEngine('validate')) {
             blockUIWithMessage("Saving organisation data...");
             var json = JSON.parse(self.reportingTargetsAndFunding().modelAsJSON());
             saveOrganisation(json).done(function() {
diff --git a/grails-app/assets/stylesheets/organisation.css b/grails-app/assets/stylesheets/organisation.css
index 923090fde..45a31ce4f 100644
--- a/grails-app/assets/stylesheets/organisation.css
+++ b/grails-app/assets/stylesheets/organisation.css
@@ -137,3 +137,9 @@ ul.ui-autocomplete {
     width:7em;
 }
 
+#organisation-details th {
+    white-space: normal;
+    word-wrap: normal;
+    min-width: 7em;
+}
+
diff --git a/grails-app/services/au/org/ala/merit/OrganisationService.groovy b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
index ff01193ad..4d1e6140e 100644
--- a/grails-app/services/au/org/ala/merit/OrganisationService.groovy
+++ b/grails-app/services/au/org/ala/merit/OrganisationService.groovy
@@ -127,7 +127,7 @@ class OrganisationService {
         regenerateOrganisationReports(organisation, organisationReportCategories)
     }
 
-    List<String> generateTargetPeriods(String id) {
+    List<Map> generateTargetPeriods(String id) {
         Map organisation = get(id)
         generateTargetPeriods(organisation)
     }
diff --git a/grails-app/services/au/org/ala/merit/ReportService.groovy b/grails-app/services/au/org/ala/merit/ReportService.groovy
index 25b5c5f79..42eb903cb 100644
--- a/grails-app/services/au/org/ala/merit/ReportService.groovy
+++ b/grails-app/services/au/org/ala/merit/ReportService.groovy
@@ -152,10 +152,16 @@ class ReportService {
     List<Map> generateTargetPeriods(ReportConfig reportConfig, ReportOwner reportOwner, String formatString = null) {
         List<Map> reports = new ReportGenerator().generateReports(
                 reportConfig, reportOwner, 0, null)
-        Closure dateFormatter = {
-            formatString ? DateUtils.format(DateUtils.parse(it), formatString) : it
+        Closure fromDateFormatter = {
+            formatString ? DateUtils.format(DateUtils.parse(it), formatString, DateTimeZone.default) : it
         }
-        reports.collect{[label:dateFormatter(it.toDate), value:it.toDate]}
+        Closure toDateFormatter = {
+            // Compensate for the endDate of a report being 00:00:00 on the following day to have no overlap with the next start time.
+            DateTime toDate = DateUtils.parse(it).minusHours(1)
+            formatString ? DateUtils.format(toDate, formatString, DateTimeZone.default) : it
+        }
+
+        reports.collect{[label:fromDateFormatter(it.fromDate) +' - '+toDateFormatter(it.toDate), value:it.toDate]}
     }
 
     boolean needsRegeneration(Map report1, Map report2) {
diff --git a/grails-app/views/organisation/_admin.gsp b/grails-app/views/organisation/_admin.gsp
index 042795d82..4594311b1 100644
--- a/grails-app/views/organisation/_admin.gsp
+++ b/grails-app/views/organisation/_admin.gsp
@@ -10,7 +10,7 @@
             <a class="nav-link" data-toggle="pill" href="#config" id="config-tab" role="tab">Configuration</a>
         </g:if>
         <g:if test="${showTargets}">
-        <a class="nav-link" data-toggle="pill" href="#organisation-targets" id="organisation-targets-tab" role="tab">Targets</a>
+        <a class="nav-link" data-toggle="pill" href="#organisation-details" id="organisation-details-tab" role="tab">Targets</a>
         </g:if>
     </div>
 
@@ -155,7 +155,7 @@
             <!-- /ko -->
         </g:else>
         <g:if test="${showTargets}">
-        <div id="organisation-targets" class="tab-pane">
+        <div id="organisation-details" class="tab-pane validationEngineContainer">
             <h3>Total funding</h3>
             <g:render template="/organisation/funding"/>
             <h3>Service Targets</h3>
diff --git a/grails-app/views/organisation/_funding.gsp b/grails-app/views/organisation/_funding.gsp
index bb870c7d3..a50350406 100644
--- a/grails-app/views/organisation/_funding.gsp
+++ b/grails-app/views/organisation/_funding.gsp
@@ -18,7 +18,7 @@
         <tr>
             <!-- ko foreach: costs -->
             <td class="budget-amount">
-                <input type="number" class="form-control form-control-sm" data-bind="value: dollar, numeric: $root.number, disable: $root.isProjectDetailsLocked()" data-validation-engine="validate[custom[number]]"/>
+                <input type="number" class="form-control form-control-sm" data-bind="value: dollar, numeric: $root.number, disable: $root.isProjectDetailsLocked()" data-validation-engine="validate[custom[number],min[0]"/>
             </td>
             <!-- /ko -->
         </tr>
diff --git a/grails-app/views/organisation/_serviceTargets.gsp b/grails-app/views/organisation/_serviceTargets.gsp
index f8d6bdc79..eed391963 100644
--- a/grails-app/views/organisation/_serviceTargets.gsp
+++ b/grails-app/views/organisation/_serviceTargets.gsp
@@ -1,20 +1,20 @@
 <!-- ko with:reportingTargetsAndFunding() -->
 <h4>${title ?: "Organisation services and minimum targets"}</h4>
 <!-- ko with: services -->
-<table class="table service-targets validationEngineContainer">
+<table class="table service-targets">
     <thead>
     <tr>
         <th class="index" rowspan="2"></th>
         <th class="service required" rowspan="2">${serviceName ?: "Service"}</th>
         <th class="score required" rowspan="2" style="width: 20px">Target measure</th>
-        <th class="budget-cell required" rowspan="2">Overall target <g:if test="${totalHelpText}"> <fc:iconHelp> ${totalHelpText} </fc:iconHelp></g:if></th>
+        <th class="budget-cell required" rowspan="2">Overall target (%) <g:if test="${totalHelpText}"> <fc:iconHelp> ${totalHelpText} </fc:iconHelp></g:if></th>
         <g:if test="${showTargetDate}">
             <th class="target-date required" rowspan="2">
                 Delivery date <g:if test="${deliveryHelpText}"> <fc:iconHelp> ${deliveryHelpText} </fc:iconHelp> </g:if>
             </th>
         </g:if>
         <!-- ko if: periods && periods.length -->
-        <th data-bind="attr:{colspan:periods.length+1}">${periodTargetsLabel ?: "Targets by date"}</th>
+        <th data-bind="attr:{colspan:periods.length+1}">${periodTargetsLabel ?: " % Targets by date"}</th>
         <!-- /ko -->
     </tr>
     <!-- ko if: periods && periods.length -->
@@ -30,16 +30,16 @@
     <tr>
         <td class="index"><span data-bind="text:$index()+1"></span></td>
         <td class="service">
-            <select class="form-control form-control-sm" data-bind="options: selectableServices, optionsText:'name', optionsValue:'id', optionsCaption: 'Please select', value:serviceId, disable: $root.isProjectDetailsLocked()"
+            <select class="form-control form-control-sm" data-bind="options: selectableServices, optionsText:'name', optionsValue:'id',  value:serviceId, disable: $root.isProjectDetailsLocked()"
                     data-validation-engine="validate[required]"></select>
         </td>
         <td class="score">
-            <select class="form-control form-control-sm" data-bind="options: selectableScores, optionsText:'label', optionsValue:'scoreId', optionsCaption: 'Please select', value:scoreId, disable: $root.isProjectDetailsLocked()"
+            <select class="form-control form-control-sm" data-bind="options: selectableScores, optionsText:'label', optionsValue:'scoreId',  value:scoreId, disable: $root.isProjectDetailsLocked()"
                     data-validation-engine="validate[required]"></select>
         </td>
         <td class="budget-cell">
             <input class="form-control form-control-sm" type="number" disabled data-bind="value: target"
-                   data-validation-engine="validate[min[0.01]]"  data-warningmessage="The sum of the minimum targets must be less than or equal to the overall target">
+                   data-validation-engine="validate[min[0]]"  data-warningmessage="The sum of the minimum targets must be less than or equal to the overall target">
         </td>
 
         <g:if test="${showTargetDate}">
diff --git a/grails-app/views/organisation/index.gsp b/grails-app/views/organisation/index.gsp
index 526a86b1a..9760b2b93 100644
--- a/grails-app/views/organisation/index.gsp
+++ b/grails-app/views/organisation/index.gsp
@@ -86,7 +86,8 @@
                 reportingConfigSelector:'#reporting-config form',
                 availableReportCategories:availableReportCategories,
                 targetPeriods: targetPeriods,
-                services: services
+                services: services,
+                organisationDetailsSelector: '#organisation-details',
 
             }, fcConfig);
 
diff --git a/src/main/groovy/au/org/ala/merit/DateUtils.groovy b/src/main/groovy/au/org/ala/merit/DateUtils.groovy
index cae4a85fb..f80a0df56 100644
--- a/src/main/groovy/au/org/ala/merit/DateUtils.groovy
+++ b/src/main/groovy/au/org/ala/merit/DateUtils.groovy
@@ -135,8 +135,9 @@ class DateUtils {
         dateTime.format(ISO_DATE_FORMATTER)
     }
 
-    static String format(DateTime date, String formatString) {
-        DateTimeFormatter formatter = DateTimeFormat.forPattern(formatString).withZone(date.getZone())
+    static String format(DateTime date, String formatString, DateTimeZone zone = null) {
+        zone = zone ?: date.getZone()
+        DateTimeFormatter formatter = DateTimeFormat.forPattern(formatString).withZone(zone)
         return formatter.print(date)
     }
 

From f4066f5c145a3288c94843c672cb5b8361b50887 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Wed, 11 Dec 2024 10:31:38 +1100
Subject: [PATCH 20/30] label change and targets fixed to 2 decimal places
 #3369

---
 grails-app/assets/javascripts/services.js         | 3 ++-
 grails-app/views/organisation/_serviceTargets.gsp | 4 ++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/grails-app/assets/javascripts/services.js b/grails-app/assets/javascripts/services.js
index bf37e74ef..748ea60e0 100644
--- a/grails-app/assets/javascripts/services.js
+++ b/grails-app/assets/javascripts/services.js
@@ -76,7 +76,8 @@ function OrganisationServicesViewModel(serviceIds, allServices, outputTargets, p
         target.targetDate = ko.observable().extend({simpleDate:false});
 
         target.periodTargets = _.map(periods, function (period) {
-            return {period: period.value, target: ko.observable(0)};
+            // TODO in the future, allowing a score to specifiy the number of decimal places would be useful
+            return {period: period.value, target: ko.observable(0).extend({numericString:2})};
         });
 
         function evaluateAndAssignAverage() {
diff --git a/grails-app/views/organisation/_serviceTargets.gsp b/grails-app/views/organisation/_serviceTargets.gsp
index eed391963..8ce480e9a 100644
--- a/grails-app/views/organisation/_serviceTargets.gsp
+++ b/grails-app/views/organisation/_serviceTargets.gsp
@@ -7,14 +7,14 @@
         <th class="index" rowspan="2"></th>
         <th class="service required" rowspan="2">${serviceName ?: "Service"}</th>
         <th class="score required" rowspan="2" style="width: 20px">Target measure</th>
-        <th class="budget-cell required" rowspan="2">Overall target (%) <g:if test="${totalHelpText}"> <fc:iconHelp> ${totalHelpText} </fc:iconHelp></g:if></th>
+        <th class="budget-cell required" rowspan="2">Overall % target <g:if test="${totalHelpText}"> <fc:iconHelp> ${totalHelpText} </fc:iconHelp></g:if></th>
         <g:if test="${showTargetDate}">
             <th class="target-date required" rowspan="2">
                 Delivery date <g:if test="${deliveryHelpText}"> <fc:iconHelp> ${deliveryHelpText} </fc:iconHelp> </g:if>
             </th>
         </g:if>
         <!-- ko if: periods && periods.length -->
-        <th data-bind="attr:{colspan:periods.length+1}">${periodTargetsLabel ?: " % Targets by date"}</th>
+        <th data-bind="attr:{colspan:periods.length+1}">${periodTargetsLabel ?: " % targets by date"}</th>
         <!-- /ko -->
     </tr>
     <!-- ko if: periods && periods.length -->

From e5ce4c3600f05196473a029b19bead9dfc5e22f7 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Wed, 11 Dec 2024 11:10:06 +1100
Subject: [PATCH 21/30] Reverted 2 decimal places in targets #3369

---
 grails-app/assets/javascripts/services.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/grails-app/assets/javascripts/services.js b/grails-app/assets/javascripts/services.js
index 748ea60e0..bcd8c4582 100644
--- a/grails-app/assets/javascripts/services.js
+++ b/grails-app/assets/javascripts/services.js
@@ -76,8 +76,7 @@ function OrganisationServicesViewModel(serviceIds, allServices, outputTargets, p
         target.targetDate = ko.observable().extend({simpleDate:false});
 
         target.periodTargets = _.map(periods, function (period) {
-            // TODO in the future, allowing a score to specifiy the number of decimal places would be useful
-            return {period: period.value, target: ko.observable(0).extend({numericString:2})};
+             return {period: period.value, target: ko.observable(0)};
         });
 
         function evaluateAndAssignAverage() {

From f036ca96e6e553cb1b7feefc2e18eae7fccc1652 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Wed, 11 Dec 2024 12:02:20 +1100
Subject: [PATCH 22/30] Fixed access to org targets to only officer and above
 #3369

---
 .../controllers/au/org/ala/merit/OrganisationController.groovy  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy b/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
index 0b3c5f519..03bbb1302 100644
--- a/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
+++ b/grails-app/controllers/au/org/ala/merit/OrganisationController.groovy
@@ -80,7 +80,7 @@ class OrganisationController {
             services = organisationService.findApplicableServices(organisation, metadataService.getProjectServices())
             targetPeriods = organisationService.generateTargetPeriods(organisation)
         }
-        boolean showTargets = services != null
+        boolean showTargets = userService.userIsSiteAdmin() && services && targetPeriods
 
         List reportOrder = null
         if (reportingVisible) {

From 129207c83d559de7d284b9109aa71a8fbb5f61df Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Thu, 12 Dec 2024 13:05:01 +1100
Subject: [PATCH 23/30] Minor label changes for #3369

---
 forms/other/regionalCapacityServicesReportV1.json | 4 ++--
 forms/other/regionalCapacityServicesReportV2.json | 4 ++--
 forms/other/regionalCapacityServicesReportV3.json | 4 ++--
 grails-app/views/organisation/_serviceTargets.gsp | 2 +-
 src/main/scripts/releases/4.1/configureIPPRS.js   | 4 ++--
 5 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/forms/other/regionalCapacityServicesReportV1.json b/forms/other/regionalCapacityServicesReportV1.json
index 3615c860c..630d959cb 100644
--- a/forms/other/regionalCapacityServicesReportV1.json
+++ b/forms/other/regionalCapacityServicesReportV1.json
@@ -257,7 +257,7 @@
           {
             "dataType": "number",
             "name": "organisationPanelProjectValue",
-            "description": "Enter the total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
+            "description": "Total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
             "validate": "required,min[0]"
           },
           {
@@ -731,7 +731,7 @@
             "items": [
               {
                 "css": "span7",
-                "preLabel": "What is your total organisation panel/project $ value (GST inclusive)?",
+                "preLabel": "Your total organisation panel/project $ value (GST inclusive).",
                 "source": "organisationPanelProjectValue",
                 "type": "number",
                 "readonly": true
diff --git a/forms/other/regionalCapacityServicesReportV2.json b/forms/other/regionalCapacityServicesReportV2.json
index 40d6a2364..46c731990 100644
--- a/forms/other/regionalCapacityServicesReportV2.json
+++ b/forms/other/regionalCapacityServicesReportV2.json
@@ -256,7 +256,7 @@
           {
             "dataType": "number",
             "name": "organisationPanelProjectValue",
-            "description": "Enter the total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
+            "description": "Total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
             "validate": "required,min[0]"
           },
           {
@@ -681,7 +681,7 @@
             "items": [
               {
                 "css": "span7",
-                "preLabel": "What is your total organisation panel/project $ value (GST inclusive)?",
+                "preLabel": "Your total organisation panel/project $ value (GST inclusive).",
                 "source": "organisationPanelProjectValue",
                 "type": "number",
                 "readonly": true
diff --git a/forms/other/regionalCapacityServicesReportV3.json b/forms/other/regionalCapacityServicesReportV3.json
index b68f48cc2..4ff490454 100644
--- a/forms/other/regionalCapacityServicesReportV3.json
+++ b/forms/other/regionalCapacityServicesReportV3.json
@@ -238,7 +238,7 @@
           {
             "dataType": "number",
             "name": "organisationPanelProjectValue",
-            "description": "Enter the total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
+            "description": "Total dollar value (GST inclusive) for the Deed and all Contracts (including <span style='white-space: nowrap;'>non-DCCEEW</span> Contracts).",
             "validate": "required,min[0]"
           },
           {
@@ -642,7 +642,7 @@
             "items": [
               {
                 "css": "span7",
-                "preLabel": "What is your total organisation panel/project $ value (GST inclusive)?",
+                "preLabel": "Your total organisation panel/project $ value (GST inclusive).",
                 "source": "organisationPanelProjectValue",
                 "type": "number",
                 "readonly": true
diff --git a/grails-app/views/organisation/_serviceTargets.gsp b/grails-app/views/organisation/_serviceTargets.gsp
index 8ce480e9a..e67153002 100644
--- a/grails-app/views/organisation/_serviceTargets.gsp
+++ b/grails-app/views/organisation/_serviceTargets.gsp
@@ -1,5 +1,5 @@
 <!-- ko with:reportingTargetsAndFunding() -->
-<h4>${title ?: "Organisation services and minimum targets"}</h4>
+<h4>${title ?: "Organisation targets"}</h4>
 <!-- ko with: services -->
 <table class="table service-targets">
     <thead>
diff --git a/src/main/scripts/releases/4.1/configureIPPRS.js b/src/main/scripts/releases/4.1/configureIPPRS.js
index b21023c9d..2783f97a8 100644
--- a/src/main/scripts/releases/4.1/configureIPPRS.js
+++ b/src/main/scripts/releases/4.1/configureIPPRS.js
@@ -20,7 +20,7 @@ var scores = [
                 type: "filter"
             },
             "childAggregations": [{
-                "property": "data.workforcePerformancePercentage",
+                "property": "data.tmp.workforcePerformancePercentage",
                     "type": "AVERAGE"
             }]
         },
@@ -44,7 +44,7 @@ var scores = [
                 type: "filter"
             },
             "childAggregations": [{
-                "property": "data.workforcePerformancePercentage",
+                "property": "data.tmp.supplyChainPerformancePercentage",
                 "type": "AVERAGE"
             }]
         },

From b23fa5fd4ec119338d3a77d5515a7dbfc7a1686f Mon Sep 17 00:00:00 2001
From: temi <temi.varghese@csiro.au>
Date: Thu, 12 Dec 2024 14:54:09 +1100
Subject: [PATCH 24/30] ala-security-plugin:6.3.0

---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 4f0856efe..0c5d549ee 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,4 +16,4 @@ org.gradle.jvmargs=-Xmx2048M
 seleniumVersion=3.12.0
 seleniumSafariDriverVersion=3.14.0
 snapshotCacheTime=1800
-alaSecurityLibsVersion=6.3.0-SNAPSHOT
+alaSecurityLibsVersion=6.3.0

From 9b809897e9432478f00ad65469a0f7fadd8f9198 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Thu, 12 Dec 2024 16:19:50 +1100
Subject: [PATCH 25/30] Cache org name search results #3369

---
 .../components/javascript/associated-orgs.js  | 35 +++++++++++--------
 .../assets/stylesheets/organisation.css       |  4 +++
 2 files changed, 25 insertions(+), 14 deletions(-)

diff --git a/grails-app/assets/components/javascript/associated-orgs.js b/grails-app/assets/components/javascript/associated-orgs.js
index 2d90b8221..e103b033d 100644
--- a/grails-app/assets/components/javascript/associated-orgs.js
+++ b/grails-app/assets/components/javascript/associated-orgs.js
@@ -58,24 +58,31 @@ ko.components.register('associated-orgs', {
             this.toDate = ko.observable(associatedOrg.toDate).extend({simpleDate:false});
             this.label = ko.observable(this.name());
 
-            this.updateLabel = function() {
-                if (this.organisationId) {
-                    var self = this;
-                    findMatchingOrganisation(self.organisationId(), function(matchingOrg) {
-                        if (matchingOrg && matchingOrg._source) {
-                            if (matchingOrg._source.name && matchingOrg._source.name != self.name()) {
-                                self.label(self.name() + ' (' + matchingOrg._source.name + ')');
-                            }
-                        }
-                    });
+            var previousOrganisationId = null;
+            var organisationName = null;
+            var self = this;
+            function setLabel() {
+                if (organisationName && organisationName != self.name()) {
+                    self.label(self.name() + ' (' + organisationName + ')');
                 }
                 else {
-                    this.label(this.name());
+                    self.label(self.name());
+                }
+            }
+            function updateLabel() {
+                if (self.organisationId() && self.organisationId() != previousOrganisationId) {
+
+                    findMatchingOrganisation(self.organisationId(), function(matchingOrg) {
+                        previousOrganisationId = self.organisationId();
+                        organisationName = matchingOrg && matchingOrg._source ? matchingOrg._source.name : null;
+                        setLabel();
+                    });
                 }
+                setLabel();
             }
-            this.organisationId.subscribe(this.updateLabel, this);
-            this.name.subscribe(this.updateLabel, this);
-            this.updateLabel();
+            this.organisationId.subscribe(updateLabel, this);
+            this.name.subscribe(updateLabel, this);
+            updateLabel();
 
 
             this.toJSON = function() {
diff --git a/grails-app/assets/stylesheets/organisation.css b/grails-app/assets/stylesheets/organisation.css
index 6437d2437..32a47787c 100644
--- a/grails-app/assets/stylesheets/organisation.css
+++ b/grails-app/assets/stylesheets/organisation.css
@@ -144,3 +144,7 @@ ul.ui-autocomplete {
     min-width: 7em;
 }
 
+ul.ui-menu {
+    list-style: none;
+}
+

From f2766690d8ab990932168949bda15995674a69c9 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Thu, 12 Dec 2024 16:25:33 +1100
Subject: [PATCH 26/30] Cache org name search results #3369

---
 grails-app/assets/components/javascript/associated-orgs.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/grails-app/assets/components/javascript/associated-orgs.js b/grails-app/assets/components/javascript/associated-orgs.js
index e103b033d..4a0b80f76 100644
--- a/grails-app/assets/components/javascript/associated-orgs.js
+++ b/grails-app/assets/components/javascript/associated-orgs.js
@@ -60,6 +60,7 @@ ko.components.register('associated-orgs', {
 
             var previousOrganisationId = null;
             var organisationName = null;
+            var queryInProgress = false;
             var self = this;
             function setLabel() {
                 if (organisationName && organisationName != self.name()) {
@@ -70,12 +71,13 @@ ko.components.register('associated-orgs', {
                 }
             }
             function updateLabel() {
-                if (self.organisationId() && self.organisationId() != previousOrganisationId) {
-
+                if (!queryInProgress && self.organisationId() && self.organisationId() != previousOrganisationId) {
+                    queryInProgress = true;
                     findMatchingOrganisation(self.organisationId(), function(matchingOrg) {
                         previousOrganisationId = self.organisationId();
                         organisationName = matchingOrg && matchingOrg._source ? matchingOrg._source.name : null;
                         setLabel();
+                        queryInProgress = false;
                     });
                 }
                 setLabel();

From b5c77687d3b02bef61f7b95921c2ce9929883d17 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Thu, 12 Dec 2024 16:26:35 +1100
Subject: [PATCH 27/30] Cache org name search results #3369

---
 grails-app/assets/components/javascript/associated-orgs.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/grails-app/assets/components/javascript/associated-orgs.js b/grails-app/assets/components/javascript/associated-orgs.js
index 4a0b80f76..d64425405 100644
--- a/grails-app/assets/components/javascript/associated-orgs.js
+++ b/grails-app/assets/components/javascript/associated-orgs.js
@@ -83,7 +83,7 @@ ko.components.register('associated-orgs', {
                 setLabel();
             }
             this.organisationId.subscribe(updateLabel, this);
-            this.name.subscribe(updateLabel, this);
+            this.name.subscribe(setLabel, this);
             updateLabel();
 
 

From c035a38cf4d64702f6813a87a42cc62b3b774471 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Thu, 12 Dec 2024 16:27:52 +1100
Subject: [PATCH 28/30] Removed business names from org about page #2880

---
 grails-app/views/organisation/_about.gsp | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/grails-app/views/organisation/_about.gsp b/grails-app/views/organisation/_about.gsp
index 47445d3ba..9616f6015 100644
--- a/grails-app/views/organisation/_about.gsp
+++ b/grails-app/views/organisation/_about.gsp
@@ -35,18 +35,12 @@
             </div>
 
             <div class="col-6">
-                <g:if test="${organisation.businessNames}">
-                    <g:each in="${organisation.businessNames}" var="name">
-                        <p>Business name: ${name}</p>
-                    </g:each>
-                </g:if>
-
                 <g:if test="${organisation.orgType}">
                     <p>Organisation type: ${organisation.orgType}</p>
                 </g:if>
                 <g:if test="${organisation.contractNames}">
                     <g:each in="${organisation.contractNames}" var="name">
-                        <p>Contract name: ${name}</p>
+                        <p>Contracted recipient name: ${name}</p>
                     </g:each>
                 </g:if>
 

From 89fb6645b2997fc0f9f1ad48e4dd739e9e138779 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Thu, 12 Dec 2024 17:02:23 +1100
Subject: [PATCH 29/30] Trying to fix list styling in test #2880

---
 grails-app/assets/stylesheets/associated-orgs.css | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/grails-app/assets/stylesheets/associated-orgs.css b/grails-app/assets/stylesheets/associated-orgs.css
index 5ffdb21d6..75518e10b 100644
--- a/grails-app/assets/stylesheets/associated-orgs.css
+++ b/grails-app/assets/stylesheets/associated-orgs.css
@@ -12,3 +12,8 @@
     padding: 5px;
     margin-right: 5px;
 }
+
+ul.ui-menu {
+    list-style: none;
+}
+

From 08d0e576368378c2df0c91ce3af7047e2dfe34d4 Mon Sep 17 00:00:00 2001
From: chrisala <chris.godwin.ala@gmail.com>
Date: Thu, 12 Dec 2024 17:27:22 +1100
Subject: [PATCH 30/30] Trying to fix list styling in test #2880

---
 grails-app/assets/stylesheets/associated-orgs.css | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/grails-app/assets/stylesheets/associated-orgs.css b/grails-app/assets/stylesheets/associated-orgs.css
index 75518e10b..2e408241c 100644
--- a/grails-app/assets/stylesheets/associated-orgs.css
+++ b/grails-app/assets/stylesheets/associated-orgs.css
@@ -15,5 +15,8 @@
 
 ul.ui-menu {
     list-style: none;
+    padding-left: 10px;
 }
 
+
+