Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 5 Next »

Overview

The aim of this project is to allow users to download OpenMRS data (encounters) as hl7 objects.
Initially, we will support only the export of encounters as ORUR01 objects.

  1. Users will be able to request to download either a single encounter or multiple encounters as a single ORUR01 message
    • Something like /openmrs/hl7query/orur01?patient=uuidofpatient&encounter=uuidofencounter
  2. The ORUR01 message will contain the elements MSH, PV1, PID, OBR and OBX
  3. The PV1 and PID segments (and then the OBR/OBX) will repeat for each extra encounter included
  4. The hl7 message will be exported as a pipe delimited object
  5. The admin can define the exact elements, layout and flow of each different message type by creating groovy templates
    • Groovy template is xml based
    • The xml refers to exact fields in hl7
    • HAPI is used to parse the xml into hl7

Documentation / How-To

For now, see OpenMRS HL7Query Module Design Page

Create the message

An ORUR01 message consists of several segments (MSH, PID, PVI, OBR, OBX)
We have created several default groovy templates to create each of these message segments. The main "orur01" template is responsible for joining them together to output the final (and complete) ORUR01 message.

The groovy template would essentially be this: (this is PSEUDO CODE and obviously not actual groovy)

createORUR01Message(patient, encounterList) {
     getMSHTemplate(patient);
     Loop over list of encounters
         getPIDTemplateOutput(patient);
         getPV1TemplateOutput(patient, encounter);
       Loop over list of obs in current encounter {
            if obs group
               getOBRTemplateOutput(obs);
            else if not obs group
               getOBXTemplateOutput(obs);
       }
    }
}

The admin can edit the orur01 main template above and call different templates or hard code more values.

The admin could also open the "PID" or "MSH" template and edit there (which would effect every other template calling the PID or MSH template)

what a groovy template? how it will be used to help create hl7 messages.

groovy templates are used to create the hl7 messages.
We will use groovy templates to create the required message in xml format.
Once the xml format of the message has been created, it may be translated into the pipe delimited format via conversion using the HAPI API and the pipes returned to the user.

Given below is a detailed OURR01 message which maps between OpenMRS data and the relevant hl7 segments.
This message contains an Obs group with seven observations.

