Skip to content

Commit

Permalink
RA-1397 - Ensure all data submitted by registrationapp are saved with…
Browse files Browse the repository at this point in the history
…in a transaction (#136)
  • Loading branch information
mseaton committed Nov 30, 2023
1 parent 333c7ca commit 83f29e5
Show file tree
Hide file tree
Showing 7 changed files with 382 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.registrationapp.api;

import org.openmrs.Encounter;
import org.openmrs.Location;
import org.openmrs.Obs;
import org.openmrs.module.registrationapp.action.AfterPatientCreatedAction;
import org.openmrs.module.registrationcore.RegistrationData;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Encapsulates the data that may be collected and saved within a patient registration transaction
*/
public class RegistrationAppData implements Serializable {

private Date registrationDate;
private Location registrationLocation;
private RegistrationData registrationData;
private Encounter registrationEncounter;
private List<Obs> registrationObs;
private List<AfterPatientCreatedAction> afterPatientCreatedActions;
private Map<String, String[]> parameters;

public RegistrationAppData() {
}

public Date getRegistrationDate() {
return registrationDate;
}

public void setRegistrationDate(Date registrationDate) {
this.registrationDate = registrationDate;
}

public Location getRegistrationLocation() {
return registrationLocation;
}

public void setRegistrationLocation(Location registrationLocation) {
this.registrationLocation = registrationLocation;
}

public RegistrationData getRegistrationData() {
return registrationData;
}

public void setRegistrationData(RegistrationData registrationData) {
this.registrationData = registrationData;
}

public Encounter getRegistrationEncounter() {
return registrationEncounter;
}

public void setRegistrationEncounter(Encounter registrationEncounter) {
this.registrationEncounter = registrationEncounter;
}

public List<Obs> getRegistrationObs() {
if (registrationObs == null) {
registrationObs = new ArrayList<Obs>();
}
return registrationObs;
}

public void setRegistrationObs(List<Obs> registrationObs) {
this.registrationObs = registrationObs;
}

public List<AfterPatientCreatedAction> getAfterPatientCreatedActions() {
if (afterPatientCreatedActions == null) {
afterPatientCreatedActions = new ArrayList<AfterPatientCreatedAction>();
}
return afterPatientCreatedActions;
}

public void setAfterPatientCreatedActions(List<AfterPatientCreatedAction> afterPatientCreatedActions) {
this.afterPatientCreatedActions = afterPatientCreatedActions;
}

public Map<String, String[]> getParameters() {
if (parameters == null) {
parameters = new HashMap<String, String[]>();
}
return parameters;
}

public void setParameters(Map<String, String[]> parameters) {
this.parameters = parameters;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.module.registrationapp.api;

import org.openmrs.Patient;
import org.openmrs.api.OpenmrsService;

/**
* This service provides transactional services for the registration app module
*/
public interface RegistrationAppService extends OpenmrsService {

/**
* Registers patient, along with associated registration data
*/
Patient registerPatient(RegistrationAppData registrationData);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.module.registrationapp.api;

import org.openmrs.Encounter;
import org.openmrs.Obs;
import org.openmrs.Patient;
import org.openmrs.api.EncounterService;
import org.openmrs.api.ObsService;
import org.openmrs.api.impl.BaseOpenmrsService;
import org.openmrs.module.registrationapp.action.AfterPatientCreatedAction;
import org.openmrs.module.registrationcore.api.RegistrationCoreService;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
* This service provides transactional services for the registration app module
*/
@Transactional
public class RegistrationAppServiceImpl extends BaseOpenmrsService implements RegistrationAppService {

private RegistrationCoreService registrationCoreService;
private EncounterService encounterService;
private ObsService obsService;

@Override
public Patient registerPatient(RegistrationAppData registrationAppData) {
Patient patient = registrationCoreService.registerPatient(registrationAppData.getRegistrationData());

Encounter registrationEncounter = registrationAppData.getRegistrationEncounter();
for (Obs o : registrationAppData.getRegistrationObs()) {
if (registrationEncounter != null) {
registrationEncounter.addObs(o);
}
else {
if (o.getPerson() == null) {
o.setPerson(patient);
}
if (o.getLocation() == null) {
o.setLocation(registrationAppData.getRegistrationLocation());
}
if (o.getObsDatetime() == null) {
o.setObsDatetime(registrationAppData.getRegistrationDate());
if (o.getObsDatetime() == null) {
o.setObsDatetime(new Date());
}
}
obsService.saveObs(o, null);
}
}
if (registrationEncounter != null) {
encounterService.saveEncounter(registrationEncounter);
}

for (AfterPatientCreatedAction action : registrationAppData.getAfterPatientCreatedActions()) {
action.afterPatientCreated(patient, registrationAppData.getParameters());
}

return patient;
}

public void setRegistrationCoreService(RegistrationCoreService registrationCoreService) {
this.registrationCoreService = registrationCoreService;
}

public void setEncounterService(EncounterService encounterService) {
this.encounterService = encounterService;
}

public void setObsService(ObsService obsService) {
this.obsService = obsService;
}
}
27 changes: 27 additions & 0 deletions api/src/main/resources/moduleApplicationContext.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Add here beans related to the API context -->
<bean id="registrationAppService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<bean class="org.openmrs.module.registrationapp.api.RegistrationAppServiceImpl">
<property name="registrationCoreService" ref="registrationCoreService" />
<property name="encounterService" ref="encounterService" />
<property name="obsService" ref="obsService" />
</bean>
</property>
<property name="preInterceptors">
<ref bean="serviceInterceptors" />
</property>
<property name="transactionAttributeSource">
<ref bean="transactionAttributeSource" />
</property>
</bean>

<bean parent="serviceContext">
<property name="moduleService">
<list>
<value>org.openmrs.module.registrationapp.api.RegistrationAppService</value>
<ref bean="registrationAppService" />
</list>
</property>
</bean>

<bean id="stringToPersonAddressConverter" class="org.openmrs.module.registrationapp.converter.StringToPersonAddressConverter" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import org.openmrs.module.registrationapp.RegistrationAppUiUtils;
import org.openmrs.module.registrationapp.RegistrationAppUtils;
import org.openmrs.module.registrationapp.action.AfterPatientCreatedAction;
import org.openmrs.module.registrationapp.api.RegistrationAppData;
import org.openmrs.module.registrationapp.api.RegistrationAppService;
import org.openmrs.module.registrationapp.form.RegisterPatientFormBuilder;
import org.openmrs.module.registrationapp.model.Field;
import org.openmrs.module.registrationapp.model.NavigableFormStructure;
Expand Down Expand Up @@ -104,7 +106,7 @@ public FragmentActionResult importMpiPatient(@RequestParam("mpiPersonId") String
}

public FragmentActionResult submit(UiSessionContext sessionContext, @RequestParam(value="appId") AppDescriptor app,
@SpringBean("registrationCoreService") RegistrationCoreService registrationService,
@SpringBean("registrationAppService") RegistrationAppService registrationService,
@ModelAttribute("patient") @BindParams Patient patient,
@ModelAttribute("personName") @BindParams PersonName name,
@ModelAttribute("personAddress") @BindParams PersonAddress address,
Expand Down Expand Up @@ -185,33 +187,20 @@ public FragmentActionResult submit(UiSessionContext sessionContext, @RequestPara
registrationData.addBiometricData(new BiometricData(subject, identifierType));
}

try {
// if patientIdentifier is blank, the underlying registerPatient method should automatically generate one
patient = registrationService.registerPatient(registrationData);
}
catch (Exception ex) {

// TODO I remember getting into trouble if i called this validator before the above save method.
// TODO Am therefore putting this here for: https://tickets.openmrs.org/browse/RA-232
patientValidator.validate(patient, errors);
int originalErrorCount = errors.getErrorCount();
RegistrationAppUiUtils.checkForIdentifierExceptions(ex, errors); // TODO do I need to check this again here since we are now calling it earlier? can keep it just to be save
RegistrationAppData registrationAppData = new RegistrationAppData();
registrationAppData.setRegistrationDate(registrationDate);
registrationAppData.setRegistrationLocation(sessionContext.getSessionLocation());

if (!errors.hasErrors() || (originalErrorCount == errors.getErrorCount())) {
errors.reject(ex.getMessage());
}
return new FailureResult(createErrorMessage(errors, messageSourceService));
}
// add core registration data
registrationAppData.setRegistrationData(registrationData);

// now create the registration encounter, if configured to do so
// add a registration encounter if submitted
Encounter registrationEncounter = buildRegistrationEncounter(patient, registrationDate, sessionContext, app, encounterService);
if (registrationEncounter != null) {
encounterService.saveEncounter(registrationEncounter);
}
registrationAppData.setRegistrationEncounter(registrationEncounter);

Map<String, List<ObsGroupItem>> obsGroupMap = new LinkedHashMap<String, List<ObsGroupItem>>();
// build any obs that are submitted
List<Obs> obsToCreate = new ArrayList<Obs>();
Map<String, List<ObsGroupItem>> obsGroupMap = new LinkedHashMap<String, List<ObsGroupItem>>();
for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements(); ) {
String param = e.nextElement();
if (param.startsWith("obsgroup.")) {
Expand All @@ -222,31 +211,12 @@ public FragmentActionResult submit(UiSessionContext sessionContext, @RequestPara
buildObs(conceptService, obsToCreate, conceptUuid, request.getParameterValues(param));
}
}

if (obsGroupMap.size() > 0 ){
if (!obsGroupMap.isEmpty()){
buildGroupObs(conceptService, obsToCreate, obsGroupMap);
}
if (!obsToCreate.isEmpty()) {
if (registrationEncounter != null) {
for (Obs obs : obsToCreate) {
registrationEncounter.addObs(obs);
}
encounterService.saveEncounter(registrationEncounter);
}
else {
Date datetime = registrationDate != null ? registrationDate : new Date();
for (Obs obs : obsToCreate) {
// since we don't inherit anything from the Encounter, we need to specify these
obs.setPerson(patient);
obs.setLocation(sessionContext.getSessionLocation());
obs.setObsDatetime(datetime);
obsService.saveObs(obs, null);
}
}
}
registrationAppData.setRegistrationObs(obsToCreate);

// run any AfterPatientCreated actions
// TODO wrap everything here in a single transaction
// construct any AfterPatientCreated actions
ArrayNode afterCreatedArray = (ArrayNode) app.getConfig().get("afterCreatedActions");
if (afterCreatedArray != null) {
for (JsonNode actionNode : afterCreatedArray) {
Expand All @@ -262,9 +232,28 @@ public FragmentActionResult submit(UiSessionContext sessionContext, @RequestPara
} else {
throw new IllegalStateException("Invalid afterCreatedAction: " + actionString);
}
registrationAppData.getAfterPatientCreatedActions().add(action);
}
}
registrationAppData.setParameters(request.getParameterMap());

// register the patient and execute all actions transactionally
// if patientIdentifier is blank, the underlying registerPatient method should automatically generate one
try {
patient = registrationService.registerPatient(registrationAppData);
}
catch (Exception ex) {

action.afterPatientCreated(patient, request.getParameterMap());
// TODO I remember getting into trouble if i called this validator before the above save method.
// TODO Am therefore putting this here for: https://tickets.openmrs.org/browse/RA-232
patientValidator.validate(patient, errors);
int originalErrorCount = errors.getErrorCount();
RegistrationAppUiUtils.checkForIdentifierExceptions(ex, errors); // TODO do I need to check this again here since we are now calling it earlier? can keep it just to be save

if (!errors.hasErrors() || (originalErrorCount == errors.getErrorCount())) {
errors.reject(ex.getMessage());
}
return new FailureResult(createErrorMessage(errors, messageSourceService));
}

InfoErrorMessageUtil.flashInfoMessage(request.getSession(), ui.message("registrationapp.createdPatientMessage", ui.encodeHtml(ui.format(patient))));
Expand Down

0 comments on commit 83f29e5

Please sign in to comment.