Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RA-1397 - Ensure all data submitted by registrationapp are saved within a transaction #136

Merged
merged 8 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading