Skip to content

Commit

Permalink
Merge pull request #1031 from AtlasOfLivingAustralia/feature/issue994
Browse files Browse the repository at this point in the history
Feature/issue994
  • Loading branch information
temi authored Oct 16, 2024
2 parents 6d9552a + a6c3ee7 commit fe57bd0
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 111 deletions.
8 changes: 0 additions & 8 deletions grails-app/conf/data/mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@
"organisationId": {
"type" : "keyword"
},
"orgIdSvcProvider": {
"type" : "keyword",
"copy_to": ["organisationId"]
},
"organisationName": {
"type" : "text",
"copy_to": ["organisationFacet", "organisationSort"]
Expand All @@ -92,10 +88,6 @@
"dateCreatedSort" : {
"type" : "keyword", "normalizer" : "case_insensitive_sort"
},
"serviceProviderName": {
"type" : "text",
"copy_to": ["organisationName","organisationFacet", "organisationSort"]
},
"associatedOrgs": {
"properties" : {
"name" : {
Expand Down
21 changes: 21 additions & 0 deletions grails-app/domain/au/org/ala/ecodata/AssociatedOrg.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,29 @@ import groovy.transform.ToString
@JsonIgnoreProperties(['metaClass', 'errors', 'expandoMetaClass'])
class AssociatedOrg {

/** Reference to the Organisation entity if ecodata has a record of the Organisation */
String organisationId

/**
* The name of the organisation in the context of the relationship. e.g. it could be a name used
* in a contract with a project that is different from the current business name of the organisation
*/
String name
String logo
String url

/**
* The date the association started. A null date indicates the relationship started at the same
* time as the related entity. e.g. the start of a Project
*/
Date fromDate

/**
* The date the association e ended. A null date indicates the relationship ended at the same
* time as the related entity. e.g. the end of a Project
*/
Date toDate

/** A description of the association - e.g. Service Provider, Grantee, Sponsor */
String description

Expand All @@ -25,8 +43,11 @@ class AssociatedOrg {
organisationId nullable: true
name nullable: true
logo nullable: true

url nullable: true
description nullable: true
fromDate nullable: true
toDate nullable: true
}

}
33 changes: 32 additions & 1 deletion grails-app/domain/au/org/ala/ecodata/Organisation.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,34 @@ class Organisation {
String description
String announcements
String abn
String url
String abnStatus // N/A, Active, Cancelled
String entityName
String sourceSystem // MERIT or Collectory
String entityType // Type code from the ABN register
String orgType // Type name as selected in BioCollect/ Name from the ABN register
List<String> businessNames
String state
Integer postcode
List<ExternalId> externalIds // For financial system vendor codes/reference
List<String> indigenousOrganisationRegistration
List<AssociatedOrg> associatedOrgs // e.g. parent organisation such as for NSW LLS group
List<String> contractNames // When contracts are written for projects with this organisation with a name that doesn't match the organisation name
String status = Status.ACTIVE

String status = 'active'
/** Stores configuration information for how reports should be generated for this organisation (if applicable) */
Map config

String collectoryInstitutionId // Reference to the Collectory

Date dateCreated
Date lastUpdated

static embedded = ['externalIds', 'associatedOrgs']

static mapping = {
organisationId index: true
name index:true
version false
}

Expand All @@ -42,7 +59,21 @@ class Organisation {
announcements nullable: true
description nullable: true
collectoryInstitutionId nullable: true
abnStatus nullable: true
entityName nullable: true
entityType nullable: true
orgType nullable: true
businessNames nullable: true
contractNames nullable: true
state nullable: true
postcode nullable: true
indigenousOrganisationRegistration nullable: true
associatedOrgs nullable: true
abn nullable: true
url nullable: true
config nullable: true
sourceSystem nullable: true
externalIds nullable: true
hubId nullable: true, validator: { String hubId, Organisation organisation, Errors errors ->
GormMongoUtil.validateWriteOnceProperty(organisation, 'organisationId', 'hubId', errors)
}
Expand Down
39 changes: 22 additions & 17 deletions grails-app/services/au/org/ala/ecodata/OrganisationService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ package au.org.ala.ecodata
import com.mongodb.client.MongoCollection
import com.mongodb.client.model.Filters
import grails.validation.ValidationException
import grails.web.databinding.DataBinder
import org.bson.conversions.Bson

import static au.org.ala.ecodata.Status.DELETED

/**
* Works with Organisations, mostly CRUD operations at this point.
*/
class OrganisationService {
class OrganisationService implements DataBinder {

/** Use to include related projects in the toMap method */
public static final String PROJECTS = 'projects'

private static final List EXCLUDE_FROM_BINDING = ['organisationId', 'collectoryInstitutionId', 'status', 'id']

static transactional = 'mongo'
static final FLAT = 'flat'

Expand All @@ -40,23 +43,19 @@ class OrganisationService {
}

def list(levelOfDetail = []) {
return Organisation.findAllByStatusNotEqual('deleted').collect{toMap(it, levelOfDetail)}
return Organisation.findAllByStatusNotEqual(DELETED).collect{toMap(it, levelOfDetail)}
}

def create(Map props, boolean createInCollectory = true) {
def create(Map props, boolean createInCollectory = false) {

def organisation = new Organisation(organisationId: Identifiers.getNew(true, ''), name:props.name)

if (createInCollectory) {
organisation.collectoryInstitutionId = createCollectoryInstitution(props)
}
try {
// name is a mandatory property and hence needs to be set before dynamic properties are used (as they trigger validations)
bindData(organisation, props, [exclude:EXCLUDE_FROM_BINDING])
organisation.save(failOnError: true, flush:true)
props.remove('id')
props.remove('organisationId')
props.remove('collectoryInstitutionId')
commonService.updateProperties(organisation, props)

// Assign the creating user as an admin.
permissionService.addUserAsRoleToOrganisation(userService.getCurrentUserDetails()?.userId, AccessLevel.admin, organisation.organisationId)
Expand Down Expand Up @@ -91,23 +90,30 @@ class OrganisationService {
return institutionId
}

def update(String id, props) {
def update(String id, props, boolean createInCollectory = false) {

def organisation = Organisation.findByOrganisationId(id)
if (organisation) {

try {
String oldName = organisation.name
commonService.updateProperties(organisation, props)
// if no collectory institution exists for this organisation, create one
if (!organisation.collectoryInstitutionId || organisation.collectoryInstitutionId == 'null' || organisation.collectoryInstitutionId == '') {
props.collectoryInstitutionId = createCollectoryInstitution(props)
// We shouldn't be doing this unless the org is attached to a project that exports data
// to the ALA.
if (createInCollectory && (!organisation.collectoryInstitutionId || organisation.collectoryInstitutionId == 'null' || organisation.collectoryInstitutionId == '')) {
organisation.collectoryInstitutionId = createCollectoryInstitution(props)
}
œ
String oldName = organisation.name
List contractNameChanges = props.remove('contractNameChanges')
bindData(organisation, props, [exclude:EXCLUDE_FROM_BINDING])

getCommonService().updateProperties(organisation, props)
if (props.name && (oldName != props.name)) {
projectService.updateOrganisationName(organisation.organisationId, props.name)
projectService.updateOrganisationName(organisation.organisationId, oldName, props.name)
}
contractNameChanges?.each { Map change ->
projectService.updateOrganisationName(organisation.organisationId, change.oldName, change.newName)
}
organisation.save(failOnError:true)
return [status:'ok']
} catch (Exception e) {
Organisation.withSession { session -> session.clear() }
Expand Down Expand Up @@ -136,7 +142,7 @@ class OrganisationService {
if (destroy) {
organisation.delete()
} else {
organisation.status = 'deleted'
organisation.status = DELETED
organisation.save(flush: true, failOnError: true)
}
return [status: 'ok']
Expand All @@ -160,7 +166,6 @@ class OrganisationService {
if ('projects' in levelOfDetail) {
mapOfProperties.projects = []
mapOfProperties.projects += projectService.search([organisationId: org.organisationId], ['flat'])
mapOfProperties.projects += projectService.search([orgIdSvcProvider: org.organisationId], ['flat'])
}
if ('documents' in levelOfDetail) {
mapOfProperties.documents = documentService.findAllByOwner('organisationId', org.organisationId)
Expand Down
59 changes: 50 additions & 9 deletions grails-app/services/au/org/ala/ecodata/ProjectService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,9 @@ class ProjectService {
if (it.organisationId) {
Organisation org = Organisation.findByOrganisationId(it.organisationId)
if (org) {
it.name = org.name
if (!it.name) { // Is this going to cause BioCollect an issue?
it.name = org.name
}
it.url = org.url
it.logo = Document.findByOrganisationIdAndRoleAndStatus(it.organisationId, "logo", ACTIVE)?.thumbnailUrl
}
Expand Down Expand Up @@ -689,15 +691,36 @@ class ProjectService {
List<Map> search(Map searchCriteria, levelOfDetail = []) {

def criteria = Project.createCriteria()

def projects = criteria.list {
ne("status", DELETED)
searchCriteria.each { prop, value ->
// Special case for organisationId - also included embedded associatedOrg relationships.
if (prop == 'organisationId') {
or {
if (value instanceof List) {
inList(prop, value)
} else {
eq(prop, value)
}

if (value instanceof List) {
inList(prop, value)
} else {
eq(prop, value)
associatedOrgs {
if (value instanceof List) {
inList(prop, value)
} else {
eq(prop, value)
}
}
}
}
else {
if (value instanceof List) {
inList(prop, value)
} else {
eq(prop, value)
}
}

}

}
Expand All @@ -721,10 +744,28 @@ class ProjectService {
* @param orgId identifies the organsation that has changed name
* @param orgName the new organisation name
*/
void updateOrganisationName(orgId, orgName) {
Project.findAllByOrganisationIdAndStatusNotEqual(orgId, DELETED).each { project ->
project.organisationName = orgName
project.save()
void updateOrganisationName(String orgId, String oldName, String newName) {
Project.findAllByOrganisationIdAndOrganisationNameAndStatusNotEqual(orgId, oldName, DELETED).each { project ->
project.organisationName = newName
project.save(flush:true)
}

List projects = Project.where {
status != DELETED
associatedOrgs {
organisationId == orgId
name == oldName
}
}.list()


projects?.each { Project project ->
project.associatedOrgs.each { org ->
if (org.organisationId == orgId && org.name == oldName) {
org.name = newName
}
}
project.save(flush:true)
}
}

Expand Down
30 changes: 30 additions & 0 deletions scripts/releases/5.0/updateAssociatedOrgNames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
load('../../utils/audit.js');
let projects = db.project.find({status:{$ne:'deleted'}, associatedOrgs:{$exists:true}, isMERIT:false});
while (projects.hasNext()) {
let changed = false;

let project = projects.next();
let associatedOrgs = project.associatedOrgs;
if (associatedOrgs) {
for (let i = 0; i < associatedOrgs.length; i++) {
if (associatedOrgs[i].organisationId) {
let org = db.organisation.findOne({organisationId: associatedOrgs[i].organisationId});
if (org) {
if (org.name != associatedOrgs[i].name) {
print("Updating associated org for project " + project.projectId + " from " + associatedOrgs[i].name + " to " + org.name);
associatedOrgs[i].name = org.name;
changed = true;
}
} else {
print("No organisation found for associated org " + associatedOrgs[i].organisationId + " in project " + project.projectId);
}

}
}
if (changed) {
db.project.replaceOne({projectId: project.projectId}, project);
audit(project, project.projectId, 'au.org.ala.ecodata.Project', 'system');
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ class OrganisationControllerSpec extends IntegrationTestHelper {
savedOrganisation.organisationId == organisationId
savedOrganisation.name == org.name
savedOrganisation.description == org.description
// savedOrganisation.dynamicProperty == org.dynamicProperty (dynamic properties not working in tests)
savedOrganisation.collectoryInstitutionId == institutionId

and: "the user who created the organisation is an admin of the new organisation"
def orgPermissions = UserPermission.findAllByEntityIdAndEntityType(savedOrganisation.organisationId, Organisation.class.name)
Expand Down Expand Up @@ -119,14 +117,14 @@ class OrganisationControllerSpec extends IntegrationTestHelper {

}

void "projects can be associated with an organisation by the serviceProviderOrganisationId property"() {
void "projects can be associated with an organisation by the associatedOrgs property"() {
setup:

// Create some data for the database.
def organisation = TestDataHelper.buildOrganisation([name: 'org 1'])
def projects = []
(1..2).each {
projects << TestDataHelper.buildProject([orgIdSvcProvider: organisation.organisationId, name:'svc project '+it])
projects << TestDataHelper.buildProject([associatedOrgs: [[organisationId:organisation.organisationId, name:'org project '+it]]])
}
projects << TestDataHelper.buildProject([organisationId: organisation.organisationId, name:'org project'])
(1..3).each {
Expand Down
Loading

0 comments on commit fe57bd0

Please sign in to comment.