<ORU_R01 xmlns="urn:hl7-org:v2xml">
    <MSH>
        <MSH.1>|</MSH.1> <!-- This is the field seperator. It is a constant. The user should NOT be allowed to change it-->
        <MSH.2>^~\&amp;</MSH.2> <!-- Thease are the encoding characcters. The user should NOT be allowed to change it-->
        <MSH.4>
            <HD.1>hl7source.name</HD.1> <!--The hl7source specified here must exist in the receivers hl7source table--> <!-- use a module setting/gp for this -->
        </MSH.4>
        <MSH.6>
            <HD.1>Sending Faculty</HD.1>  <!-- The Sending faculty. This is implementation specific. it represents the implementation site. use a module setting/gp for this -->
        </MSH.6>
        <MSH.7>
            <TS.1>new Date()</TS.1> <!--A time stamp representing when this message was created -->
        </MSH.7>
        <MSH.9>
            <MSG.1>ORU</MSG.1> <!-- The Message type. It is a constant for this type of message-->
            <MSG.2>R01</MSG.2> <!-- The message event. It is a constant for this type of message -->
            <MSG.3>ORU_R01</MSG.3> <!-- The message structure. It is a constant for this type of message -->
        </MSH.9>
        <MSH.10>UUID.randomUUID().toString()</MSH.10>  <!-- The message control ID-->
        <MSH.11>
            <PT.1>D</PT.1> <!-- Processing Id. It is a constant/hardcoded -->
            <PT.2>C</PT.2> <!-- Processing mode. It is a constant/hardcoded-->
        </MSH.11>
        <MSH.12>
            <VID.1>2.5</VID.1> <!-- The hl7 version im using. It is a constant. I DONT think the user should be allowed to change this, because it may affect the message structure -->
            <VID.2>
                <CE.1>RWA</CE.1> <!-- The internationalization code. For jembi purposes, this is a fixed constant of RWA, what to use for a generic one? -->
            </VID.2>
        </MSH.12>
        <MSH.21>
            <EI.1>CLSM_V0.83</EI.1> <!-- The Message profile identifier : A constant, as proposed by Grahame. Who determines this? -->
        </MSH.21>
    </MSH>
    <ORU_R01.PATIENT_RESULT>
        <ORU_R01.PATIENT>
            <PID>
                <PID.1>1</PID.1> <!--Set ID - PID. It is a hardcoded -->

                <PID.3> <!-- PID.3 segments represent patient identifier and identifier type pairs. They can be repeated -->
                    <CX.1>patient.patientIdentifier.identifier</CX.1> <!-- displays the id number -->
                    <CX.5>patient.patientIdentifierType.name</CX.5> <!-- displays the name of the identifier type  -->
                </PID.3>
                <PID.5> <!-- PID.3 segments represent several names that a patient may have. They can be repeated -->
                    <XPN.1>
                        <FN.1>patient.getFamilyName()</FN.1>
                    </XPN.1>
                    <XPN.2>patient.getGivenName()</XPN.2>
                </PID.5>
            </PID>
			<!-- Note: The PID segment should also include the patient's address (Repeatable), sex, race, country code and telephone numbers. Jembi are not using these segments. However, they may be of use to others -->
            <ORU_R01.VISIT>
                <PV1>
                    <PV1.2>0</PV1.2> <!-- The patient class. It is a hardcoded -->
                    <PV1.3>
                        <PL.1>encounter.getLocation().getUuid()</PL.1>
                        <PL.4>
                            <HD.1>encounter.getLocation().getName()</HD.1>
                        </PL.4>
                    </PV1.3>
                    <PV1.4>encounter.getEncounterType().getName()</PV1.4>
                    <PV1.7>
                        <XCN.1>encounter.getProvider().getUuid()</XCN.1>
                        <XCN.2>
                            <FN.1>encounter.getProvider().getFamilyName()</FN.1>
                        </XCN.2>
                        <XCN.3>encounter.getProvider().getGivenName()</XCN.3>
                        <XCN.13>NID</XCN.13> <!-- This is the identifier type name of the identifier we will use to identify the provider. The user will be allowed to set it -->
                    </PV1.7>
                    <PV1.44>
                        <TS.1>encounter.getEncounterDatetime()</TS.1> <!-- The date the visit took place -->
                    </PV1.44>
                </PV1>
            </ORU_R01.VISIT>
        </ORU_R01.PATIENT>
        <ORU_R01.ORDER_OBSERVATION>
            <ORC>
                <ORC.1>RE</ORC.1> <!-- Order Control. It is a constant. The user should NOT be allowed to change it -->
                <ORC.9>
                    <TS.1>new Date()</TS.1> <!-- This is supposed to store when the transaction data/time. In our case, a simple new Date() will siffice ?
                </ORC.9>
                <ORC.12>
                    <XCN.1>encounter.getProvider(X).getUuid()</XCN.1> <!-- the id for whoever provider who made the Order -->
                </ORC.12>
                <ORC.16> <!-- Im not sure what these segments are. I'vs put in some 'make belive' data for now -->
                    <CE.1>XXX</CE.1> <!-- Order control code reason.Identifier-->
                    <CE.2>YYY</CE.2> <!-- Order control code reason.Text-->
                    <CE.3>ZZZ</CE.3> <!-- Order control code reason.NameOfCodingSystem-->
                </ORC.16>
            </ORC>
            <OBR> <!-- The First OBR segment will represent the encounter. There will be one of these per each message -->
                <OBR.1>0</OBR.1>
                <OBR.3>
                    <EI.1>encounter.getUuid()</EI.1>
                </OBR.3>
                <OBR.4>
                    <CE.2>encounter.getEncounterType().getName()</CE.2> <!-- The encounter type -->
                </OBR.4>
                <OBR.7>
                    <TS.1>201207120000</TS.1>
                </OBR.7>
                <OBR.16>
                    <XCN.1>encounter.getProvider().getUuid()</XCN.1> <!-- Question. Which ID type should we use here ? -->
                    <XCN.2>
                        <FN.1>encounter.getProvider().getFamilyName()</FN.1>
                    </XCN.2>
                    <XCN.3>encounter.getProvider().getGivenName()</XCN.3>
                </OBR.16>
                <OBR.20>encounter.getLocation().getUuid()</OBR.20>
                <OBR.21>encounter.getLocation().getName()</OBR.21>
            </OBR>
        </ORU_R01.ORDER_OBSERVATION>
        <ORU_R01.ORDER_OBSERVATION>
            <OBR> <!-- Additional OBR segments will be used to represent each Obs group -->
                <OBR.1>1</OBR.1>
				<OBR.4> <!-- This is the concept source id, the concept name and the concept source name of the obs grouper -->
					<CE.1>map.getSourceCode()</CE.1>
                    <CE.2>obs.getConcept().getName().toString()</CE.2>
                    <CE.3>map.getSource().getName()</CE.3>
				</OBR.4>
                <OBR.18>0</OBR.18>
                <OBR.29>
                    <EIP.2>
                        <EI.3>encounter.getUuid()</EI.3>
                    </EIP.2>
                </OBR.29>
            </OBR>
            <ORU_R01.OBSERVATION>
                <OBX>
                    <OBX.1>0</OBX.1> <!-- This is the 1st Observation comming under this encounter -->
                    <OBX.2>HL7Constants.HL7_NUMERIC</OBX.2>  <!-- This is a constant from the core HL7Constants.java class. Here, since the concept type is numeric, it's value is 'NM'-->
                    <OBX.3> <!-- OBX.3 segments are used to share the concept name, mapping and ID. There can be only one OBX.3 segment per OBX -->
					<!-- So assume we do this :
					Collection<ConceptMap> conceptMappings =  ob.getConcept().getConceptMappings();
					Iterator<ConceptMap> itr = conceptMappings.iterator();
					ConceptMap map = itr.next();
					Then, -->

                        <CE.1>map.getSourceCode()</CE.1>
                        <CE.2>obs.getConcept().getName().toString()</CE.2>
                        <CE.3>map.getSource().getName()</CE.3>
                    </OBX.3>

                    <OBX.5>obs.getValueNumeric()</OBX.5>
                    <OBX.6>
                        <CE.1>conceptNumeric.getUnits()</CE.1> <!-- Since the concept type is numeric, we get to do this -->
                        <CE.3>UCUM</CE.3> <!-- Unit coding system. It is a constant. The user should NOT be allowed to change it-->
                    </OBX.6>
                    <OBX.14>
                        <TS.1>obs.getDateCreated()</TS.1>
                    </OBX.14>
                </OBX>
            </ORU_R01.OBSERVATION>
            <ORU_R01.OBSERVATION>
                <OBX>
                    <OBX.1>1</OBX.1> <!-- This is the 2nd Observation comming under this encounter -->
                    <OBX.2>NM</OBX.2>

                    <OBX.3> <!-- OBX.3 segments are used to share the concept name, mapping and ID. There can be only one OBX.3 segment per OBX -->
					<!-- So assume we do this :
					Collection<ConceptMap> conceptMappings =  ob.getConcept().getConceptMappings();
					Iterator<ConceptMap> itr = conceptMappings.iterator();
					ConceptMap map = itr.next();
					Then, -->

                    <OBX.3>
                        <CE.1>map.getSourceCode()</CE.1>
                        <CE.2>obs.getConcept().getName().toString()</CE.2>
                        <CE.3>map.getSource().getName()</CE.3>
                    </OBX.3>
                    <OBX.5>34.0</OBX.5>
                    <OBX.6>
                        <CE.1>conceptNumeric.getUnits()</CE.1>
                        <CE.3>UCUM</CE.3>
                    </OBX.6>
                    <OBX.14>
                        <TS.1>20120712114812</TS.1>
                    </OBX.14>
                </OBX>
            </ORU_R01.OBSERVATION>
            <ORU_R01.OBSERVATION>
                <OBX>
                    <OBX.1>2</OBX.1> <!-- This is the 3rd Observation comming under this encounter -->
                    <OBX.2>NM</OBX.2>
                    <OBX.3> <!-- OBX.3 segments are used to share the concept name, mapping and ID. There can be only one OBX.3 segment per OBX -->
					<!-- So assume we do this :
					Collection<ConceptMap> conceptMappings =  ob.getConcept().getConceptMappings();
					Iterator<ConceptMap> itr = conceptMappings.iterator();
					ConceptMap map = itr.next();
					Then, -->

                        <CE.1>map.getSourceCode()</CE.1>
                        <CE.2>obs.getConcept().getName().toString()</CE.2>
                        <CE.3>map.getSource().getName()</CE.3>
                    </OBX.3>
                    <OBX.5>34.0</OBX.5>
                    <OBX.6>
                        <CE.1>conceptNumeric.getUnits()</CE.1>
                        <CE.3>UCUM</CE.3>
                    </OBX.6>
                    <OBX.14>
                        <TS.1>20120712114812</TS.1>
                    </OBX.14>
                </OBX>
            </ORU_R01.OBSERVATION>
            <!-- Lets see what an Observation with A coded concept looks like -->

            <ORU_R01.OBSERVATION>
                <OBX>
                    <OBX.1>3</OBX.1> <!-- This is the 4th Observation comming under this encounter -->
                    <OBX.2>CE</OBX.2>
                    <OBX.3> <!-- OBX.3 segments are used to share the concept name, mapping and ID. There can be only one OBX.3 segment per OBX -->
					<!-- So assume we do this :
					Collection<ConceptMap> conceptMappings =  ob.getConcept().getConceptMappings();
					Iterator<ConceptMap> itr = conceptMappings.iterator();
					ConceptMap map = itr.next();
					Then, -->

                        <CE.1>map.getSourceCode()</CE.1>
                        <CE.2>obs.getConcept().getName().toString()</CE.2>
                        <CE.3>map.getSource().getName()</CE.3>
                    </OBX.3>

                 <!--OBX.5 stores the value of the Observation. In this case, it is a Concept. Therefore, we are storing the concept name, Concept source code and the source name-->
                    <OBX.5>
                        <CE.1>map.getSourceCode()</CE.1>
                        <CE.2>obs.getConcept().getName().toString()</CE.2>
                        <CE.3>map.getSource().getName()</CE.3>
                    </OBX.5>
                    <OBX.14>
                        <TS.1>20120712114812</TS.1>
                    </OBX.14>
                </OBX>
            </ORU_R01.OBSERVATION>
            <ORU_R01.OBSERVATION>
                <OBX>
                    <OBX.1>4</OBX.1> <!-- This is the 5th Observation comming under this encounter -->
                    <OBX.2>NM</OBX.2>
                    <OBX.3> <!-- OBX.3 segments are used to share the concept name, mapping and ID. There can be only one OBX.3 segment per OBX -->
					<!-- So assume we do this :
					Collection<ConceptMap> conceptMappings =  ob.getConcept().getConceptMappings();
					Iterator<ConceptMap> itr = conceptMappings.iterator();
					ConceptMap map = itr.next();
					Then, -->

                        <CE.1>map.getSourceCode()</CE.1>
                        <CE.2>obs.getConcept().getName().toString()</CE.2>
                        <CE.3>map.getSource().getName()</CE.3>
                    </OBX.3>
                    <OBX.5>34.0</OBX.5>
                    <OBX.6>
                        <CE.1>conceptNumeric.getUnits()</CE.1>
                        <CE.3>UCUM</CE.3>
                    </OBX.6>
                    <OBX.14>
                        <TS.1>20120712114812</TS.1>
                    </OBX.14>
                </OBX>
            </ORU_R01.OBSERVATION>
            <ORU_R01.OBSERVATION>
                <OBX>
                    <OBX.1>5</OBX.1> <!-- This is the 6th Observation comming under this encounter -->
                    <OBX.2>NM</OBX.2>
                    <OBX.3> <!-- OBX.3 segments are used to share the concept name, mapping and ID. There can be only one OBX.3 segment per OBX -->
					<!-- So assume we do this :
					Collection<ConceptMap> conceptMappings =  ob.getConcept().getConceptMappings();
					Iterator<ConceptMap> itr = conceptMappings.iterator();
					ConceptMap map = itr.next();
					Then, -->

                        <CE.1>map.getSourceCode()</CE.1>
                        <CE.2>obs.getConcept().getName().toString()</CE.2>
                        <CE.3>map.getSource().getName()</CE.3>
                    </OBX.3>
                    <OBX.5>34.0</OBX.5>
                    <OBX.6>
                        <CE.1>conceptNumeric.getUnits()</CE.1>
                        <CE.3>UCUM</CE.3>
                    </OBX.6>
                    <OBX.14>
                        <TS.1>20120712114812</TS.1>
                    </OBX.14>
                </OBX>
            </ORU_R01.OBSERVATION>
            <ORU_R01.OBSERVATION>
                <OBX>
                    <OBX.1>6</OBX.1> <!-- This is the 7th Observation comming under this encounter -->
                    <OBX.2>NM</OBX.2>
                    <OBX.3> <!-- OBX.3 segments are used to share the concept name, mapping and ID. There can be only one OBX.3 segment per OBX -->
					<!-- So assume we do this :
					Collection<ConceptMap> conceptMappings =  ob.getConcept().getConceptMappings();
					Iterator<ConceptMap> itr = conceptMappings.iterator();
					ConceptMap map = itr.next();
					Then, -->

                        <CE.1>map.getSourceCode()</CE.1>
                        <CE.2>obs.getConcept().getName().toString()</CE.2>
                        <CE.3>map.getSource().getName()</CE.3>
                    </OBX.3>
                    <OBX.5>34.0</OBX.5>
                    <OBX.6>
                        <CE.1>conceptNumeric.getUnits()</CE.1>
                        <CE.3>UCUM</CE.3>
                    </OBX.6>
                    <OBX.14>
                        <TS.1>20120712114812</TS.1>
                    </OBX.14>
                </OBX>
            </ORU_R01.OBSERVATION>
        </ORU_R01.ORDER_OBSERVATION>
    </ORU_R01.PATIENT_RESULT>
