Skip to content

Commit

Permalink
fix: use tracker importer in EventSavingSMSListener
Browse files Browse the repository at this point in the history
  • Loading branch information
teleivo committed Sep 4, 2024
1 parent 1379999 commit b998c64
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 390 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ protected List<Object> saveEvent(
User user,
List<SmsDataValue> values,
SmsEventStatus eventStatus,
Date eventDate,
Date dueDate,
Date occurredDate,
Date scheduledDate,
GeoPoint coordinates) {
ArrayList<Object> errorUids = new ArrayList<>();

Expand All @@ -163,8 +163,8 @@ protected List<Object> saveEvent(
event.setOrganisationUnit(orgUnit);
event.setProgramStage(programStage);
event.setEnrollment(enrollment);
event.setOccurredDate(eventDate);
event.setScheduledDate(dueDate);
event.setOccurredDate(occurredDate);
event.setScheduledDate(scheduledDate);
event.setAttributeOptionCombo(aoc);
event.setStoredBy(user.getUsername());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,48 +27,58 @@
*/
package org.hisp.dhis.tracker.imports.sms;

import java.time.Instant;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.hisp.dhis.category.CategoryOptionCombo;
import org.hisp.dhis.category.CategoryService;
import org.hisp.dhis.common.IdentifiableObjectManager;
import org.hisp.dhis.common.IllegalQueryException;
import org.hisp.dhis.dataelement.DataElement;
import org.hisp.dhis.dataelement.DataElementService;
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
import org.hisp.dhis.feedback.ForbiddenException;
import org.hisp.dhis.feedback.NotFoundException;
import org.hisp.dhis.fileresource.FileResourceService;
import org.hisp.dhis.event.EventStatus;
import org.hisp.dhis.eventdatavalue.EventDataValue;
import org.hisp.dhis.message.MessageSender;
import org.hisp.dhis.organisationunit.OrganisationUnit;
import org.hisp.dhis.organisationunit.OrganisationUnitService;
import org.hisp.dhis.program.Enrollment;
import org.hisp.dhis.program.Event;
import org.hisp.dhis.program.ProgramService;
import org.hisp.dhis.program.ProgramStage;
import org.hisp.dhis.program.ProgramStageService;
import org.hisp.dhis.sms.incoming.IncomingSms;
import org.hisp.dhis.sms.incoming.IncomingSmsService;
import org.hisp.dhis.sms.listener.CompressionSMSListener;
import org.hisp.dhis.sms.listener.SMSProcessingException;
import org.hisp.dhis.smscompression.SmsConsts.SmsEventStatus;
import org.hisp.dhis.smscompression.SmsConsts.SubmissionType;
import org.hisp.dhis.smscompression.SmsResponse;
import org.hisp.dhis.smscompression.models.GeoPoint;
import org.hisp.dhis.smscompression.models.SmsSubmission;
import org.hisp.dhis.smscompression.models.TrackerEventSmsSubmission;
import org.hisp.dhis.smscompression.models.Uid;
import org.hisp.dhis.system.util.ValidationUtils;
import org.hisp.dhis.trackedentity.TrackedEntityAttributeService;
import org.hisp.dhis.trackedentity.TrackedEntityTypeService;
import org.hisp.dhis.tracker.export.enrollment.EnrollmentService;
import org.hisp.dhis.tracker.export.event.EventChangeLogService;
import org.hisp.dhis.tracker.export.event.EventService;
import org.hisp.dhis.tracker.imports.TrackerImportParams;
import org.hisp.dhis.tracker.imports.TrackerImportService;
import org.hisp.dhis.tracker.imports.TrackerImportStrategy;
import org.hisp.dhis.tracker.imports.domain.Event.EventBuilder;
import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier;
import org.hisp.dhis.tracker.imports.domain.TrackerObjects;
import org.hisp.dhis.tracker.imports.report.ImportReport;
import org.hisp.dhis.tracker.imports.report.Status;
import org.hisp.dhis.user.User;
import org.hisp.dhis.user.UserDetails;
import org.hisp.dhis.user.UserService;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component("org.hisp.dhis.tracker.sms.TrackerEventSMSListener")
@Transactional
public class TrackerEventSMSListener extends EventSavingSMSListener {
private final ProgramStageService programStageService;

private final EnrollmentService enrollmentService;
public class TrackerEventSMSListener extends CompressionSMSListener {
private final TrackerImportService trackerImportService;

public TrackerEventSMSListener(
IncomingSmsService incomingSmsService,
Expand All @@ -81,12 +91,7 @@ public TrackerEventSMSListener(
CategoryService categoryService,
DataElementService dataElementService,
IdentifiableObjectManager identifiableObjectManager,
EventService eventService,
EventChangeLogService eventChangeLogService,
FileResourceService fileResourceService,
DhisConfigurationProvider config,
ProgramStageService programStageService,
EnrollmentService enrollmentService) {
TrackerImportService trackerImportService) {
super(
incomingSmsService,
smsSender,
Expand All @@ -97,68 +102,228 @@ public TrackerEventSMSListener(
organisationUnitService,
categoryService,
dataElementService,
identifiableObjectManager,
eventService,
eventChangeLogService,
fileResourceService,
config);
this.programStageService = programStageService;
this.enrollmentService = enrollmentService;
identifiableObjectManager);
this.trackerImportService = trackerImportService;
}

@Override
protected SmsResponse postProcess(IncomingSms sms, SmsSubmission submission, User user)
throws SMSProcessingException {
TrackerEventSmsSubmission subm = (TrackerEventSmsSubmission) submission;

Uid ouid = subm.getOrgUnit();
Uid stageid = subm.getProgramStage();
Uid enrolmentid = subm.getEnrollment();
Uid aocid = subm.getAttributeOptionCombo();
// TODO(ivo) which fields are optional? guard against NPEs
EventBuilder event =
org.hisp.dhis.tracker.imports.domain.Event.builder()
.event(subm.getEvent().getUid())
.enrollment(subm.getEnrollment().getUid())
.orgUnit(MetadataIdentifier.ofUid(subm.getOrgUnit().getUid()))
.programStage(MetadataIdentifier.ofUid(subm.getProgramStage().getUid()))
.attributeOptionCombo(MetadataIdentifier.ofUid(subm.getAttributeOptionCombo().getUid()))
.storedBy(user.getUsername())
.occurredAt(subm.getEventDate().toInstant())
.scheduledAt(subm.getDueDate().toInstant())
.status(map(subm.getEventStatus()))
.geometry(map(subm.getCoordinates()));

if (subm.getEventStatus() == SmsEventStatus.COMPLETED) {
event.completedBy(user.getUsername());
event.completedAt(Instant.now());
}

TrackerImportParams params =
TrackerImportParams.builder()
// TODO(ivo) don't forget to set this to CREATE_UPDATE I just want to see my test fail
// first
.importStrategy(TrackerImportStrategy.CREATE)
.userId(
user.getUid()) // SMS processing is done inside a job executed as the user that sent
// the SMS. We might want to remove the params user in favor of the currentUser set on
// the thread.
.build();
TrackerObjects trackerObjects = TrackerObjects.builder().events(List.of(event.build())).build();
ImportReport importReport = trackerImportService.importTracker(params, trackerObjects);

if (Status.OK == importReport.getStatus()) {
return SmsResponse.SUCCESS;
}
// TODO(DHIS2-18003) we need to map tracker import report errors/warnings to an sms
return SmsResponse.INVALID_EVENT.set(subm.getEvent());

// ArrayList<Object> errorUids = new ArrayList<>();
// List<SmsDataValue> values = subm.getValues();
// Map<DataElement, EventDataValue> dataElementsAndEventDataValues = new HashMap<>();
// if (values != null) {
// for (SmsDataValue dv : values) {
// Uid deid = dv.getDataElement();
// String val = dv.getValue();
//
// DataElement de = dataElementService.getDataElement(deid.getUid());
//
// // TODO: Is this the correct way of handling errors here?
// if (de == null) {
//// log.warn(
//// String.format(
//// "Given data element [%s] could not be found. Continuing with
// submission...",
//// deid));
// errorUids.add(deid);
//
// continue;
// } else if (val == null || StringUtils.isEmpty(val)) {
//// log.warn(
//// String.format(
//// "Value for atttribute [%s] is null or empty. Continuing with
// submission...",
//// deid));
// continue;
// }
//
// EventDataValue eventDataValue =
// new EventDataValue(deid.getUid(), dv.getValue(), currentUserInfo);
// eventDataValue.setAutoFields();
// dataElementsAndEventDataValues.put(de, eventDataValue);
// }
// }
//
// saveEventDataValuesAndSaveEvent(event, dataElementsAndEventDataValues);
//
// List<Object> errorUIDs = errorUids;
//
// if (!errorUIDs.isEmpty()) {
// return SmsResponse.WARN_DVERR.setList(errorUIDs);
// } else if (subm.getValues() == null || subm.getValues().isEmpty()) {
// // TODO: Should we save the event if there are no data values?
// return SmsResponse.WARN_DVEMPTY;
// }
//
// return SmsResponse.SUCCESS;
}

private EventStatus map(SmsEventStatus eventStatus) {
return switch (eventStatus) {
case ACTIVE -> EventStatus.ACTIVE;
case COMPLETED -> EventStatus.COMPLETED;
case VISITED -> EventStatus.VISITED;
case SCHEDULE -> EventStatus.SCHEDULE;
case OVERDUE -> EventStatus.OVERDUE;
case SKIPPED -> EventStatus.SKIPPED;
};
}

private Geometry map(GeoPoint coordinates) {
if (coordinates == null) {
return null;
}

GeometryFactory gf = new GeometryFactory();
Coordinate co = new Coordinate(coordinates.getLongitude(), coordinates.getLatitude());

return gf.createPoint(co);
}

private void saveEventDataValuesAndSaveEvent(
Event event, Map<DataElement, EventDataValue> dataElementEventDataValueMap) {
validateEventDataValues(dataElementEventDataValueMap);
Set<EventDataValue> eventDataValues = new HashSet<>(dataElementEventDataValueMap.values());
event.setEventDataValues(eventDataValues);

OrganisationUnit orgUnit = organisationUnitService.getOrganisationUnit(ouid.getUid());
event.setAutoFields();
if (!event.hasAttributeOptionCombo()) {
CategoryOptionCombo aoc = categoryService.getDefaultCategoryOptionCombo();
event.setAttributeOptionCombo(aoc);
}
manager.save(event);

Enrollment enrollment;
try {
enrollment =
enrollmentService.getEnrollment(enrolmentid.getUid(), UserDetails.fromUser(user));
} catch (ForbiddenException | NotFoundException e) {
throw new SMSProcessingException(SmsResponse.INVALID_ENROLL.set(enrolmentid));
for (Map.Entry<DataElement, EventDataValue> entry : dataElementEventDataValueMap.entrySet()) {
entry.getValue().setAutoFields();
// TODO(ivo) this is done by the importer, right?
// createAndAddChangeLog(entry.getValue(), entry.getKey(), event);
handleFileDataValueSave(entry.getValue(), entry.getKey());
}
}

private String validateEventDataValue(DataElement dataElement, EventDataValue eventDataValue) {

ProgramStage programStage = programStageService.getProgramStage(stageid.getUid());
if (programStage == null) {
throw new SMSProcessingException(SmsResponse.INVALID_STAGE.set(stageid));
if (StringUtils.isEmpty(eventDataValue.getStoredBy())) {
return "Stored by is null or empty";
}

CategoryOptionCombo aoc = categoryService.getCategoryOptionCombo(aocid.getUid());
if (aoc == null) {
throw new SMSProcessingException(SmsResponse.INVALID_AOC.set(aocid));
if (StringUtils.isEmpty(eventDataValue.getDataElement())) {
return "Data element is null or empty";
}

List<Object> errorUIDs =
saveEvent(
subm.getEvent().getUid(),
orgUnit,
programStage,
enrollment,
aoc,
user,
subm.getValues(),
subm.getEventStatus(),
subm.getEventDate(),
subm.getDueDate(),
subm.getCoordinates());
if (!errorUIDs.isEmpty()) {
return SmsResponse.WARN_DVERR.setList(errorUIDs);
} else if (subm.getValues() == null || subm.getValues().isEmpty()) {
// TODO: Should we save the event if there are no data values?
return SmsResponse.WARN_DVEMPTY;
if (!dataElement.getUid().equals(eventDataValue.getDataElement())) {
throw new IllegalQueryException(
"DataElement "
+ dataElement.getUid()
+ " assigned to EventDataValues does not match with one EventDataValue: "
+ eventDataValue.getDataElement());
}

return SmsResponse.SUCCESS;
String result =
ValidationUtils.valueIsValid(eventDataValue.getValue(), dataElement.getValueType());

return result == null ? null : "Value is not valid: " + result;
}

private void validateEventDataValues(
Map<DataElement, EventDataValue> dataElementEventDataValueMap) {
String result;
for (Map.Entry<DataElement, EventDataValue> entry : dataElementEventDataValueMap.entrySet()) {
result = validateEventDataValue(entry.getKey(), entry.getValue());
if (result != null) {
throw new IllegalQueryException(result);
}
}
}

private void createAndAddChangeLog(
EventDataValue dataValue, DataElement dataElement, Event event) {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'dataValue' is never used.

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'dataElement' is never used.

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'event' is never used.
// if (!config.isEnabled(CHANGELOG_TRACKER) || dataElement == null) {
// return;
// }
//
// TrackedEntityDataValueChangeLog dataValueChangeLog =
// new TrackedEntityDataValueChangeLog(
// dataElement,
// event,
// dataValue.getValue(),
// dataValue.getStoredBy(),
// dataValue.getProvidedElsewhere(),
// ChangeLogType.CREATE);
//
// eventChangeLogService.addTrackedEntityDataValueChangeLog(dataValueChangeLog);
}

/** Update FileResource with 'assigned' status. */
private void handleFileDataValueSave(EventDataValue dataValue, DataElement dataElement) {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'dataValue' is never used.
if (dataElement == null) {
return;
}

// TODO(ivo) what is this about?
// FileResource fileResource = fetchFileResource(dataValue, dataElement);
//
// if (fileResource == null) {
// return;
// }
//
// setAssigned(fileResource);
}

// private FileResource fetchFileResource(EventDataValue dataValue, DataElement dataElement) {
// if (!dataElement.isFileType()) {
// return null;
// }
//
// return fileResourceService.getFileResource(dataValue.getValue());
// }
//
// private void setAssigned(FileResource fileResource) {
// fileResource.setAssigned(true);
// fileResourceService.updateFileResource(fileResource);
// }

@Override
protected boolean handlesType(SubmissionType type) {
return (type == SubmissionType.TRACKER_EVENT);
Expand Down
Loading

0 comments on commit b998c64

Please sign in to comment.