Skip to content

Commit

Permalink
Merge pull request #465 from Health-Education-England/feat/responsibl…
Browse files Browse the repository at this point in the history
…eOfficer

Feat: responsible officer - needed resources
  • Loading branch information
ReubenRobertsHEE authored Jul 15, 2024
2 parents da9803a + 256b6d6 commit da78d66
Show file tree
Hide file tree
Showing 30 changed files with 3,180 additions and 39 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
}

group = "uk.nhs.hee.tis.trainee"
version = "1.15.0"
version = "1.16.0"

configurations {
compileOnly {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexOperations;
import uk.nhs.hee.tis.trainee.sync.model.CurriculumMembership;
import uk.nhs.hee.tis.trainee.sync.model.Dbc;
import uk.nhs.hee.tis.trainee.sync.model.HeeUser;
import uk.nhs.hee.tis.trainee.sync.model.LocalOffice;
import uk.nhs.hee.tis.trainee.sync.model.Placement;
import uk.nhs.hee.tis.trainee.sync.model.PlacementSite;
Expand All @@ -38,6 +40,8 @@
import uk.nhs.hee.tis.trainee.sync.model.PostSpecialty;
import uk.nhs.hee.tis.trainee.sync.model.Programme;
import uk.nhs.hee.tis.trainee.sync.model.ProgrammeMembership;
import uk.nhs.hee.tis.trainee.sync.model.UserDesignatedBody;
import uk.nhs.hee.tis.trainee.sync.model.UserRole;

@Configuration
public class MongoConfiguration {
Expand All @@ -53,6 +57,10 @@ public class MongoConfiguration {
*/
@PostConstruct
public void initIndexes() {
// DBC
IndexOperations dbcIndexOps = template.indexOps(Dbc.class);
dbcIndexOps.ensureIndex(new Index().on("data.dbc", Direction.ASC));

// CurriculumMembership
IndexOperations cmIndexOps = template.indexOps(CurriculumMembership.class);
cmIndexOps.ensureIndex(new Index().on("data.programmeId", Direction.ASC));
Expand All @@ -73,6 +81,10 @@ public void initIndexes() {
IndexOperations localOfficeIndexOps = template.indexOps(LocalOffice.class);
localOfficeIndexOps.ensureIndex(new Index().on("data.abbreviation", Direction.ASC));

// HeeUser
IndexOperations heeUserIndexOps = template.indexOps(HeeUser.class);
heeUserIndexOps.ensureIndex(new Index().on("data.name", Direction.ASC));

// Placement
IndexOperations placementIndexOps = template.indexOps(Placement.class);
placementIndexOps.ensureIndex(new Index().on("data.postId", Direction.ASC));
Expand Down Expand Up @@ -157,5 +169,15 @@ public void initIndexes() {
Index programmeMembershipCompoundIndex = new CompoundIndexDefinition(pmKeys)
.named("programmeMembershipCompoundIndex");
programmeMembershipIndexOps.ensureIndex(programmeMembershipCompoundIndex);

// UserDesignatedBody
IndexOperations userDbIndexOps = template.indexOps(UserDesignatedBody.class);
userDbIndexOps.ensureIndex(new Index().on("data.userName", Direction.ASC));
userDbIndexOps.ensureIndex(new Index().on("data.designatedBodyCode", Direction.ASC));

// UserRole
IndexOperations userRoleIndexOps = template.indexOps(UserRole.class);
userRoleIndexOps.ensureIndex(new Index().on("data.userName", Direction.ASC));
userRoleIndexOps.ensureIndex(new Index().on("data.roleName", Direction.ASC));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
@Slf4j
public class DbcEventListener extends AbstractMongoEventListener<Dbc> {

private static final String DBC_NAME = "name";
public static final String DBC_NAME = "name";

private final DbcSyncService dbcSyncService;

Expand Down Expand Up @@ -115,6 +115,13 @@ public void onAfterDelete(AfterDeleteEvent<Dbc> event) {
* @param dbc The DBC to get related programmes for.
*/
private void queueRelatedProgrammes(Dbc dbc) {
//NOTE: refactor this as per: https://hee-tis.atlassian.net/browse/TIS21-6228
//As a Designated body name will no longer be equal to a Local Office name, and the programme
//owner is a Local Office name, the 'findByOwner()' below will become invalid.
//This may require
// (1) sync LocalOffice into sync db as well as reference.
// (2) ensure abbr field is included (its missing from the Reference LocalOffice collection)
// (3) join dbc.abbr <-> LocalOffice.abbreviation and use LocalOffice.name <-> Programme.owner
Set<Programme> programmes =
programmeSyncService.findByOwner(dbc.getData().get(DBC_NAME));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* The MIT License (MIT)
*
* Copyright 2024 Crown Copyright (Health Education England)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package uk.nhs.hee.tis.trainee.sync.event;

import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.AfterDeleteEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
import org.springframework.data.mongodb.core.mapping.event.BeforeDeleteEvent;
import org.springframework.stereotype.Component;
import uk.nhs.hee.tis.trainee.sync.model.HeeUser;
import uk.nhs.hee.tis.trainee.sync.service.DbcSyncService;
import uk.nhs.hee.tis.trainee.sync.service.HeeUserSyncService;

/**
* A listener for HEE user mongo events.
*/
@Slf4j
@Component
public class HeeUserEventListener extends AbstractMongoEventListener<HeeUser> {

public static final String HEE_USER_NAME = "name";

private final HeeUserSyncService heeUserSyncService;
private final DbcSyncService dbcSyncService;

private final Cache cache;

HeeUserEventListener(HeeUserSyncService heeUserSyncService,
DbcSyncService dbcSyncService, CacheManager cacheManager) {
this.heeUserSyncService = heeUserSyncService;
this.dbcSyncService = dbcSyncService;
this.cache = cacheManager.getCache(HeeUser.ENTITY_NAME);
}

@Override
public void onAfterSave(AfterSaveEvent<HeeUser> event) {
super.onAfterSave(event);

HeeUser heeUser = event.getSource();
String userName = heeUser.getData().get(HEE_USER_NAME);

dbcSyncService.resyncProgrammesIfUserIsResponsibleOfficer(userName);
}

/**
* Before deleting a HEE user, ensure it is cached.
*
* @param event The before-delete event for the HEE user.
*/
@Override
public void onBeforeDelete(BeforeDeleteEvent<HeeUser> event) {
super.onBeforeDelete(event);
String id = event.getSource().get("_id").toString();
HeeUser heeUser = cache.get(id, HeeUser.class);

if (heeUser == null) {
Optional<HeeUser> newHeeUser = heeUserSyncService.findById(id);
newHeeUser.ifPresent(psToCache -> cache.put(id, psToCache));
}
}

/**
* Retrieve the deleted HEE user from the cache and sync related programmes if they are a
* responsible officer.
*
* @param event The after-delete event for the HEE user.
*/
@Override
public void onAfterDelete(AfterDeleteEvent<HeeUser> event) {
super.onAfterDelete(event);
String id = event.getSource().get("_id").toString();
HeeUser heeUser = cache.get(id, HeeUser.class);

if (heeUser != null) {
String userName = heeUser.getData().get(HEE_USER_NAME);
dbcSyncService.resyncProgrammesIfUserIsResponsibleOfficer(userName);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* The MIT License (MIT)
*
* Copyright 2024 Crown Copyright (Health Education England)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package uk.nhs.hee.tis.trainee.sync.event;

import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.AfterDeleteEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
import org.springframework.data.mongodb.core.mapping.event.BeforeDeleteEvent;
import org.springframework.stereotype.Component;
import uk.nhs.hee.tis.trainee.sync.model.Dbc;
import uk.nhs.hee.tis.trainee.sync.model.HeeUser;
import uk.nhs.hee.tis.trainee.sync.model.UserDesignatedBody;
import uk.nhs.hee.tis.trainee.sync.service.DbcSyncService;
import uk.nhs.hee.tis.trainee.sync.service.HeeUserSyncService;
import uk.nhs.hee.tis.trainee.sync.service.UserDesignatedBodySyncService;

/**
* A listener for UserDesignatedBody mongo events.
*/
@Slf4j
@Component
public class UserDesignatedBodyEventListener extends
AbstractMongoEventListener<UserDesignatedBody> {

private static final String USER_NAME = "userName";
public static final String DESIGNATED_BODY_CODE = "designatedBodyCode";

private final UserDesignatedBodySyncService userDesignatedBodySyncService;
private final DbcSyncService dbcSyncService;
private final HeeUserSyncService heeUserSyncService;

private final Cache cache;

UserDesignatedBodyEventListener(UserDesignatedBodySyncService userDesignatedBodySyncService,
DbcSyncService dbcSyncService, HeeUserSyncService heeUserSyncService,
CacheManager cacheManager) {
this.userDesignatedBodySyncService = userDesignatedBodySyncService;
this.dbcSyncService = dbcSyncService;
this.heeUserSyncService = heeUserSyncService;
this.cache = cacheManager.getCache(UserDesignatedBody.ENTITY_NAME);
}

@Override
public void onAfterSave(AfterSaveEvent<UserDesignatedBody> event) {
//NOTE: this assumes an update to a UserDesignatedBody record is received as a delete and then
//an insert. Given the composite primary key on both table fields, this seems reasonable but
//needs to be checked, otherwise we could have stale RO details attached to programmes where
//a UserDesignatedBody record is modified to refer to a non-RO user.
super.onAfterSave(event);

UserDesignatedBody userDesignatedBody = event.getSource();
syncOrRequestMissingData(userDesignatedBody, "saved");
}

/**
* Before deleting a user designated body, ensure it is cached.
*
* @param event The before-delete event for the user designated body.
*/
@Override
public void onBeforeDelete(BeforeDeleteEvent<UserDesignatedBody> event) {
super.onBeforeDelete(event);
String id = event.getSource().get("_id").toString();
UserDesignatedBody userDesignatedBody = cache.get(id, UserDesignatedBody.class);

if (userDesignatedBody == null) {
Optional<UserDesignatedBody> newUserDesignatedBody = userDesignatedBodySyncService.findById(
id);
newUserDesignatedBody.ifPresent(udbToCache -> cache.put(id, udbToCache));
}
}

/**
* Retrieve the deleted user designated body from the cache and sync the updated programmes.
*
* @param event The after-delete event for the user designated body.
*/
@Override
public void onAfterDelete(AfterDeleteEvent<UserDesignatedBody> event) {
super.onAfterDelete(event);
String id = event.getSource().get("_id").toString();
UserDesignatedBody userDesignatedBody = cache.get(id, UserDesignatedBody.class);

if (userDesignatedBody != null) {
syncOrRequestMissingData(userDesignatedBody, "deleted");
}
}

/**
* Sync associated DBC programmes, or request missing HEE user or DBC records.
*
* @param userDesignatedBody The user designated body to sync from.
* @param eventContext The event context (for logging purposes).
*/
private void syncOrRequestMissingData(UserDesignatedBody userDesignatedBody,
String eventContext) {
String userNameValue = userDesignatedBody.getData().get(USER_NAME);
String designatedBodyCodeValue = userDesignatedBody.getData().get(DESIGNATED_BODY_CODE);

Optional<HeeUser> optionalHeeUser = heeUserSyncService.findByName(userNameValue);
Optional<Dbc> optionalDbc = dbcSyncService.findByDbc(designatedBodyCodeValue);

if (optionalHeeUser.isPresent() && optionalDbc.isPresent()) {
log.debug("User designated body {} {} and HEE user {} and Dbc found.",
designatedBodyCodeValue, eventContext, userNameValue);
dbcSyncService
.resyncProgrammesForSingleDbcIfUserIsResponsibleOfficer(userNameValue,
designatedBodyCodeValue);
} else {
if (optionalHeeUser.isEmpty()) {
log.info("User designated body {} {} but HEE user {} not found, requesting data.",
designatedBodyCodeValue, eventContext, userNameValue);
heeUserSyncService.request(userNameValue);
}
if (optionalDbc.isEmpty()) {
log.info("User designated body {} {} but Dbc not found, requesting data.",
designatedBodyCodeValue, eventContext);
dbcSyncService.request(designatedBodyCodeValue);
}
}
}
}

Loading

0 comments on commit da78d66

Please sign in to comment.