</ORU_R01>

Why Groovy Templates?

Groovy will alow us to make the message specification as flexible as possible. Groovy allows us to create a resuable template which gathers these data, and displayes them in a manner which makes it easy to edit / manipulate. The template will have access to almost all data about the patient/encounter/obs via the api and create any parts of a message that are wanted.

About The Groovy Template

The template receives a series of bindings from its parent. (the patient object, patient name object, list of encounters, etc)
The template can loop over and display any of that
The template is built by calling other (sub)templates
A sub template is responsible for creating each of the main message segments (MSH, PID, PV1, ORC, OBR and OBX)
Each sub template may be in turn composed of several other sub templates.
The MSH segment is made up of all constants, and hence may not need to be composed of other segments.
The OBR segment is used to represent an OpenMRS obs group
The OBX is a single segment.
If there are multiple encounters in the message, the PID + PV1 segments need to be repeated for that encounter

Rendering the Template

To turn the template into xml output, the groovy needs to be able to call openmrs objects. The template is passed to the "render" method in the service. The render method also accepts a series of "bindings" (aka parameters) for the objects that the groovy template will use. Most of the time this will be the current patient and the list of encounters needing to be converted into hl7.

Converting to HL7 Pipes

The xml string returned from the rendering can be passed through HAPI

Converting the HAPI object into the pipe delimited format is done using a HAPI PipeParser

PipeParser parser = new PipeParser();
String hl7pipesmsg = null;
try {
	hl7pipesmsg = parser.encode(xmlString);
}
catch (HL7Exception e) {
	log.error("Exception parsing constructed message.");
}

pseudo-template for a R01 message (contributed by Darius)

/* template requires: patient, encounters */
    <msh> (hard coded for this template)...</msh>  /* or just ${ callTemplate("MSH", patient) } and its done */
    ${ callTemplate("PID", patient) }
    <% encounters.each { encounter -> %>
        ${ callTemplate("PV1", encounter) }
        <% encounter.getAllObs().filter({ /* ungrouped only */ }) { obs -> %>
            ${ callTemplate("OBX", obs) }
        <% } %>
        <% encounter.getAllObs().filter({ /* obs groups only */ }) { group -> %>
            ${ callTemplate("obs-group-as-OBR-and-OBX", group) }
        <% } %>
    <% } %>

Template with name "PID":
<!-- assumes these vars are made available:
  patient == Patient object
-->
   <pid>
       <> ${ callTemplate("XPN", [name: patient.personName)] }</>
   </pid>

Template with name "XPN":
<!-- assumes these vars are made available:
  name == PersonName object
  useFieldX == boolean for whether or not to show something. (optional parameter)
-->
<XPN>
    <XPN.1>
        <FN.1>${ name.familyName }</FN.1>
        <% if (name.familyName2) { %>
            <FN.2>${ name.familyName2 }</FN.2>
        <% } %>
        <% if (param.useFieldX == true) { %>
            <FN.X> ${name.getAttribute('asdf')}</FN.X>
        <% } %>
    </XPN.1>
    <XPN.2>${ name.givenName }</XPN.2>
</XPN>

Template with name "PatientIdentifier-to-CX":
...

Template with name "PersonAttribute-to-CX":
<!-- assumes these vars are made available:
    attribute == PersonAttribute
    identifierTypeHl7Code == String
-->

In order to avoid calling the service layer directly from the templates.Do as follows -

class R01Controller {
    public String handle() {
        Map<String, Object> bindings = new HashMap();
        bindings.put("patient", ...);
        bindings.put("encounters", ...);
        bindings.put("fn", new TemplateFunctions());

        return templateEngine.render(service.getTemplate("ORU-R01"), bindings);
    }
}

Testing the hl7query module.

We will focus on test driven development to test the module

public class Hl7MessageGeneratorTest {

	Map<String, Object> defaultBindings;

	@Before
	public void beforeEachTest() throws Exception {
		// build a Patient and an Encounter in-memory
	}

	/**
	 * Each template that we're going to write should be developed based on a test like this.
	 * Basically you can build and debug the whole thing without ever even having to fully build the
	 * module.
	 */
	public void testPatientIdentifierToCxTemplate() throws Exception {
		Hl7MessageGenerator mock = Mockito.mock(Hl7MessageGenerator.class);
		when(mock.getTemplate("PatientIdentifier-to-CX")).thenReturn(patientIdentifierToCxTemplate());

		Map<String, Object> bindings = new HashMap<String, Object>();
		bindings.put("patientIdentifier", buildPatientIdentifier());

		String output = mock.applyTemplate("PatientIdentifier-to-CX", bindings);
		Assert.assertEquals("<CX>what it should look like</CX>", output);
	}

    /**
     * I've put this in its own method because we know that higher-level templates (e.g. Patient-to-PID)
     * will also need to include this same template.
     */
    private String patientIdentifierToCxTemplate() {
    	return "Template goes here, maybe you want to actually load it from an XML file";
    }

	private PatientIdentifier buildPatientIdentifier() {
	    // TODO
    }

}

Converting the xml version of the hl7 message into the pipe delimited format.

1. Convert the xml object into string
2. Convert the string into the HAPI object
3. Use HAPI to convert the hl7 message into the pipe delimited format

Converting an hl7 String into the HAPI object

public Message processMessage(String hl7, String enterpriseId) throws HL7Exception {

		log.debug("Register handler applications for R01 and A28");
		// TODO draw registered applications from database or configuration file
		router = new MessageTypeRouter();
		router.registerApplication("ORU", "R01", new RHEA_ORU_R01Handler(enterpriseId));
		router.registerApplication("ADT", "A28", new ADTA28Handler());

		// TODO: any pre-parsing for HL7 messages would go here

		// First, try and parse the message
		Message message;
		try {
			message = parser.parse(hl7);
		}
		catch (EncodingNotSupportedException e) {
			throw new HL7Exception("HL7 encoding not supported", e);
		}
		catch (HL7Exception e) {
			throw new HL7Exception("Error parsing message", e);
		}

		// TODO: any post-parsing (pre-routing) processing would go here

		// If parsing succeeded, then try to route the message
		Message response;
		try {
			if (!router.canProcess(message))
				throw new HL7Exception("No route for message");
			response = router.processMessage(message);
		}
		catch (ApplicationException e) {
			throw new HL7Exception("Error routing HL7 message", e);
		}
		return response;
	}
}

Use case scenarios

Scenario 1: User requests hl7 message for patient X and encounter Y

1. Admin installs the hl7query module
2. Admin does not make any changes to the default configuration (He does not change the global property identifying the selected ORUR01 message template to export, and nor does he attempt to edit any templates / segments)
3A. User makes a valid request to the hl7query controller class passing in the uuid of the patient and uuid of the encounter
The controller retrieves the "orur01" groovy template.
The template rendered uses the given patient+encounter and turns the template into hl7-like xml (see A Developer's Guide to the ORUR01 Message)
The xml is passed through HAPI to turn the xml into hl7
Details of the user's request are logged.
3B. User makes an invalid request to the hl7query controller class.
The user receives an ACK message notifying him of the error.
Details of the users request are logged.

Scenario 2: Admin wishes to change the default template.

1. Admin installs the hl7query module
2. Admin navigates to the module administration page
3. Admin wishes to create a custom ORUR01 template

Scenario 3: Admin wishes to edit part of the default template.

1. Admin installs the hl7query module
2. Admin navigates to the module administration page
3. Admin clicks on the template link. This leads him to an editable text area where he can edit and save the template. Changing which templates are called and/or adding in more xml hl7 elements.

Downloads

http://modules.openmrs.org/modules/view.jsp?module=hl7query

Source code: https://github.com/OpenMRS/openmrs-module-hl7query

Screenshots

Release Notes

  • 1.0 (initial release)
    • TBD

About

This module was developed by Suranga K for Jembi (and during this sprint)

  • No labels