From 4b8ba8612e41f35049dad401f33109888353534f Mon Sep 17 00:00:00 2001
From: Ian <ian_bacher@brown.edu>
Date: Mon, 11 Jan 2021 09:40:42 -0500
Subject: [PATCH 01/18] Move FhirImmunizationServiceTest

---
 .../FhirImmunizationServiceImplTest.java}                    | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
 rename api/src/test/java/org/openmrs/module/fhir2/api/{FhirImmunizationServiceTest.java => impl/FhirImmunizationServiceImplTest.java} (98%)

diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/FhirImmunizationServiceTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirImmunizationServiceImplTest.java
similarity index 98%
rename from api/src/test/java/org/openmrs/module/fhir2/api/FhirImmunizationServiceTest.java
rename to api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirImmunizationServiceImplTest.java
index 49daf45be..1203f1f52 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/FhirImmunizationServiceTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirImmunizationServiceImplTest.java
@@ -7,7 +7,7 @@
  * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
  * graphic logo is a trademark of OpenMRS Inc.
  */
-package org.openmrs.module.fhir2.api;
+package org.openmrs.module.fhir2.api.impl;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -47,13 +47,14 @@
 import org.openmrs.api.ObsService;
 import org.openmrs.module.fhir2.FhirConstants;
 import org.openmrs.module.fhir2.TestFhirSpringConfiguration;
+import org.openmrs.module.fhir2.api.FhirImmunizationService;
 import org.openmrs.module.fhir2.api.translators.impl.ImmunizationObsGroupHelper;
 import org.openmrs.test.BaseModuleContextSensitiveTest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.ContextConfiguration;
 
 @ContextConfiguration(classes = TestFhirSpringConfiguration.class, inheritLocations = false)
-public class FhirImmunizationServiceTest extends BaseModuleContextSensitiveTest {
+public class FhirImmunizationServiceImplTest extends BaseModuleContextSensitiveTest {
 	
 	private static final String IMMUNIZATIONS_METADATA_XML = "org/openmrs/module/fhir2/api/translators/ImmunizationTranslator_metadata.xml";
 	

From 9ec21df83939aa7ed5c761ab86c5c42bd6d1c2fe Mon Sep 17 00:00:00 2001
From: Ian <ian_bacher@brown.edu>
Date: Wed, 13 Jan 2021 16:55:39 -0500
Subject: [PATCH 02/18] Log an error, but do not fail if we cannot read the
 global property for server URI

---
 .../module/fhir2/api/dao/impl/BaseFhirDao.java     |  2 +-
 .../fhir2/web/util/OpenmrsFhirAddressStrategy.java | 14 +++++++++++++-
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java
index 2741af695..6243696dd 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java
@@ -262,7 +262,7 @@ protected T retireObject(T object) {
 	 * @param theParams the parameters for this search
 	 */
 	protected void setupSearchParams(Criteria criteria, SearchParameterMap theParams) {
-
+		
 	}
 	
 	@Override
diff --git a/omod/src/main/java/org/openmrs/module/fhir2/web/util/OpenmrsFhirAddressStrategy.java b/omod/src/main/java/org/openmrs/module/fhir2/web/util/OpenmrsFhirAddressStrategy.java
index a1e58262a..e6b97d6bd 100644
--- a/omod/src/main/java/org/openmrs/module/fhir2/web/util/OpenmrsFhirAddressStrategy.java
+++ b/omod/src/main/java/org/openmrs/module/fhir2/web/util/OpenmrsFhirAddressStrategy.java
@@ -17,13 +17,17 @@
 import ca.uhn.fhir.rest.server.IServerAddressStrategy;
 import lombok.AccessLevel;
 import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
+import org.hibernate.HibernateException;
 import org.openmrs.module.fhir2.FhirConstants;
 import org.openmrs.module.fhir2.api.FhirGlobalPropertyService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
+@Slf4j
 @Setter(AccessLevel.PACKAGE)
 public class OpenmrsFhirAddressStrategy implements IServerAddressStrategy {
 	
@@ -31,8 +35,16 @@ public class OpenmrsFhirAddressStrategy implements IServerAddressStrategy {
 	private FhirGlobalPropertyService globalPropertyService;
 	
 	@Override
+	@Transactional(readOnly = true)
 	public String determineServerBase(ServletContext context, HttpServletRequest request) {
-		String gpPrefix = globalPropertyService.getGlobalProperty(FhirConstants.GLOBAL_PROPERTY_URI_PREFIX);
+		String gpPrefix = null;
+		
+		try {
+			gpPrefix = globalPropertyService.getGlobalProperty(FhirConstants.GLOBAL_PROPERTY_URI_PREFIX);
+		}
+		catch (HibernateException e) {
+			log.error("An error occurred while trying to read the {} property", FhirConstants.GLOBAL_PROPERTY_URI_PREFIX, e);
+		}
 		
 		if (StringUtils.isNotBlank(gpPrefix)) {
 			StringBuilder gpUrl = new StringBuilder().append(gpPrefix);

From 41f6c12c039fd858e1e5765e476b82f48c0a75d7 Mon Sep 17 00:00:00 2001
From: Ian <ian_bacher@brown.edu>
Date: Wed, 20 Jan 2021 11:23:08 -0500
Subject: [PATCH 03/18] Rename test class

---
 ...latorTest.java => BaseProvenanceHandlingTranslatorTest.java} | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
 rename api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/{AbstractProvenanceHandlingTranslatorTest.java => BaseProvenanceHandlingTranslatorTest.java} (99%)

diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/AbstractProvenanceHandlingTranslatorTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/BaseProvenanceHandlingTranslatorTest.java
similarity index 99%
rename from api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/AbstractProvenanceHandlingTranslatorTest.java
rename to api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/BaseProvenanceHandlingTranslatorTest.java
index b2c604dd1..a181c897e 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/AbstractProvenanceHandlingTranslatorTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/BaseProvenanceHandlingTranslatorTest.java
@@ -29,7 +29,7 @@
 import org.openmrs.module.fhir2.api.translators.PractitionerReferenceTranslator;
 
 @RunWith(MockitoJUnitRunner.class)
-public class AbstractProvenanceHandlingTranslatorTest {
+public class BaseProvenanceHandlingTranslatorTest {
 	
 	private static final String USER_UUID = "ddc25312-9798-4e6c-b8f8-269f2dd07cfd";
 	

From 16423202cf860d2da20fa4043beecd6991822df0 Mon Sep 17 00:00:00 2001
From: Ian <ian_bacher@brown.edu>
Date: Wed, 20 Jan 2021 11:26:51 -0500
Subject: [PATCH 04/18] FM2-329 Resolve error being thrown when order type
 unknown

Also makes the code more likely to be able to generate an appropriate link
---
 .../impl/BaseReferenceHandlingTranslator.java | 21 ++---
 .../BaseReferenceHandlingTranslatorTest.java  | 81 +++++++++++++++++++
 .../MedicationRequestTranslatorImplTest.java  | 53 +++++++-----
 ...ionBasedOnReferenceTranslatorImplTest.java | 14 ++--
 .../ServiceRequestTranslatorImplTest.java     |  6 +-
 5 files changed, 140 insertions(+), 35 deletions(-)

diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/BaseReferenceHandlingTranslator.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/BaseReferenceHandlingTranslator.java
index fe7da0102..fc3d7bb12 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/BaseReferenceHandlingTranslator.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/BaseReferenceHandlingTranslator.java
@@ -15,9 +15,11 @@
 
 import lombok.AccessLevel;
 import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
 import org.hl7.fhir.r4.model.Identifier;
 import org.hl7.fhir.r4.model.Reference;
 import org.openmrs.Drug;
+import org.openmrs.DrugOrder;
 import org.openmrs.Encounter;
 import org.openmrs.Location;
 import org.openmrs.Obs;
@@ -28,18 +30,16 @@
 import org.openmrs.PatientIdentifierType;
 import org.openmrs.Person;
 import org.openmrs.Provider;
+import org.openmrs.TestOrder;
 import org.openmrs.User;
 import org.openmrs.Visit;
 import org.openmrs.module.fhir2.FhirConstants;
 import org.openmrs.module.fhir2.api.util.FhirUtils;
 
 @Setter(AccessLevel.PACKAGE)
+@Slf4j
 public abstract class BaseReferenceHandlingTranslator {
 	
-	public static final String DRUG_ORDER_TYPE_UUID = "131168f4-15f5-102d-96e4-000c29c2a5d7";
-	
-	public static final String TEST_ORDER_TYPE_UUID = "52a447d3-a64a-11e3-9aeb-50e549534c5e";
-	
 	protected Reference createEncounterReference(@Nonnull Encounter encounter) {
 		return createEncounterReference((OpenmrsObject) encounter);
 	}
@@ -130,18 +130,19 @@ protected Reference createPractitionerReference(@Nonnull Provider provider) {
 	}
 	
 	protected Reference createOrderReference(@Nonnull Order order) {
-		if (order.getOrderType() == null) {
+		if (order == null) {
 			return null;
 		}
 		
-		if (order.getOrderType().getUuid().equals(TEST_ORDER_TYPE_UUID)) {
+		if (order instanceof TestOrder) {
 			return new Reference().setReference(FhirConstants.SERVICE_REQUEST + "/" + order.getUuid())
 			        .setType(FhirConstants.SERVICE_REQUEST);
-		} else if (order.getOrderType().getUuid().equals(DRUG_ORDER_TYPE_UUID)) {
-			return new Reference().setReference(FhirConstants.MEDICATION + "/" + order.getUuid())
-			        .setType(FhirConstants.MEDICATION);
+		} else if (order instanceof DrugOrder) {
+			return new Reference().setReference(FhirConstants.MEDICATION_REQUEST + "/" + order.getUuid())
+			        .setType(FhirConstants.MEDICATION_REQUEST);
 		} else {
-			throw new IllegalArgumentException("Cannot create reference of undetermined order type");
+			log.warn("Could not determine order type for order {}", order);
+			return null;
 		}
 	}
 	
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/BaseReferenceHandlingTranslatorTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/BaseReferenceHandlingTranslatorTest.java
index 45e61e97d..93253daed 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/BaseReferenceHandlingTranslatorTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/BaseReferenceHandlingTranslatorTest.java
@@ -19,14 +19,17 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.DrugOrder;
 import org.openmrs.Encounter;
 import org.openmrs.Location;
+import org.openmrs.Order;
 import org.openmrs.Patient;
 import org.openmrs.PatientIdentifier;
 import org.openmrs.PatientIdentifierType;
 import org.openmrs.Person;
 import org.openmrs.PersonName;
 import org.openmrs.Provider;
+import org.openmrs.TestOrder;
 import org.openmrs.User;
 import org.openmrs.module.fhir2.FhirConstants;
 
@@ -74,6 +77,12 @@ public class BaseReferenceHandlingTranslatorTest {
 	
 	private static final String PRACTITIONER_REFERENCE = FhirConstants.PRACTITIONER + "/" + USER_UUID;
 	
+	private static final String ORDER_UUID = "7ed459d6-82a2-4cc0-ba44-86c7fcaf3fc5";
+	
+	private static final String TEST_ORDER_REFERENCE = FhirConstants.SERVICE_REQUEST + "/" + ORDER_UUID;
+	
+	private static final String DRUG_ORDER_REFERENCE = FhirConstants.MEDICATION_REQUEST + "/" + ORDER_UUID;
+	
 	private Patient patient;
 	
 	private Provider provider;
@@ -226,6 +235,7 @@ public void shouldReturnReferenceWithDisplayForProviderWithPerson() {
 	@Test
 	public void shouldReturnNullDisplayForPractitionerWithNullPerson() {
 		Reference reference = referenceHandlingTranslator.createPractitionerReference(provider);
+		
 		assertThat(reference, notNullValue());
 		assertThat(reference.getDisplay(), nullValue());
 	}
@@ -262,6 +272,7 @@ public void shouldAddEncounterReference() {
 	@Test
 	public void shouldAddPractitionerGivenOpenMrsUserReference() {
 		Reference reference = referenceHandlingTranslator.createPractitionerReference(user);
+		
 		assertThat(reference, notNullValue());
 		assertThat(reference.getReference(), equalTo(PRACTITIONER_REFERENCE));
 		assertThat(reference.getType(), equalTo(FhirConstants.PRACTITIONER));
@@ -272,9 +283,79 @@ public void shouldAddPractitionerGivenOpenMrsUserReference() {
 	public void shouldReturnReferenceWithNullDisplayIfUserPersonNameIsNull() {
 		User user = new User();
 		user.setUuid(USER_UUID);
+		
 		Reference reference = referenceHandlingTranslator.createPractitionerReference(user);
+		
 		assertThat(reference, notNullValue());
 		assertThat(reference.getReference(), equalTo(PRACTITIONER_REFERENCE));
 		assertThat(reference.getDisplay(), nullValue());
 	}
+	
+	@Test
+	public void shouldAddOrderReferenceForTestOrder() {
+		TestOrder order = new TestOrder();
+		order.setUuid(ORDER_UUID);
+		
+		Reference reference = referenceHandlingTranslator.createOrderReference(order);
+		
+		assertThat(reference, notNullValue());
+		assertThat(reference.getReference(), equalTo(TEST_ORDER_REFERENCE));
+		assertThat(reference.getDisplay(), nullValue());
+	}
+	
+	@Test
+	public void shouldAddOrderReferenceForTestOrderSubclass() {
+		TestOrder order = new TestOrder() {};
+		order.setUuid(ORDER_UUID);
+		
+		Reference reference = referenceHandlingTranslator.createOrderReference(order);
+		
+		assertThat(reference, notNullValue());
+		assertThat(reference.getReference(), equalTo(TEST_ORDER_REFERENCE));
+		assertThat(reference.getDisplay(), nullValue());
+	}
+	
+	@Test
+	public void shouldAddOrderReferenceForDrugOrder() {
+		DrugOrder order = new DrugOrder();
+		order.setUuid(ORDER_UUID);
+		
+		Reference reference = referenceHandlingTranslator.createOrderReference(order);
+		
+		assertThat(reference, notNullValue());
+		assertThat(reference.getReference(), equalTo(DRUG_ORDER_REFERENCE));
+		assertThat(reference.getDisplay(), nullValue());
+	}
+	
+	@Test
+	public void shouldAddOrderReferenceForDrugOrderSubclass() {
+		DrugOrder order = new DrugOrder() {};
+		order.setUuid(ORDER_UUID);
+		
+		Reference reference = referenceHandlingTranslator.createOrderReference(order);
+		
+		assertThat(reference, notNullValue());
+		assertThat(reference.getReference(), equalTo(DRUG_ORDER_REFERENCE));
+		assertThat(reference.getDisplay(), nullValue());
+	}
+	
+	@Test
+	public void shouldReturnNullForRawOrder() {
+		Order order = new Order();
+		order.setUuid(ORDER_UUID);
+		
+		Reference reference = referenceHandlingTranslator.createOrderReference(order);
+		
+		assertThat(reference, nullValue());
+	}
+	
+	@Test
+	public void shouldReturnNullForUnknownOrderSubclass() {
+		Order order = new Order() {};
+		order.setUuid(ORDER_UUID);
+		
+		Reference reference = referenceHandlingTranslator.createOrderReference(order);
+		
+		assertThat(reference, nullValue());
+	}
 }
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTranslatorImplTest.java
index fec111cc5..9ce47ffec 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTranslatorImplTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTranslatorImplTest.java
@@ -19,6 +19,7 @@
 
 import java.lang.reflect.Field;
 
+import lombok.SneakyThrows;
 import org.hl7.fhir.r4.model.Annotation;
 import org.hl7.fhir.r4.model.BooleanType;
 import org.hl7.fhir.r4.model.CodeableConcept;
@@ -53,6 +54,8 @@
 @RunWith(MockitoJUnitRunner.class)
 public class MedicationRequestTranslatorImplTest {
 	
+	public static final String DRUG_ORDER_TYPE_UUID = "131168f4-15f5-102d-96e4-000c29c2a5d7";
+	
 	private static final String DRUG_ORDER_UUID = "44fdc8ad-fe4d-499b-93a8-8a991c1d477e";
 	
 	private static final String DISCONTINUED_DRUG_ORDER_UUID = "efca4077-493c-496b-8312-856ee5d1cc27";
@@ -61,7 +64,8 @@ public class MedicationRequestTranslatorImplTest {
 	
 	private static final String DISCONTINUED_DRUG_ORDER_NUMBER = "ORD-2";
 	
-	private static final String PRIOR_MEDICATION_REQUEST_REFERENCE = FhirConstants.MEDICATION + "/" + DRUG_ORDER_UUID;
+	private static final String PRIOR_MEDICATION_REQUEST_REFERENCE = FhirConstants.MEDICATION_REQUEST + "/"
+	        + DRUG_ORDER_UUID;
 	
 	private static final String DRUG_UUID = "99fdc8ad-fe4d-499b-93a8-8a991c1d477g";
 	
@@ -125,8 +129,9 @@ public void setup() {
 		drugOrder = new DrugOrder();
 		drugOrder.setUuid(DRUG_ORDER_UUID);
 		setOrderNumberByReflection(drugOrder, DRUG_ORDER_NUMBER);
+		
 		OrderType ordertype = new OrderType();
-		ordertype.setUuid(BaseReferenceHandlingTranslator.DRUG_ORDER_TYPE_UUID);
+		ordertype.setUuid(DRUG_ORDER_TYPE_UUID);
 		drugOrder.setOrderType(ordertype);
 		
 		discontinuedDrugOrder = new DrugOrder();
@@ -142,6 +147,7 @@ public void setup() {
 	@Test
 	public void toOpenMrsType_shouldTranslateToOpenType() {
 		DrugOrder result = medicationRequestTranslator.toOpenmrsType(new DrugOrder(), medicationRequest);
+		
 		assertThat(result, notNullValue());
 		assertThat(result.getUuid(), notNullValue());
 		assertThat(result.getUuid(), equalTo(DRUG_ORDER_UUID));
@@ -150,6 +156,7 @@ public void toOpenMrsType_shouldTranslateToOpenType() {
 	@Test
 	public void toFhirResource_shouldTranslateToFhirResource() {
 		MedicationRequest result = medicationRequestTranslator.toFhirResource(drugOrder);
+		
 		assertThat(result, notNullValue());
 		assertThat(result.getId(), notNullValue());
 		assertThat(result.getId(), equalTo(DRUG_ORDER_UUID));
@@ -203,9 +210,12 @@ public void toOpenMrsType_shouldThrowExceptionIfMedicationIsNull() {
 	public void toFhirResource_shouldConvertStatusToFhirType() {
 		drugOrder.setVoided(true);
 		drugOrder.setVoidedBy(new User());
+		
 		when(medicationRequestStatusTranslator.toFhirResource(drugOrder))
 		        .thenReturn(MedicationRequest.MedicationRequestStatus.CANCELLED);
+		
 		MedicationRequest result = medicationRequestTranslator.toFhirResource(drugOrder);
+		
 		assertThat(result, notNullValue());
 		assertThat(result.getStatus(), notNullValue());
 		assertThat(result.getStatus(), equalTo(MedicationRequest.MedicationRequestStatus.CANCELLED));
@@ -215,7 +225,9 @@ public void toFhirResource_shouldConvertStatusToFhirType() {
 	public void toFhirResource_shouldConvertActiveStatusToFhirType() {
 		when(medicationRequestStatusTranslator.toFhirResource(drugOrder))
 		        .thenReturn(MedicationRequest.MedicationRequestStatus.ACTIVE);
+		
 		MedicationRequest result = medicationRequestTranslator.toFhirResource(drugOrder);
+		
 		assertThat(result, notNullValue());
 		assertThat(result.getStatus(), notNullValue());
 		assertThat(result.getStatus(), equalTo(MedicationRequest.MedicationRequestStatus.ACTIVE));
@@ -326,6 +338,7 @@ public void toFhirResource_shouldTranslatePatientToFhirType() {
 		patientReference.setReference(FhirConstants.PATIENT + "/" + PATIENT_UUID);
 		
 		when(patientReferenceTranslator.toFhirResource(patient)).thenReturn(patientReference);
+		
 		MedicationRequest result = medicationRequestTranslator.toFhirResource(drugOrder);
 		
 		assertThat(result, notNullValue());
@@ -338,6 +351,7 @@ public void toFhirResource_shouldTranslateUrgencyToFhirType() {
 		drugOrder.setUrgency(DrugOrder.Urgency.ON_SCHEDULED_DATE);
 		when(medicationRequestPriorityTranslator.toFhirResource(DrugOrder.Urgency.ON_SCHEDULED_DATE))
 		        .thenReturn(MedicationRequest.MedicationRequestPriority.URGENT);
+		
 		MedicationRequest result = medicationRequestTranslator.toFhirResource(drugOrder);
 		
 		assertThat(result, notNullValue());
@@ -349,7 +363,7 @@ public void toFhirResource_shouldTranslateUrgencyToFhirType() {
 	@Test
 	public void toOpenMrsType_shouldTranslateMedicationToOpenMrsDrug() {
 		Reference medicationRef = new Reference();
-		medicationRef.setReference(FhirConstants.MEDICATION + "/" + DRUG_UUID);
+		medicationRef.setReference(FhirConstants.MEDICATION_REQUEST + "/" + DRUG_UUID);
 		Drug drug = new Drug();
 		drug.setUuid(DRUG_UUID);
 		
@@ -368,9 +382,10 @@ public void toFhirResource_shouldTranslateDrugToMedicationReference() {
 		drug.setUuid(DRUG_UUID);
 		drugOrder.setDrug(drug);
 		Reference medicationRef = new Reference();
-		medicationRef.setReference(FhirConstants.MEDICATION + "/" + DRUG_UUID);
+		medicationRef.setReference(FhirConstants.MEDICATION_REQUEST + "/" + DRUG_UUID);
 		
 		when(medicationReferenceTranslator.toFhirResource(drug)).thenReturn(medicationRef);
+		
 		MedicationRequest result = medicationRequestTranslator.toFhirResource(drugOrder);
 		
 		assertThat(result, notNullValue());
@@ -389,7 +404,9 @@ public void toFhirResource_shouldTranslateOrderReasonToReasonCode() {
 		codeableConcept.addCoding(new Coding().setCode(concept.getConceptId().toString()));
 		
 		when(conceptTranslator.toFhirResource(concept)).thenReturn(codeableConcept);
+		
 		MedicationRequest result = medicationRequestTranslator.toFhirResource(drugOrder);
+		
 		assertThat(result, notNullValue());
 		assertThat(result.getReasonCode(), not(empty()));
 		assertThat(result.getReasonCodeFirstRep(), equalTo(codeableConcept));
@@ -406,7 +423,9 @@ public void toOpenMrsType_shouldTranslateReasonCodeToOrderReason() {
 		medicationRequest.addReasonCode(codeableConcept);
 		
 		when(conceptTranslator.toOpenmrsType(codeableConcept)).thenReturn(concept);
+		
 		DrugOrder drugOrder = medicationRequestTranslator.toOpenmrsType(new DrugOrder(), medicationRequest);
+		
 		assertThat(drugOrder, notNullValue());
 		assertThat(drugOrder.getOrderReason(), notNullValue());
 		assertThat(drugOrder.getOrderReason().getUuid(), equalTo(CONCEPT_UUID));
@@ -418,6 +437,7 @@ public void toFhirResource_shouldTranslateCommentToFulFillerToNote() {
 		drugOrder.setCommentToFulfiller(COMMENT_TO_THE_FULL_FILLER);
 		
 		MedicationRequest result = medicationRequestTranslator.toFhirResource(drugOrder);
+		
 		assertThat(result, notNullValue());
 		assertThat(result.getNote(), not(empty()));
 		assertThat(result.getNoteFirstRep().getText(), equalTo(COMMENT_TO_THE_FULL_FILLER));
@@ -428,6 +448,7 @@ public void toOpenMrsType_shouldTranslateNoteToCommentToFullFiller() {
 		medicationRequest.addNote(new Annotation().setText(COMMENT_TO_THE_FULL_FILLER));
 		
 		DrugOrder result = medicationRequestTranslator.toOpenmrsType(new DrugOrder(), medicationRequest);
+		
 		assertThat(result, notNullValue());
 		assertThat(result.getCommentToFulfiller(), equalTo(COMMENT_TO_THE_FULL_FILLER));
 	}
@@ -453,6 +474,7 @@ public void toFhirResource_shouldAddDosageInstructions() {
 		when(dosageTranslator.toFhirResource(drugOrder)).thenReturn(dosage);
 		
 		MedicationRequest result = medicationRequestTranslator.toFhirResource(drugOrder);
+		
 		assertThat(result, notNullValue());
 		assertThat(result.getDosageInstructionFirstRep(), notNullValue());
 		assertThat(result.getDosageInstructionFirstRep().getText(), equalTo(DOSING_INSTRUCTIONS));
@@ -460,20 +482,15 @@ public void toFhirResource_shouldAddDosageInstructions() {
 		assertThat(result.getDosageInstructionFirstRep().getRoute(), equalTo(codeableConcept));
 	}
 	
-	private DrugOrder setOrderNumberByReflection(DrugOrder order, String orderNumber) {
-		try {
-			Class clazz = order.getClass();
-			Field orderNumberField = clazz.getSuperclass().getDeclaredField("orderNumber");
-			Boolean isAccessible = orderNumberField.isAccessible();
-			if (!isAccessible) {
-				orderNumberField.setAccessible(true);
-			}
-			orderNumberField.set(((Order) order), orderNumber);
-		}
-		catch (Exception e) {
-			e.printStackTrace();
+	@SneakyThrows
+	private void setOrderNumberByReflection(DrugOrder order, String orderNumber) {
+		Class<? extends DrugOrder> clazz = order.getClass();
+		Field orderNumberField = clazz.getSuperclass().getDeclaredField("orderNumber");
+		boolean isAccessible = orderNumberField.isAccessible();
+		if (!isAccessible) {
+			orderNumberField.setAccessible(true);
 		}
-		return order;
+		
+		orderNumberField.set(order, orderNumber);
 	}
-	
 }
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ObservationBasedOnReferenceTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ObservationBasedOnReferenceTranslatorImplTest.java
index 6e6ed3c5b..42afcd9bf 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ObservationBasedOnReferenceTranslatorImplTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ObservationBasedOnReferenceTranslatorImplTest.java
@@ -62,8 +62,9 @@ public void toFhirResource_shouldReturnNullWhenCalledWithNullObject() {
 	
 	@Test
 	public void toFhirResource_shouldConvertTestOrderToReference() {
-		Order order = new Order();
+		Order order = new TestOrder();
 		order.setUuid(ORDER_UUID);
+		
 		OrderType orderType = new OrderType();
 		orderType.setUuid(TEST_ORDER_TYPE_UUID);
 		order.setOrderType(orderType);
@@ -76,8 +77,9 @@ public void toFhirResource_shouldConvertTestOrderToReference() {
 	
 	@Test
 	public void toFhirResource_shouldConvertDrugOrderToReference() {
-		Order order = new Order();
+		Order order = new DrugOrder();
 		order.setUuid(ORDER_UUID);
+		
 		OrderType orderType = new OrderType();
 		orderType.setUuid(DRUG_ORDER_TYPE_UUID);
 		order.setOrderType(orderType);
@@ -85,7 +87,7 @@ public void toFhirResource_shouldConvertDrugOrderToReference() {
 		Reference result = translator.toFhirResource(order);
 		
 		assertThat(result, notNullValue());
-		assertThat(result.getType(), equalTo(FhirConstants.MEDICATION));
+		assertThat(result.getType(), equalTo(FhirConstants.MEDICATION_REQUEST));
 	}
 	
 	@Test
@@ -98,9 +100,9 @@ public void toFhirResource_shouldReturnNullIfOrderTypeIsNull() {
 		assertThat(result, nullValue());
 	}
 	
-	@Test(expected = IllegalArgumentException.class)
-	public void toFhirType_shouldThrowIllegalArgumentExceptionException() {
-		Order order = new DrugOrder();
+	@Test
+	public void toFhirType_shouldReturnNullForUnknownOrderType() {
+		Order order = new Order() {};
 		order.setUuid(ORDER_UUID);
 		
 		OrderType orderType = new OrderType();
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImplTest.java
index 1476492df..90b7acf0c 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImplTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImplTest.java
@@ -58,6 +58,8 @@
 @RunWith(MockitoJUnitRunner.class)
 public class ServiceRequestTranslatorImplTest {
 	
+	public static final String TEST_ORDER_TYPE_UUID = "52a447d3-a64a-11e3-9aeb-50e549534c5e";
+	
 	private static final String SERVICE_REQUEST_UUID = "4e4851c3-c265-400e-acc9-1f1b0ac7f9c4";
 	
 	private static final String DISCONTINUED_TEST_ORDER_UUID = "efca4077-493c-496b-8312-856ee5d1cc27";
@@ -116,8 +118,9 @@ public void setup() {
 		order = new TestOrder();
 		order.setUuid(SERVICE_REQUEST_UUID);
 		setOrderNumberByReflection(order, TEST_ORDER_NUMBER);
+		
 		OrderType ordertype = new OrderType();
-		ordertype.setUuid(BaseReferenceHandlingTranslator.TEST_ORDER_TYPE_UUID);
+		ordertype.setUuid(TEST_ORDER_TYPE_UUID);
 		order.setOrderType(ordertype);
 		
 		discontinuedTestOrder = new TestOrder();
@@ -126,6 +129,7 @@ public void setup() {
 		discontinuedTestOrder.setPreviousOrder(order);
 	}
 	
+	@Test
 	public void toFhirResource_shouldTranslateToFhirResourceWithReplacesFieldGivenDiscontinuedOrder() {
 		discontinuedTestOrder.setAction(Order.Action.DISCONTINUE);
 		

From 24c4a9bc2d68cacf192ff28b0ab30f40170564dd Mon Sep 17 00:00:00 2001
From: dkayiwa <kayiwadaniel@gmail.com>
Date: Fri, 22 Jan 2021 00:05:35 +0300
Subject: [PATCH 05/18] FM2-331: Should run on PostgreSQL

---
 api/src/main/resources/liquibase.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/api/src/main/resources/liquibase.xml b/api/src/main/resources/liquibase.xml
index 2daef5eda..a6d18b98e 100644
--- a/api/src/main/resources/liquibase.xml
+++ b/api/src/main/resources/liquibase.xml
@@ -67,7 +67,7 @@
             <and>
                 <tableExists tableName="fhir_concept_source"/>
                 <tableExists tableName="concept_reference_source"/>
-                <sqlCheck expectedResult="1">select 1 from concept_reference_source where name='LOINC' and retired = 0
+                <sqlCheck expectedResult="1">select 1 from concept_reference_source where name='LOINC' and retired = false
                 </sqlCheck>
                 <sqlCheck expectedResult="0">select count(*) from fhir_concept_source where uuid =
                     '249b13c8-72fa-4b96-8d3d-b200efed985e'
@@ -78,7 +78,7 @@
             insert into fhir_concept_source (name, concept_source_id, url, creator, date_created, uuid)
             select name, concept_source_id, 'http://loinc.org', 1, now(), '249b13c8-72fa-4b96-8d3d-b200efed985e'
             from concept_reference_source
-            where name = 'LOINC' and retired = 0
+            where name = 'LOINC' and retired = false
         ]]></sql>
     </changeSet>
 
@@ -87,7 +87,7 @@
             <and>
                 <tableExists tableName="fhir_concept_source"/>
                 <tableExists tableName="concept_reference_source"/>
-                <sqlCheck expectedResult="1">select 1 from concept_reference_source where name='CIEL' and retired = 0
+                <sqlCheck expectedResult="1">select 1 from concept_reference_source where name='CIEL' and retired = false
                 </sqlCheck>
                 <sqlCheck expectedResult="0">select count(*) from fhir_concept_source where uuid =
                     '2b3c1ff8-768a-102f-83f4-12313b04a615'
@@ -98,7 +98,7 @@
             insert into fhir_concept_source (name, concept_source_id, url, creator, date_created, uuid)
             select name, concept_source_id, 'urn:oid:2.16.840.1.113883.3.7201', 1, now(), '2b3c1ff8-768a-102f-83f4-12313b04a615'
             from concept_reference_source
-            where name = 'CIEL' and retired = 0
+            where name = 'CIEL' and retired = false
         ]]></sql>
     </changeSet>
 

From 5d046de2f0fd8fd2b28e169aceac4dd86f9c2524 Mon Sep 17 00:00:00 2001
From: Ankit kumar <49350053+theanandankit@users.noreply.github.com>
Date: Mon, 1 Feb 2021 20:09:41 +0530
Subject: [PATCH 06/18] FM2-324: Mapping ServiceRequest status for TestOrders
 (#314)

---
 .../impl/ServiceRequestTranslatorImpl.java    |  54 +++----
 .../ServiceRequestTranslatorImplTest.java     | 140 ++++++++++++------
 2 files changed, 117 insertions(+), 77 deletions(-)

diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImpl.java
index 1b053d967..433f9f35f 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImpl.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImpl.java
@@ -15,6 +15,7 @@
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.stream.Collectors;
 
 import ca.uhn.fhir.rest.api.server.IBundleProvider;
@@ -75,7 +76,7 @@ public ServiceRequest toFhirResource(@Nonnull TestOrder order) {
 		
 		serviceRequest.setId(order.getUuid());
 		
-		serviceRequest.setStatus(determineServiceRequestStatus(order.getUuid()));
+		serviceRequest.setStatus(determineServiceRequestStatus(order));
 		
 		serviceRequest.setCode(conceptTranslator.toFhirResource(order.getConcept()));
 		
@@ -111,41 +112,24 @@ public TestOrder toOpenmrsType(@Nonnull ServiceRequest resource) {
 		throw new UnsupportedOperationException();
 	}
 	
-	private ServiceRequest.ServiceRequestStatus determineServiceRequestStatus(String orderUuid) {
-		IBundleProvider results = taskService.searchForTasks(
-		    new ReferenceAndListParam()
-		            .addAnd(new ReferenceOrListParam().add(new ReferenceParam("ServiceRequest", null, orderUuid))),
-		    null, null, null, null, null);
-		
-		Collection<Task> serviceRequestTasks = results.getResources(START_INDEX, END_INDEX).stream().map(p -> (Task) p)
-		        .collect(Collectors.toList());
-		
-		ServiceRequest.ServiceRequestStatus serviceRequestStatus = ServiceRequest.ServiceRequestStatus.UNKNOWN;
-		
-		if (serviceRequestTasks.size() != 1) {
-			return serviceRequestStatus;
-		}
-		
-		Task serviceRequestTask = serviceRequestTasks.iterator().next();
-		
-		if (serviceRequestTask.hasStatus()) {
-			switch (serviceRequestTask.getStatus()) {
-				case ACCEPTED:
-				
-				case REQUESTED:
-					serviceRequestStatus = ServiceRequest.ServiceRequestStatus.ACTIVE;
-					break;
-				
-				case REJECTED:
-					serviceRequestStatus = ServiceRequest.ServiceRequestStatus.REVOKED;
-					break;
-				
-				case COMPLETED:
-					serviceRequestStatus = ServiceRequest.ServiceRequestStatus.COMPLETED;
-					break;
-			}
+	private ServiceRequest.ServiceRequestStatus determineServiceRequestStatus(TestOrder order) {
+		
+		Date currentDate = new Date();
+		
+		boolean isCompeted = order.isActivated()
+		        && ((order.getDateStopped() != null && currentDate.after(order.getDateStopped()))
+		                || (order.getAutoExpireDate() != null && currentDate.after(order.getAutoExpireDate())));
+		boolean isDiscontinued = order.isActivated() && order.getAction() == Order.Action.DISCONTINUE;
+		
+		if ((isCompeted && isDiscontinued)) {
+			return ServiceRequest.ServiceRequestStatus.UNKNOWN;
+		} else if (isDiscontinued) {
+			return ServiceRequest.ServiceRequestStatus.REVOKED;
+		} else if (isCompeted) {
+			return ServiceRequest.ServiceRequestStatus.COMPLETED;
+		} else {
+			return ServiceRequest.ServiceRequestStatus.ACTIVE;
 		}
-		return serviceRequestStatus;
 	}
 	
 	private Reference determineServiceRequestPerformer(String orderUuid) {
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImplTest.java
index 90b7acf0c..7acecadcb 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImplTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ServiceRequestTranslatorImplTest.java
@@ -21,7 +21,7 @@
 import static org.mockito.Mockito.when;
 
 import java.lang.reflect.Field;
-import java.util.Arrays;
+import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -54,6 +54,7 @@
 import org.openmrs.module.fhir2.api.translators.PatientReferenceTranslator;
 import org.openmrs.module.fhir2.api.translators.PractitionerReferenceTranslator;
 import org.openmrs.module.fhir2.providers.r4.MockIBundleProvider;
+import org.openmrs.order.OrderUtilTest;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ServiceRequestTranslatorImplTest {
@@ -197,14 +198,15 @@ public void toFhirResource_shouldTranslateOpenmrsTestOrderToFhirServiceRequest()
 	}
 	
 	@Test
-	public void toFhirResource_shouldTranslateOrderFromRequestedTaskToActiveServiceRequest() {
+	public void toFhirResource_shouldTranslateOrderFromOnlyDateActivatedToActiveServiceRequest() {
 		TestOrder newOrder = new TestOrder();
-		newOrder.setUuid(SERVICE_REQUEST_UUID);
-		
-		List<Task> tasks = setUpBasedOnScenario(Task.TaskStatus.REQUESTED);
 		
 		when(taskService.searchForTasks(any(), any(), any(), any(), any(), any()))
-		        .thenReturn(new MockIBundleProvider<>(tasks, PREFERRED_PAGE_SIZE, COUNT));
+		        .thenReturn(new MockIBundleProvider<>(Collections.emptyList(), PREFERRED_PAGE_SIZE, COUNT));
+		
+		Calendar activationDate = Calendar.getInstance();
+		activationDate.set(2000, Calendar.APRIL, 16);
+		newOrder.setDateActivated(activationDate.getTime());
 		
 		ServiceRequest result = translator.toFhirResource(newOrder);
 		
@@ -213,30 +215,40 @@ public void toFhirResource_shouldTranslateOrderFromRequestedTaskToActiveServiceR
 	}
 	
 	@Test
-	public void toFhirResource_shouldTranslateOrderFromRejectedTaskToRevokedServiceRequest() {
+	public void toFhirResource_shouldTranslateOrderFromAutoExpireToCompleteServiceRequest() throws Exception {
 		TestOrder newOrder = new TestOrder();
-		newOrder.setUuid(SERVICE_REQUEST_UUID);
-		
-		List<Task> tasks = setUpBasedOnScenario(Task.TaskStatus.REJECTED);
 		
 		when(taskService.searchForTasks(any(), any(), any(), any(), any(), any()))
-		        .thenReturn(new MockIBundleProvider<>(tasks, PREFERRED_PAGE_SIZE, COUNT));
+		        .thenReturn(new MockIBundleProvider<>(Collections.emptyList(), PREFERRED_PAGE_SIZE, COUNT));
+		
+		Calendar date = Calendar.getInstance();
+		date.set(2000, Calendar.APRIL, 16);
+		newOrder.setDateActivated(date.getTime());
+		date.set(2070, Calendar.APRIL, 16);
+		newOrder.setAutoExpireDate(date.getTime());
+		date.set(2010, Calendar.APRIL, 16);
+		OrderUtilTest.setDateStopped(newOrder, date.getTime());
 		
 		ServiceRequest result = translator.toFhirResource(newOrder);
 		
 		assertThat(result, notNullValue());
-		assertThat(result.getStatus(), equalTo(ServiceRequest.ServiceRequestStatus.REVOKED));
+		assertThat(result.getStatus(), equalTo(ServiceRequest.ServiceRequestStatus.COMPLETED));
 	}
 	
 	@Test
-	public void toFhirResource_shouldTranslateOrderFromAcceptedTaskToActiveServiceRequest() {
+	public void toFhirResource_shouldTranslateOrderToActiveServiceRequest() throws Exception {
 		TestOrder newOrder = new TestOrder();
-		newOrder.setUuid(SERVICE_REQUEST_UUID);
-		
-		List<Task> tasks = setUpBasedOnScenario(Task.TaskStatus.ACCEPTED);
 		
 		when(taskService.searchForTasks(any(), any(), any(), any(), any(), any()))
-		        .thenReturn(new MockIBundleProvider<>(tasks, PREFERRED_PAGE_SIZE, COUNT));
+		        .thenReturn(new MockIBundleProvider<>(Collections.emptyList(), PREFERRED_PAGE_SIZE, COUNT));
+		
+		Calendar date = Calendar.getInstance();
+		date.set(2000, Calendar.APRIL, 16);
+		newOrder.setDateActivated(date.getTime());
+		date.set(2070, Calendar.APRIL, 16);
+		newOrder.setAutoExpireDate(date.getTime());
+		date.set(2069, Calendar.APRIL, 16);
+		OrderUtilTest.setDateStopped(newOrder, date.getTime());
 		
 		ServiceRequest result = translator.toFhirResource(newOrder);
 		
@@ -245,14 +257,19 @@ public void toFhirResource_shouldTranslateOrderFromAcceptedTaskToActiveServiceRe
 	}
 	
 	@Test
-	public void toFhirResource_shouldTranslateOrderFromCompletedTaskToCompletedServiceRequest() {
+	public void toFhirResource_shouldTranslateOrderToCompletedServiceRequest() throws Exception {
 		TestOrder newOrder = new TestOrder();
-		newOrder.setUuid(SERVICE_REQUEST_UUID);
-		
-		List<Task> tasks = setUpBasedOnScenario(Task.TaskStatus.COMPLETED);
 		
 		when(taskService.searchForTasks(any(), any(), any(), any(), any(), any()))
-		        .thenReturn(new MockIBundleProvider<>(tasks, PREFERRED_PAGE_SIZE, COUNT));
+		        .thenReturn(new MockIBundleProvider<>(Collections.emptyList(), PREFERRED_PAGE_SIZE, COUNT));
+		
+		Calendar date = Calendar.getInstance();
+		date.set(2000, Calendar.APRIL, 16);
+		newOrder.setDateActivated(date.getTime());
+		date.set(2011, Calendar.APRIL, 16);
+		newOrder.setAutoExpireDate(date.getTime());
+		date.set(2010, Calendar.APRIL, 16);
+		OrderUtilTest.setDateStopped(newOrder, date.getTime());
 		
 		ServiceRequest result = translator.toFhirResource(newOrder);
 		
@@ -261,14 +278,20 @@ public void toFhirResource_shouldTranslateOrderFromCompletedTaskToCompletedServi
 	}
 	
 	@Test
-	public void toFhirResource_shouldTranslateOrderFromOtherTaskToUnknownServiceRequest() {
+	public void toFhirResource_shouldTranslateWrongOrderFromActiveToUnknownServiceRequest() throws Exception {
 		TestOrder newOrder = new TestOrder();
-		newOrder.setUuid(SERVICE_REQUEST_UUID);
-		
-		List<Task> tasks = setUpBasedOnScenario(Task.TaskStatus.DRAFT);
 		
 		when(taskService.searchForTasks(any(), any(), any(), any(), any(), any()))
-		        .thenReturn(new MockIBundleProvider<>(tasks, PREFERRED_PAGE_SIZE, COUNT));
+		        .thenReturn(new MockIBundleProvider<>(Collections.emptyList(), PREFERRED_PAGE_SIZE, COUNT));
+		
+		Calendar date = Calendar.getInstance();
+		date.set(2000, Calendar.APRIL, 16);
+		newOrder.setDateActivated(date.getTime());
+		date.set(2015, Calendar.APRIL, 16);
+		newOrder.setAutoExpireDate(date.getTime());
+		date.set(2010, Calendar.APRIL, 16);
+		newOrder.setAction(Order.Action.DISCONTINUE);
+		OrderUtilTest.setDateStopped(newOrder, date.getTime());
 		
 		ServiceRequest result = translator.toFhirResource(newOrder);
 		
@@ -277,43 +300,76 @@ public void toFhirResource_shouldTranslateOrderFromOtherTaskToUnknownServiceRequ
 	}
 	
 	@Test
-	public void toFhirResource_shouldTranslateOrderWithoutTaskToUnknownServiceRequest() {
+	public void toFhirResource_shouldTranslateWrongOrderFromCompleteToUnknownServiceRequest() throws Exception {
 		TestOrder newOrder = new TestOrder();
-		newOrder.setUuid(SERVICE_REQUEST_UUID);
 		
 		when(taskService.searchForTasks(any(), any(), any(), any(), any(), any()))
 		        .thenReturn(new MockIBundleProvider<>(Collections.emptyList(), PREFERRED_PAGE_SIZE, COUNT));
 		
+		Calendar date = Calendar.getInstance();
+		date.set(2000, Calendar.APRIL, 16);
+		newOrder.setDateActivated(date.getTime());
+		date.set(2070, Calendar.APRIL, 16);
+		newOrder.setAutoExpireDate(date.getTime());
+		date.set(2069, Calendar.APRIL, 16);
+		newOrder.setAction(Order.Action.DISCONTINUE);
+		OrderUtilTest.setDateStopped(newOrder, date.getTime());
+		
 		ServiceRequest result = translator.toFhirResource(newOrder);
 		
 		assertThat(result, notNullValue());
-		assertThat(result.getStatus(), equalTo(ServiceRequest.ServiceRequestStatus.UNKNOWN));
+		assertThat(result.getStatus(), equalTo(ServiceRequest.ServiceRequestStatus.REVOKED));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateOrderFromOnlyAutoExpireToCompleteServiceRequest() throws Exception {
+		TestOrder newOrder = new TestOrder();
+		
+		when(taskService.searchForTasks(any(), any(), any(), any(), any(), any()))
+		        .thenReturn(new MockIBundleProvider<>(Collections.emptyList(), PREFERRED_PAGE_SIZE, COUNT));
+		
+		Calendar date = Calendar.getInstance();
+		date.set(2000, Calendar.APRIL, 16);
+		newOrder.setDateActivated(date.getTime());
+		date.set(2015, Calendar.APRIL, 16);
+		newOrder.setAutoExpireDate(date.getTime());
+		
+		ServiceRequest result = translator.toFhirResource(newOrder);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getStatus(), equalTo(ServiceRequest.ServiceRequestStatus.COMPLETED));
 	}
 	
 	@Test
-	public void toFhirResource_shouldTranslateOrderWithMultipleTasksToUnknownServiceRequest() {
+	public void toFhirResource_shouldTranslateOrderFromOnlyDateStoppedToCompleteServiceRequest() throws Exception {
 		TestOrder newOrder = new TestOrder();
-		newOrder.setUuid(SERVICE_REQUEST_UUID);
 		
-		Task firstTask = new Task();
-		Task secondTask = new Task();
+		when(taskService.searchForTasks(any(), any(), any(), any(), any(), any()))
+		        .thenReturn(new MockIBundleProvider<>(Collections.emptyList(), PREFERRED_PAGE_SIZE, COUNT));
 		
-		Reference basedOnRef = new Reference();
-		basedOnRef.setReference("ServiceRequest/" + SERVICE_REQUEST_UUID);
-		basedOnRef.setType("ServiceRequest");
+		Calendar date = Calendar.getInstance();
+		date.set(2000, Calendar.APRIL, 16);
+		newOrder.setDateActivated(date.getTime());
+		date.set(2015, Calendar.APRIL, 16);
+		OrderUtilTest.setDateStopped(newOrder, date.getTime());
 		
-		firstTask.addBasedOn(basedOnRef);
-		secondTask.addBasedOn(basedOnRef);
+		ServiceRequest result = translator.toFhirResource(newOrder);
 		
-		List<Task> tasks = Arrays.asList(firstTask, secondTask);
+		assertThat(result, notNullValue());
+		assertThat(result.getStatus(), equalTo(ServiceRequest.ServiceRequestStatus.COMPLETED));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateFromNoDataToActiveServiceRequest() {
+		TestOrder newOrder = new TestOrder();
 		
 		when(taskService.searchForTasks(any(), any(), any(), any(), any(), any()))
-		        .thenReturn(new MockIBundleProvider<>(tasks, PREFERRED_PAGE_SIZE, COUNT));
+		        .thenReturn(new MockIBundleProvider<>(Collections.emptyList(), PREFERRED_PAGE_SIZE, COUNT));
 		
 		ServiceRequest result = translator.toFhirResource(newOrder);
 		
 		assertThat(result, notNullValue());
-		assertThat(result.getStatus(), equalTo(ServiceRequest.ServiceRequestStatus.UNKNOWN));
+		assertThat(result.getStatus(), equalTo(ServiceRequest.ServiceRequestStatus.ACTIVE));
 	}
 	
 	@Test

From 9e024e10854e76298c4bdb8936431599744d9772 Mon Sep 17 00:00:00 2001
From: Mutesasira Moses <mozzymutesa@gmail.com>
Date: Mon, 1 Feb 2021 17:42:51 +0300
Subject: [PATCH 07/18] FM2-307: Implementing Condition Resource for OpenMRS
 2.1 And Below (#308)

---
 .../dao/impl/FhirConditionDaoImpl_2_2.java    | 120 ---
 .../impl/FhirConditionDaoImpl_2_2Test.java    |   6 -
 .../ConditionSearchQueryImpl_2_2Test.java     |   2 +-
 .../openmrs/module/fhir2/FhirConstants.java   |   2 +
 .../module/fhir2/api/dao/impl/BaseDao.java    | 112 +++
 .../api/dao/impl/FhirConditionDaoImpl.java    | 135 +++
 .../api/impl/FhirConditionServiceImpl.java    |  73 +-
 .../fhir2/api/search/SearchQueryInclude.java  |   5 +
 ...ConditionClinicalStatusTranslatorImpl.java |  34 +
 .../impl/ConditionTranslatorImpl.java         | 127 +++
 .../MockedCalendarFactoryConfiguration.java   |  27 +
 .../dao/impl/FhirConditionDaoImplTest.java    | 170 ++++
 .../impl/FhirConditionServiceImplTest.java    | 223 ++++-
 .../api/search/ConditionSearchQueryTest.java  | 871 ++++++++++++++++++
 .../impl/ConditionTranslatorImplTest.java     | 302 ++++++
 ...ditionResourceProviderIntegrationTest.java |   2 +
 ...onFhirResourceProviderIntegrationTest.java | 441 +++++++++
 ...onFhirResourceProviderIntegrationTest.java | 436 +++++++++
 ...irObsConditionDaoImplTest_initial_data.xml |  27 +
 .../providers/ConditionWebTest_create_r3.json |   2 +-
 .../providers/ConditionWebTest_create_r3.xml  |   4 +-
 21 files changed, 2933 insertions(+), 188 deletions(-)
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ConditionClinicalStatusTranslatorImpl.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ConditionTranslatorImpl.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/MockedCalendarFactoryConfiguration.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImplTest.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/api/search/ConditionSearchQueryTest.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ConditionTranslatorImplTest.java
 create mode 100644 integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r3/ConditionFhirResourceProviderIntegrationTest.java
 create mode 100644 integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r4/ConditionFhirResourceProviderIntegrationTest.java
 create mode 100644 test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirObsConditionDaoImplTest_initial_data.xml

diff --git a/api-2.2/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl_2_2.java b/api-2.2/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl_2_2.java
index 868fe4dec..c6d17c80a 100644
--- a/api-2.2/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl_2_2.java
+++ b/api-2.2/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl_2_2.java
@@ -9,30 +9,15 @@
  */
 package org.openmrs.module.fhir2.api.dao.impl;
 
-import static org.hibernate.criterion.Restrictions.and;
 import static org.hibernate.criterion.Restrictions.eq;
-import static org.hibernate.criterion.Restrictions.ge;
-import static org.hibernate.criterion.Restrictions.le;
-import static org.hibernate.criterion.Restrictions.not;
 
 import javax.annotation.Nonnull;
 
-import java.math.BigDecimal;
-import java.time.Duration;
-import java.time.LocalDateTime;
-import java.time.Period;
-import java.time.ZoneId;
-import java.time.temporal.ChronoUnit;
-import java.time.temporal.TemporalAmount;
-import java.time.temporal.TemporalUnit;
-import java.util.Date;
 import java.util.List;
 import java.util.Optional;
 
 import ca.uhn.fhir.rest.param.DateRangeParam;
-import ca.uhn.fhir.rest.param.ParamPrefixEnum;
 import ca.uhn.fhir.rest.param.QuantityAndListParam;
-import ca.uhn.fhir.rest.param.QuantityParam;
 import ca.uhn.fhir.rest.param.ReferenceAndListParam;
 import ca.uhn.fhir.rest.param.TokenAndListParam;
 import lombok.AccessLevel;
@@ -46,9 +31,7 @@
 import org.openmrs.module.fhir2.FhirConstants;
 import org.openmrs.module.fhir2.api.dao.FhirConditionDao;
 import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
-import org.openmrs.module.fhir2.api.util.LocalDateTimeFactory;
 import org.openmrs.util.PrivilegeConstants;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Primary;
 import org.springframework.stereotype.Component;
 
@@ -58,9 +41,6 @@
 @OpenmrsProfile(openmrsPlatformVersion = "2.2.* - 2.*")
 public class FhirConditionDaoImpl_2_2 extends BaseFhirDao<Condition> implements FhirConditionDao<Condition> {
 	
-	@Autowired
-	private LocalDateTimeFactory localDateTimeFactory;
-	
 	@Override
 	@Authorized(PrivilegeConstants.GET_CONDITIONS)
 	public Condition get(@Nonnull String uuid) {
@@ -99,106 +79,6 @@ private ConditionClinicalStatus convertStatus(String status) {
 		return ConditionClinicalStatus.INACTIVE;
 	}
 	
-	private Optional<Criterion> handleAgeByDateProperty(@Nonnull String datePropertyName, @Nonnull QuantityParam age) {
-		BigDecimal value = age.getValue();
-		if (value == null) {
-			throw new IllegalArgumentException("Age value should be provided in " + age);
-		}
-		
-		String unit = age.getUnits();
-		if (unit == null) {
-			throw new IllegalArgumentException("Age unit should be provided in " + age);
-		}
-		
-		LocalDateTime localDateTime = localDateTimeFactory.now();
-		
-		TemporalAmount temporalAmount;
-		TemporalUnit temporalUnit;
-		// TODO check if HAPI FHIR already defines these constant strings. These are mostly from
-		// http://www.hl7.org/fhir/valueset-age-units.html with the exception of "s" which is not
-		// listed but was seen in FHIR examples: http://www.hl7.org/fhir/datatypes-examples.html#Quantity
-		switch (unit) {
-			case "s":
-				temporalUnit = ChronoUnit.SECONDS;
-				temporalAmount = Duration.ofSeconds(value.longValue());
-				break;
-			case "min":
-				temporalUnit = ChronoUnit.MINUTES;
-				temporalAmount = Duration.ofMinutes(value.longValue());
-				break;
-			case "h":
-				temporalUnit = ChronoUnit.HOURS;
-				temporalAmount = Duration.ofHours(value.longValue());
-				break;
-			case "d":
-				temporalUnit = ChronoUnit.DAYS;
-				temporalAmount = Period.ofDays(value.intValue());
-				break;
-			case "wk":
-				temporalUnit = ChronoUnit.WEEKS;
-				temporalAmount = Period.ofWeeks(value.intValue());
-				break;
-			case "mo":
-				temporalUnit = ChronoUnit.MONTHS;
-				temporalAmount = Period.ofMonths(value.intValue());
-				break;
-			case "a":
-				temporalUnit = ChronoUnit.YEARS;
-				temporalAmount = Period.ofYears(value.intValue());
-				break;
-			default:
-				throw new IllegalArgumentException(
-				        "Invalid unit " + unit + " in age " + age + " should be one of 'min', 'h', 'd', 'wk', 'mo', 'a'");
-		}
-		
-		localDateTime = localDateTime.minus(temporalAmount);
-		
-		ParamPrefixEnum prefix = age.getPrefix();
-		if (prefix == null) {
-			prefix = ParamPrefixEnum.EQUAL;
-		}
-		
-		if (prefix == ParamPrefixEnum.EQUAL || prefix == ParamPrefixEnum.NOT_EQUAL) {
-			// Create a range for the targeted unit; the interval length is determined by the unit and
-			// its center is `offsetSeconds` in the past.
-			final long offset;
-			
-			// Duration only supports hours as a chunk of seconds
-			if (temporalUnit == ChronoUnit.HOURS) {
-				offset = temporalAmount.get(ChronoUnit.SECONDS) / (2 * 3600);
-			} else {
-				offset = temporalAmount.get(temporalUnit) / 2;
-			}
-			
-			LocalDateTime lowerBoundDateTime = LocalDateTime.from(localDateTime).minus(Duration.of(offset, temporalUnit));
-			Date lowerBound = Date.from(lowerBoundDateTime.atZone(ZoneId.systemDefault()).toInstant());
-			
-			LocalDateTime upperBoundDateTime = LocalDateTime.from(localDateTime).plus(offset, temporalUnit);
-			Date upperBound = Date.from(upperBoundDateTime.atZone(ZoneId.systemDefault()).toInstant());
-			
-			if (prefix == ParamPrefixEnum.EQUAL) {
-				return Optional.of(and(ge(datePropertyName, lowerBound), le(datePropertyName, upperBound)));
-			} else {
-				return Optional.of(not(and(ge(datePropertyName, lowerBound), le(datePropertyName, upperBound))));
-			}
-		}
-		
-		switch (prefix) {
-			case LESSTHAN_OR_EQUALS:
-			case LESSTHAN:
-			case STARTS_AFTER:
-				return Optional
-				        .of(ge(datePropertyName, Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())));
-			case GREATERTHAN_OR_EQUALS:
-			case GREATERTHAN:
-				return Optional
-				        .of(le(datePropertyName, Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())));
-			// Ignoring ENDS_BEFORE as it is not meaningful for age.
-		}
-		
-		return Optional.empty();
-	}
-	
 	@Override
 	protected void setupSearchParams(Criteria criteria, SearchParameterMap theParams) {
 		theParams.getParameters().forEach(entry -> {
diff --git a/api-2.2/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl_2_2Test.java b/api-2.2/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl_2_2Test.java
index 993b0cf8e..55870daf1 100644
--- a/api-2.2/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl_2_2Test.java
+++ b/api-2.2/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl_2_2Test.java
@@ -20,7 +20,6 @@
 import org.hibernate.SessionFactory;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mock;
 import org.openmrs.CodedOrFreeText;
 import org.openmrs.Condition;
 import org.openmrs.ConditionClinicalStatus;
@@ -28,7 +27,6 @@
 import org.openmrs.api.ConceptService;
 import org.openmrs.api.PatientService;
 import org.openmrs.module.fhir2.TestFhirSpringConfiguration;
-import org.openmrs.module.fhir2.api.util.LocalDateTimeFactory;
 import org.openmrs.test.BaseModuleContextSensitiveTest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -64,16 +62,12 @@ public class FhirConditionDaoImpl_2_2Test extends BaseModuleContextSensitiveTest
 	@Autowired
 	private ConceptService conceptService;
 	
-	@Mock
-	private LocalDateTimeFactory localDateTimeFactory;
-	
 	private FhirConditionDaoImpl_2_2 dao;
 	
 	@Before
 	public void setUp() {
 		dao = new FhirConditionDaoImpl_2_2();
 		dao.setSessionFactory(sessionFactory);
-		dao.setLocalDateTimeFactory(localDateTimeFactory);
 		
 		executeDataSet(CONDITION_INITIAL_DATA_XML);
 	}
diff --git a/api-2.2/src/test/java/org/openmrs/module/fhir2/api/search/ConditionSearchQueryImpl_2_2Test.java b/api-2.2/src/test/java/org/openmrs/module/fhir2/api/search/ConditionSearchQueryImpl_2_2Test.java
index c1958ec2f..3b371c990 100644
--- a/api-2.2/src/test/java/org/openmrs/module/fhir2/api/search/ConditionSearchQueryImpl_2_2Test.java
+++ b/api-2.2/src/test/java/org/openmrs/module/fhir2/api/search/ConditionSearchQueryImpl_2_2Test.java
@@ -393,7 +393,7 @@ public void searchForConditions_shouldReturnEmptyListOfConditionsByMultiplePatie
 	}
 	
 	@Test
-	public void searchForConditions_shouldReturnConditionByPatientNotFoundName() {
+	public void searchForConditions_shouldReturnEmptyListOfConditionByPatientNotFoundName() {
 		ReferenceParam patientReference = new ReferenceParam(Patient.SP_GIVEN, PATIENT_NOT_FOUND_NAME);
 		ReferenceAndListParam patientList = new ReferenceAndListParam();
 		patientList.addValue(new ReferenceOrListParam().add(patientReference));
diff --git a/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java b/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java
index c6d537c23..451c7d266 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java
@@ -277,4 +277,6 @@ public class FhirConstants {
 	public static final String INCLUDE_RESULT_PARAM = "result";
 	
 	public static final String REVERSE_INCLUDE_SEARCH_HANDLER = "_revinclude.search.handler";
+	
+	public static final String CONDITION_OBSERVATION_CONCEPT_UUID = "1284AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
 }
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseDao.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseDao.java
index 3f0ff5106..9d64ccfac 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseDao.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseDao.java
@@ -28,6 +28,13 @@
 import javax.annotation.Nonnull;
 
 import java.math.BigDecimal;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -80,7 +87,9 @@
 import org.hl7.fhir.r4.model.codesystems.AdministrativeGender;
 import org.openmrs.module.fhir2.FhirConstants;
 import org.openmrs.module.fhir2.api.search.param.PropParam;
+import org.openmrs.module.fhir2.api.util.LocalDateTimeFactory;
 import org.openmrs.module.fhir2.model.FhirConceptSource;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * <p>
@@ -159,6 +168,9 @@ public abstract class BaseDao {
 	
 	private static final BigDecimal APPROX_RANGE = new BigDecimal("0.1");
 	
+	@Autowired
+	private LocalDateTimeFactory localDateTimeFactory;
+	
 	/**
 	 * Converts an {@link Iterable} to a {@link Stream}
 	 *
@@ -1038,4 +1050,104 @@ public static final class SortState {
 		private String parameter;
 	}
 	
+	protected Optional<Criterion> handleAgeByDateProperty(@Nonnull String datePropertyName, @Nonnull QuantityParam age) {
+		BigDecimal value = age.getValue();
+		if (value == null) {
+			throw new IllegalArgumentException("Age value should be provided in " + age);
+		}
+		
+		String unit = age.getUnits();
+		if (unit == null) {
+			throw new IllegalArgumentException("Age unit should be provided in " + age);
+		}
+		
+		LocalDateTime localDateTime = localDateTimeFactory.now();
+		
+		TemporalAmount temporalAmount;
+		TemporalUnit temporalUnit;
+		// TODO check if HAPI FHIR already defines these constant strings. These are mostly from
+		// http://www.hl7.org/fhir/valueset-age-units.html with the exception of "s" which is not
+		// listed but was seen in FHIR examples: http://www.hl7.org/fhir/datatypes-examples.html#Quantity
+		switch (unit) {
+			case "s":
+				temporalUnit = ChronoUnit.SECONDS;
+				temporalAmount = Duration.ofSeconds(value.longValue());
+				break;
+			case "min":
+				temporalUnit = ChronoUnit.MINUTES;
+				temporalAmount = Duration.ofMinutes(value.longValue());
+				break;
+			case "h":
+				temporalUnit = ChronoUnit.HOURS;
+				temporalAmount = Duration.ofHours(value.longValue());
+				break;
+			case "d":
+				temporalUnit = ChronoUnit.DAYS;
+				temporalAmount = Period.ofDays(value.intValue());
+				break;
+			case "wk":
+				temporalUnit = ChronoUnit.WEEKS;
+				temporalAmount = Period.ofWeeks(value.intValue());
+				break;
+			case "mo":
+				temporalUnit = ChronoUnit.MONTHS;
+				temporalAmount = Period.ofMonths(value.intValue());
+				break;
+			case "a":
+				temporalUnit = ChronoUnit.YEARS;
+				temporalAmount = Period.ofYears(value.intValue());
+				break;
+			default:
+				throw new IllegalArgumentException(
+				        "Invalid unit " + unit + " in age " + age + " should be one of 'min', 'h', 'd', 'wk', 'mo', 'a'");
+		}
+		
+		localDateTime = localDateTime.minus(temporalAmount);
+		
+		ParamPrefixEnum prefix = age.getPrefix();
+		if (prefix == null) {
+			prefix = ParamPrefixEnum.EQUAL;
+		}
+		
+		if (prefix == ParamPrefixEnum.EQUAL || prefix == ParamPrefixEnum.NOT_EQUAL) {
+			// Create a range for the targeted unit; the interval length is determined by the unit and
+			// its center is `offsetSeconds` in the past.
+			final long offset;
+			
+			// Duration only supports hours as a chunk of seconds
+			if (temporalUnit == ChronoUnit.HOURS) {
+				offset = temporalAmount.get(ChronoUnit.SECONDS) / (2 * 3600);
+			} else {
+				offset = temporalAmount.get(temporalUnit) / 2;
+			}
+			
+			LocalDateTime lowerBoundDateTime = LocalDateTime.from(localDateTime).minus(Duration.of(offset, temporalUnit));
+			Date lowerBound = Date.from(lowerBoundDateTime.atZone(ZoneId.systemDefault()).toInstant());
+			
+			LocalDateTime upperBoundDateTime = LocalDateTime.from(localDateTime).plus(offset, temporalUnit);
+			Date upperBound = Date.from(upperBoundDateTime.atZone(ZoneId.systemDefault()).toInstant());
+			
+			if (prefix == ParamPrefixEnum.EQUAL) {
+				return Optional.of(and(ge(datePropertyName, lowerBound), le(datePropertyName, upperBound)));
+			} else {
+				return Optional.of(not(and(ge(datePropertyName, lowerBound), le(datePropertyName, upperBound))));
+			}
+		}
+		
+		switch (prefix) {
+			case LESSTHAN_OR_EQUALS:
+			case LESSTHAN:
+			case STARTS_AFTER:
+				return Optional
+				        .of(ge(datePropertyName, Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())));
+			case GREATERTHAN_OR_EQUALS:
+			case GREATERTHAN:
+				return Optional
+				        .of(le(datePropertyName, Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())));
+			// Ignoring ENDS_BEFORE as it is not meaningful for age.
+		}
+		
+		return Optional.empty();
+	}
+	
 }
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl.java
new file mode 100644
index 000000000..9d4243021
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl.java
@@ -0,0 +1,135 @@
+/*
+ * 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.fhir2.api.dao.impl;
+
+import static org.hibernate.criterion.Restrictions.eq;
+
+import javax.annotation.Nonnull;
+
+import java.util.List;
+import java.util.Optional;
+
+import ca.uhn.fhir.rest.param.DateRangeParam;
+import ca.uhn.fhir.rest.param.QuantityAndListParam;
+import ca.uhn.fhir.rest.param.ReferenceAndListParam;
+import ca.uhn.fhir.rest.param.TokenAndListParam;
+import lombok.AccessLevel;
+import lombok.Setter;
+import org.hibernate.Criteria;
+import org.hibernate.SessionFactory;
+import org.hibernate.criterion.Criterion;
+import org.openmrs.Obs;
+import org.openmrs.annotation.Authorized;
+import org.openmrs.annotation.OpenmrsProfile;
+import org.openmrs.module.fhir2.FhirConstants;
+import org.openmrs.module.fhir2.api.dao.FhirConditionDao;
+import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
+import org.openmrs.util.PrivilegeConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+@Component
+@Setter(AccessLevel.PUBLIC)
+@OpenmrsProfile(openmrsPlatformVersion = "2.0.5 - 2.1.*")
+public class FhirConditionDaoImpl extends BaseFhirDao<Obs> implements FhirConditionDao<Obs> {
+	
+	@Qualifier("sessionFactory")
+	@Autowired
+	private SessionFactory sessionFactory;
+	
+	@Override
+	@Authorized(PrivilegeConstants.GET_OBS)
+	public Obs get(@Nonnull String uuid) {
+		return super.get(uuid);
+	}
+	
+	@Override
+	@Authorized(PrivilegeConstants.EDIT_OBS)
+	public Obs createOrUpdate(@Nonnull Obs newEntry) {
+		return super.createOrUpdate(newEntry);
+	}
+	
+	@Override
+	@Authorized(PrivilegeConstants.DELETE_OBS)
+	public Obs delete(@Nonnull String uuid) {
+		return super.delete(uuid);
+	}
+	
+	@Override
+	@Authorized(PrivilegeConstants.GET_OBS)
+	public List<String> getSearchResultUuids(@Nonnull SearchParameterMap theParams) {
+		return super.getSearchResultUuids(theParams);
+	}
+	
+	@Override
+	@Authorized(PrivilegeConstants.GET_OBS)
+	public List<Obs> getSearchResults(@Nonnull SearchParameterMap theParams, @Nonnull List<String> matchingResourceUuids,
+	        int firstResult, int lastResult) {
+		return super.getSearchResults(theParams, matchingResourceUuids, firstResult, lastResult);
+	}
+	
+	@Override
+	protected void setupSearchParams(Criteria criteria, SearchParameterMap theParams) {
+		criteria.createAlias("concept", "c");
+		criteria.add(eq("c.uuid", FhirConstants.CONDITION_OBSERVATION_CONCEPT_UUID));
+		theParams.getParameters().forEach(entry -> {
+			switch (entry.getKey()) {
+				case FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER:
+					entry.getValue().forEach(
+					    param -> handlePatientReference(criteria, (ReferenceAndListParam) param.getParam(), "person"));
+					break;
+				case FhirConstants.CODED_SEARCH_HANDLER:
+					entry.getValue().forEach(param -> handleCode(criteria, (TokenAndListParam) param.getParam()));
+					break;
+				case FhirConstants.DATE_RANGE_SEARCH_HANDLER:
+					entry.getValue()
+					        .forEach(param -> handleDateRange(param.getPropertyName(), (DateRangeParam) param.getParam())
+					                .ifPresent(criteria::add));
+					break;
+				case FhirConstants.QUANTITY_SEARCH_HANDLER:
+					entry.getValue().forEach(param -> handleOnsetAge(criteria, (QuantityAndListParam) param.getParam()));
+					break;
+				case FhirConstants.COMMON_SEARCH_HANDLER:
+					handleCommonSearchParameters(entry.getValue()).ifPresent(criteria::add);
+					break;
+			}
+		});
+	}
+	
+	private void handleCode(Criteria criteria, TokenAndListParam code) {
+		if (code != null) {
+			criteria.createAlias("valueCoded", "vc");
+			handleCodeableConcept(criteria, code, "vc", "map", "term").ifPresent(criteria::add);
+		}
+	}
+	
+	private void handleOnsetAge(Criteria criteria, QuantityAndListParam onsetAge) {
+		handleAndListParam(onsetAge, onsetAgeParam -> handleAgeByDateProperty("obsDatetime", onsetAgeParam))
+		        .ifPresent(criteria::add);
+	}
+	
+	@Override
+	protected Optional<Criterion> handleLastUpdated(DateRangeParam param) {
+		return super.handleLastUpdatedImmutable(param);
+	}
+	
+	@Override
+	protected String paramToProp(@Nonnull String param) {
+		switch (param) {
+			case org.hl7.fhir.r4.model.Condition.SP_ONSET_DATE:
+				return "obsDatetime";
+			case org.hl7.fhir.r4.model.Condition.SP_RECORDED_DATE:
+				return "dateCreated";
+		}
+		
+		return super.paramToProp(param);
+	}
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirConditionServiceImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirConditionServiceImpl.java
index 0b88891ac..0a4b0ee04 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirConditionServiceImpl.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirConditionServiceImpl.java
@@ -9,69 +9,68 @@
  */
 package org.openmrs.module.fhir2.api.impl;
 
-import javax.annotation.Nonnull;
+import javax.transaction.Transactional;
 
 import java.util.HashSet;
 
 import ca.uhn.fhir.model.api.Include;
+import ca.uhn.fhir.rest.annotation.Sort;
 import ca.uhn.fhir.rest.api.SortSpec;
 import ca.uhn.fhir.rest.api.server.IBundleProvider;
 import ca.uhn.fhir.rest.param.DateRangeParam;
 import ca.uhn.fhir.rest.param.QuantityAndListParam;
 import ca.uhn.fhir.rest.param.ReferenceAndListParam;
 import ca.uhn.fhir.rest.param.TokenAndListParam;
-import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
 import lombok.AccessLevel;
 import lombok.Getter;
+import lombok.Setter;
 import org.hl7.fhir.r4.model.Condition;
-import org.openmrs.Auditable;
-import org.openmrs.OpenmrsObject;
+import org.openmrs.Obs;
+import org.openmrs.annotation.OpenmrsProfile;
+import org.openmrs.module.fhir2.FhirConstants;
 import org.openmrs.module.fhir2.api.FhirConditionService;
-import org.openmrs.module.fhir2.api.dao.FhirDao;
-import org.openmrs.module.fhir2.api.translators.OpenmrsFhirTranslator;
+import org.openmrs.module.fhir2.api.dao.FhirConditionDao;
+import org.openmrs.module.fhir2.api.search.SearchQuery;
+import org.openmrs.module.fhir2.api.search.SearchQueryInclude;
+import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
+import org.openmrs.module.fhir2.api.translators.ConditionTranslator;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
+@Transactional
 @Getter(AccessLevel.PROTECTED)
-public class FhirConditionServiceImpl<U extends OpenmrsObject & Auditable> extends BaseFhirService<Condition, U> implements FhirConditionService {
+@Setter(AccessLevel.PACKAGE)
+@OpenmrsProfile(openmrsPlatformVersion = "2.0.5 - 2.1.*")
+public class FhirConditionServiceImpl extends BaseFhirService<Condition, Obs> implements FhirConditionService {
 	
-	private static final String MESSAGE = "Condition is not implemented for this version of OpenMRS";
+	@Autowired
+	private FhirConditionDao<org.openmrs.Obs> dao;
 	
-	@Override
-	public Condition get(@Nonnull String uuid) {
-		throw new NotImplementedOperationException(MESSAGE);
-	}
-	
-	@Override
-	protected FhirDao<U> getDao() {
-		throw new NotImplementedOperationException(MESSAGE);
-	}
+	@Autowired
+	private ConditionTranslator<org.openmrs.Obs> translator;
 	
-	@Override
-	public Condition create(@Nonnull Condition newResource) {
-		throw new NotImplementedOperationException(MESSAGE);
-	}
+	@Autowired
+	private SearchQueryInclude<Condition> searchQueryInclude;
 	
-	@Override
-	public Condition update(@Nonnull String uuid, @Nonnull Condition updatedResource) {
-		throw new NotImplementedOperationException(MESSAGE);
-	}
-	
-	@Override
-	public Condition delete(@Nonnull String uuid) {
-		throw new NotImplementedOperationException(MESSAGE);
-	}
-	
-	@Override
-	protected OpenmrsFhirTranslator<U, Condition> getTranslator() {
-		throw new NotImplementedOperationException(MESSAGE);
-	}
+	@Autowired
+	private SearchQuery<org.openmrs.Obs, Condition, FhirConditionDao<org.openmrs.Obs>, ConditionTranslator<org.openmrs.Obs>, SearchQueryInclude<Condition>> searchQuery;
 	
 	@Override
 	public IBundleProvider searchConditions(ReferenceAndListParam patientParam, TokenAndListParam code,
 	        TokenAndListParam clinicalStatus, DateRangeParam onsetDate, QuantityAndListParam onsetAge,
-	        DateRangeParam recordedDate, TokenAndListParam id, DateRangeParam lastUpdated, SortSpec sort,
+	        DateRangeParam recordedDate, TokenAndListParam id, DateRangeParam lastUpdated, @Sort SortSpec sort,
 	        HashSet<Include> includes) {
-		throw new NotImplementedOperationException(MESSAGE);
+		
+		SearchParameterMap theParams = new SearchParameterMap()
+		        .addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER, patientParam)
+		        .addParameter(FhirConstants.CODED_SEARCH_HANDLER, code)
+		        .addParameter(FhirConstants.QUANTITY_SEARCH_HANDLER, onsetAge)
+		        .addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER, "obsDatetime", onsetDate)
+		        .addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER, "dateCreated", recordedDate)
+		        .addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.ID_PROPERTY, id)
+		        .addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.LAST_UPDATED_PROPERTY, lastUpdated)
+		        .addParameter(FhirConstants.INCLUDE_SEARCH_HANDLER, includes).setSortSpec(sort);
+		return searchQuery.getQueryResults(theParams, dao, translator, searchQueryInclude);
 	}
 }
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/search/SearchQueryInclude.java b/api/src/main/java/org/openmrs/module/fhir2/api/search/SearchQueryInclude.java
index 49f16660b..d4afa5dde 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/search/SearchQueryInclude.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/search/SearchQueryInclude.java
@@ -25,6 +25,7 @@
 import org.apache.commons.collections.CollectionUtils;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.r4.model.AllergyIntolerance;
+import org.hl7.fhir.r4.model.Condition;
 import org.hl7.fhir.r4.model.DiagnosticReport;
 import org.hl7.fhir.r4.model.Encounter;
 import org.hl7.fhir.r4.model.Location;
@@ -443,6 +444,10 @@ private Set<IBaseResource> handlePatientInclude(List<U> resourceList, String par
 				resourceList.forEach(
 				    resource -> uniquePatientUUIDs.add(getIdFromReference(((ServiceRequest) resource).getSubject())));
 				break;
+			case FhirConstants.CONDITION:
+				resourceList.forEach(
+				    resource -> uniquePatientUUIDs.add(getIdFromReference(((Condition) resource).getSubject())));
+				break;
 		}
 		
 		uniquePatientUUIDs.removeIf(Objects::isNull);
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ConditionClinicalStatusTranslatorImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ConditionClinicalStatusTranslatorImpl.java
new file mode 100644
index 000000000..485f195e4
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ConditionClinicalStatusTranslatorImpl.java
@@ -0,0 +1,34 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.openmrs.Obs;
+import org.openmrs.annotation.OpenmrsProfile;
+import org.openmrs.module.fhir2.api.translators.ConditionClinicalStatusTranslator;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Component;
+
+@Primary
+@Component
+@OpenmrsProfile(openmrsPlatformVersion = "2.0.5 - 2.1.*")
+public class ConditionClinicalStatusTranslatorImpl implements ConditionClinicalStatusTranslator<Obs> {
+	
+	@Override
+	public CodeableConcept toFhirResource(Obs clinicalStatus) {
+		return null;
+	}
+	
+	@Override
+	public Obs toOpenmrsType(CodeableConcept codeableConcept) {
+		return null;
+	}
+	
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ConditionTranslatorImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ConditionTranslatorImpl.java
new file mode 100644
index 000000000..0e43e64fc
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ConditionTranslatorImpl.java
@@ -0,0 +1,127 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.apache.commons.lang3.Validate.notNull;
+
+import javax.annotation.Nonnull;
+
+import java.util.Date;
+
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import lombok.AccessLevel;
+import lombok.Setter;
+import org.hibernate.proxy.HibernateProxy;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.hl7.fhir.r4.model.DateTimeType;
+import org.openmrs.Concept;
+import org.openmrs.Obs;
+import org.openmrs.Patient;
+import org.openmrs.Person;
+import org.openmrs.User;
+import org.openmrs.annotation.OpenmrsProfile;
+import org.openmrs.api.ConceptService;
+import org.openmrs.api.db.hibernate.HibernateUtil;
+import org.openmrs.module.fhir2.FhirConstants;
+import org.openmrs.module.fhir2.api.translators.ConceptTranslator;
+import org.openmrs.module.fhir2.api.translators.ConditionClinicalStatusTranslator;
+import org.openmrs.module.fhir2.api.translators.ConditionTranslator;
+import org.openmrs.module.fhir2.api.translators.PatientReferenceTranslator;
+import org.openmrs.module.fhir2.api.translators.PractitionerReferenceTranslator;
+import org.openmrs.module.fhir2.api.translators.ProvenanceTranslator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Setter(AccessLevel.PACKAGE)
+@Component
+@OpenmrsProfile(openmrsPlatformVersion = "2.0.5 - 2.1.*")
+public class ConditionTranslatorImpl implements ConditionTranslator<Obs> {
+	
+	@Autowired
+	private PatientReferenceTranslator patientReferenceTranslator;
+	
+	@Autowired
+	private PractitionerReferenceTranslator<User> practitionerReferenceTranslator;
+	
+	@Autowired
+	private ConceptTranslator conceptTranslator;
+	
+	@Autowired
+	private ProvenanceTranslator<Obs> provenanceTranslator;
+	
+	@Autowired
+	private ConceptService conceptService;
+	
+	@Autowired
+	private ConditionClinicalStatusTranslator<Obs> conditionClinicalStatusTranslator;
+	
+	@Override
+	public org.hl7.fhir.r4.model.Condition toFhirResource(@Nonnull Obs obsCondition) {
+		notNull(obsCondition, "The Openmrs Condition object should not be null");
+		
+		org.hl7.fhir.r4.model.Condition fhirCondition = new org.hl7.fhir.r4.model.Condition();
+		fhirCondition.setId(obsCondition.getUuid());
+		
+		Person obsPerson = obsCondition.getPerson();
+		if (obsPerson != null) {
+			if (obsPerson instanceof HibernateProxy) {
+				obsPerson = HibernateUtil.getRealObjectFromProxy(obsPerson);
+			}
+			
+			if (obsPerson instanceof Patient) {
+				fhirCondition.setSubject(patientReferenceTranslator.toFhirResource((Patient) obsPerson));
+			}
+		}
+		if (obsCondition.getValueCoded() != null) {
+			fhirCondition.setCode(conceptTranslator.toFhirResource(obsCondition.getValueCoded()));
+		}
+		fhirCondition.setOnset(new DateTimeType().setValue(obsCondition.getObsDatetime()));
+		fhirCondition.setRecorder(practitionerReferenceTranslator.toFhirResource(obsCondition.getCreator()));
+		fhirCondition.setRecordedDate(obsCondition.getDateCreated());
+		fhirCondition.getMeta().setLastUpdated(obsCondition.getDateChanged());
+		fhirCondition.addContained(provenanceTranslator.getCreateProvenance(obsCondition));
+		fhirCondition.addContained(provenanceTranslator.getUpdateProvenance(obsCondition));
+		
+		return fhirCondition;
+	}
+	
+	@Override
+	public Obs toOpenmrsType(@Nonnull org.hl7.fhir.r4.model.Condition condition) {
+		notNull(condition, "The Condition object should not be null");
+		return this.toOpenmrsType(new Obs(), condition);
+	}
+	
+	@Override
+	public Obs toOpenmrsType(@Nonnull Obs existingObsCondition, @Nonnull org.hl7.fhir.r4.model.Condition condition) {
+		notNull(existingObsCondition, "The existing Openmrs Obs Condition object should not be null");
+		notNull(condition, "The Condition object should not be null");
+		existingObsCondition.setUuid(condition.getIdElement().getIdPart());
+		CodeableConcept codeableConcept = condition.getCode();
+		existingObsCondition.setValueCoded(conceptTranslator.toOpenmrsType(codeableConcept));
+		existingObsCondition.setPerson(patientReferenceTranslator.toOpenmrsType(condition.getSubject()));
+		Concept problemList = conceptService.getConceptByUuid(FhirConstants.CONDITION_OBSERVATION_CONCEPT_UUID);
+		if (problemList != null) {
+			existingObsCondition.setConcept(problemList);
+		} else {
+			throw new InternalErrorException(
+			        "Concept " + FhirConstants.CONDITION_OBSERVATION_CONCEPT_UUID + " ProblemList Not found");
+		}
+		Date onsetTime = condition.getOnsetDateTimeType().getValue();
+		Date recordTime = condition.getRecordedDateElement().getValue();
+		if (onsetTime != null) {
+			existingObsCondition.setObsDatetime(onsetTime);
+		} else if (recordTime != null) {
+			existingObsCondition.setObsDatetime(recordTime);
+		}
+		existingObsCondition.setCreator(practitionerReferenceTranslator.toOpenmrsType(condition.getRecorder()));
+		
+		return existingObsCondition;
+	}
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/MockedCalendarFactoryConfiguration.java b/api/src/test/java/org/openmrs/module/fhir2/MockedCalendarFactoryConfiguration.java
new file mode 100644
index 000000000..36e2d2b37
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/MockedCalendarFactoryConfiguration.java
@@ -0,0 +1,27 @@
+/*
+ * 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.fhir2;
+
+import org.mockito.Mockito;
+import org.openmrs.module.fhir2.api.util.LocalDateTimeFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+@Configuration
+public class MockedCalendarFactoryConfiguration {
+	
+	@Bean
+	@Primary
+	public LocalDateTimeFactory getCalendarFactory() {
+		return Mockito.mock(LocalDateTimeFactory.class);
+	}
+	
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImplTest.java
new file mode 100644
index 000000000..3949b6099
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImplTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.fhir2.api.dao.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import ca.uhn.fhir.rest.param.TokenAndListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import org.junit.Before;
+import org.junit.Test;
+import org.openmrs.Obs;
+import org.openmrs.api.ConceptService;
+import org.openmrs.api.PatientService;
+import org.openmrs.module.fhir2.FhirConstants;
+import org.openmrs.module.fhir2.TestFhirSpringConfiguration;
+import org.openmrs.module.fhir2.api.dao.FhirConditionDao;
+import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
+import org.openmrs.test.BaseModuleContextSensitiveTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+@ContextConfiguration(classes = TestFhirSpringConfiguration.class, inheritLocations = false)
+public class FhirConditionDaoImplTest extends BaseModuleContextSensitiveTest {
+	
+	private static final String VOIDED_OBS_CONDITION_UUID = "94dhs003-a55d-43c4-ac7a-bd6d1ba63388";
+	
+	private static final String EXISTING_OBS_CONDITION_UUID = "86sgf-1f7d-4394-a316-0a458edf28c4";
+	
+	private static final String WRONG_OBS_CONDITION_UUID = "430bbb70-6a9c-4e1e-badb-9d1034b1b5e9";
+	
+	private static final String NEW_OBS_CONDITION_UUID = "NEWbbb70-6a9c-4e1e-badb-9d1034b1b5e9";
+	
+	private static final String OBS_CONDITION_INITIAL_DATA_XML = "org/openmrs/module/fhir2/api/dao/impl/FhirObsConditionDaoImplTest_initial_data.xml";
+	
+	private static final String OBS_CONDITION_CONCEPT_ID = "116128";
+	
+	private static final String OBS_CONDITION_CODED_CONCEPT_ID = "1284";
+	
+	private static final String WORNG_OBS_CONDITION_CONCEPT_ID = "116145";
+	
+	private static final Integer PATIENT_ID = 6;
+	
+	@Autowired
+	private FhirConditionDao<Obs> dao;
+	
+	@Autowired
+	private ConceptService conceptService;
+	
+	@Autowired
+	private PatientService patientService;
+	
+	@Before
+	public void setUp() throws Exception {
+		executeDataSet(OBS_CONDITION_INITIAL_DATA_XML);
+	}
+	
+	@Test
+	public void get_shouldGetObsConditionByUuid() {
+		Obs result = dao.get(EXISTING_OBS_CONDITION_UUID);
+		assertThat(result, notNullValue());
+		assertThat(result.getUuid(), equalTo(EXISTING_OBS_CONDITION_UUID));
+	}
+	
+	@Test
+	public void get_shouldReturnNullIfObsNotFoundByUuid() {
+		Obs result = dao.get(WRONG_OBS_CONDITION_UUID);
+		assertThat(result, nullValue());
+	}
+	
+	@Test
+	public void search_shouldReturnConditonResourceUuidsWithObsCoded_1284() {
+		TokenAndListParam code = new TokenAndListParam();
+		TokenParam codingToken = new TokenParam();
+		codingToken.setValue(OBS_CONDITION_CONCEPT_ID);
+		code.addAnd(codingToken);
+		
+		SearchParameterMap theParams = new SearchParameterMap();
+		theParams.addParameter(FhirConstants.CODED_SEARCH_HANDLER, code);
+		
+		List<String> matchingResourceUuids = dao.getSearchResultUuids(theParams);
+		assertEquals(2, matchingResourceUuids.size());
+	}
+	
+	@Test
+	public void search_shouldReturnNoConditonResourceUuidsWhenNoWrongConceptIdIsUsed() {
+		TokenAndListParam code = new TokenAndListParam();
+		TokenParam codingToken = new TokenParam();
+		codingToken.setValue(WORNG_OBS_CONDITION_CONCEPT_ID);
+		code.addAnd(codingToken);
+		
+		SearchParameterMap theParams = new SearchParameterMap();
+		theParams.addParameter(FhirConstants.CODED_SEARCH_HANDLER, code);
+		
+		List<String> matchingResourceUuids = dao.getSearchResultUuids(theParams);
+		assertEquals(matchingResourceUuids.size(), 0);
+	}
+	
+	@Test
+	public void search_shouldReturnNoVoidedConditonResourceUuids() {
+		TokenAndListParam code = new TokenAndListParam();
+		TokenParam codingToken = new TokenParam();
+		codingToken.setValue(OBS_CONDITION_CONCEPT_ID);
+		code.addAnd(codingToken);
+		
+		SearchParameterMap theParams = new SearchParameterMap();
+		theParams.addParameter(FhirConstants.CODED_SEARCH_HANDLER, code);
+		
+		List<String> matchingResourceUuids = dao.getSearchResultUuids(theParams);
+		assertFalse(matchingResourceUuids.contains(VOIDED_OBS_CONDITION_UUID));
+	}
+	
+	@Test
+	public void search_shouldReturnSearchQuery() {
+		TokenAndListParam code = new TokenAndListParam();
+		TokenParam codingToken = new TokenParam();
+		codingToken.setValue(OBS_CONDITION_CONCEPT_ID);
+		code.addAnd(codingToken);
+		
+		SearchParameterMap theParams = new SearchParameterMap();
+		theParams.addParameter(FhirConstants.CODED_SEARCH_HANDLER, code);
+		
+		List<String> matchingResourceUuids = dao.getSearchResultUuids(theParams);
+		Collection<Obs> obs = dao.getSearchResults(theParams, matchingResourceUuids);
+		assertThat(obs, notNullValue());
+		assertEquals(obs.size(), 2);
+	}
+	
+	@Test
+	public void shouldSaveNewObsCondition() {
+		Obs obsCondition = new Obs();
+		obsCondition.setUuid(NEW_OBS_CONDITION_UUID);
+		obsCondition.setObsDatetime(new Date());
+		obsCondition.setConcept(conceptService.getConcept(OBS_CONDITION_CODED_CONCEPT_ID));
+		
+		org.openmrs.Patient patient = patientService.getPatient(PATIENT_ID);
+		obsCondition.setPerson(patient);
+		obsCondition.setValueCoded(conceptService.getConcept(OBS_CONDITION_CONCEPT_ID));
+		
+		dao.createOrUpdate(obsCondition);
+		
+		Obs result = dao.get(NEW_OBS_CONDITION_UUID);
+		assertThat(result, notNullValue());
+		assertThat(result.getUuid(), equalTo(NEW_OBS_CONDITION_UUID));
+	}
+	
+	@Test
+	public void shouldDeleteObsCondition() {
+		Obs result = dao.delete(EXISTING_OBS_CONDITION_UUID);
+		assertThat(result, notNullValue());
+		assertThat(result.getUuid(), equalTo(EXISTING_OBS_CONDITION_UUID));
+		assertThat(result.getVoided(), equalTo(true));
+		
+	}
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirConditionServiceImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirConditionServiceImplTest.java
index f4a62d20c..4948571dc 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirConditionServiceImplTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirConditionServiceImplTest.java
@@ -9,62 +9,243 @@
  */
 package org.openmrs.module.fhir2.api.impl;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
 
-import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import ca.uhn.fhir.model.api.Include;
+import ca.uhn.fhir.rest.api.SortSpec;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.DateRangeParam;
+import ca.uhn.fhir.rest.param.QuantityAndListParam;
+import ca.uhn.fhir.rest.param.QuantityOrListParam;
+import ca.uhn.fhir.rest.param.QuantityParam;
+import ca.uhn.fhir.rest.param.ReferenceAndListParam;
+import ca.uhn.fhir.rest.param.ReferenceOrListParam;
+import ca.uhn.fhir.rest.param.ReferenceParam;
+import ca.uhn.fhir.rest.param.TokenAndListParam;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.r4.model.Condition;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
-import org.openmrs.OpenmrsObject;
+import org.openmrs.Obs;
+import org.openmrs.module.fhir2.FhirConstants;
+import org.openmrs.module.fhir2.api.FhirGlobalPropertyService;
+import org.openmrs.module.fhir2.api.dao.FhirConditionDao;
+import org.openmrs.module.fhir2.api.search.SearchQuery;
+import org.openmrs.module.fhir2.api.search.SearchQueryBundleProvider;
+import org.openmrs.module.fhir2.api.search.SearchQueryInclude;
+import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
+import org.openmrs.module.fhir2.api.translators.ConditionTranslator;
 
 @RunWith(MockitoJUnitRunner.class)
 public class FhirConditionServiceImplTest {
 	
-	private static final String BAD_CONDITION_UUID = "90378769-f1a4-46af-b08b-d9fe8a09034j";
+	private static final String OBS_UUID = "12345-abcde-12345";
+	
+	private static final String WRONG_OBS_CONDITION_UUID = "90378769-f1a4-46af-034j";
+	
+	private static final String LAST_UPDATED_DATE = "2020-09-03";
+	
+	private static final int START_INDEX = 0;
+	
+	private static final int END_INDEX = 10;
+	
+	@Mock
+	private FhirConditionDao<Obs> dao;
+	
+	@Mock
+	private FhirGlobalPropertyService globalPropertyService;
 	
-	private FhirConditionServiceImpl<?> conditionService;
+	@Mock
+	private SearchQueryInclude<Condition> searchQueryInclude;
 	
-	private Condition condition;
+	@Mock
+	private SearchQuery<org.openmrs.Obs, Condition, FhirConditionDao<org.openmrs.Obs>, ConditionTranslator<org.openmrs.Obs>, SearchQueryInclude<Condition>> searchQuery;
+	
+	@Mock
+	private ConditionTranslator<Obs> translator;;
+	
+	private FhirConditionServiceImpl fhirConditionService;
+	
+	private Obs obsCondition;
+	
+	private org.hl7.fhir.r4.model.Condition condition;
 	
 	@Before
-	@SuppressWarnings("rawtypes")
 	public void setup() {
-		conditionService = new FhirConditionServiceImpl() {
+		fhirConditionService = new FhirConditionServiceImpl() {
 			
 			@Override
-			protected void validateObject(OpenmrsObject object) {
+			protected void validateObject(Obs object) {
 			}
 		};
 		
-		condition = new Condition();
-		condition.setId(BAD_CONDITION_UUID);
+		fhirConditionService.setDao(dao);
+		fhirConditionService.setTranslator(translator);
+		fhirConditionService.setSearchQueryInclude(searchQueryInclude);
+		fhirConditionService.setSearchQuery(searchQuery);
+		
+		obsCondition = new Obs();
+		obsCondition.setUuid(OBS_UUID);
+		
+		condition = new org.hl7.fhir.r4.model.Condition();
+		condition.setId(OBS_UUID);
+	}
+	
+	private List<IBaseResource> get(IBundleProvider results) {
+		return results.getResources(START_INDEX, END_INDEX);
+	}
+	
+	@Test
+	public void getObsConditionByUuid_shouldReturnConditionByUuid() {
+		when(dao.get(OBS_UUID)).thenReturn(obsCondition);
+		when(translator.toFhirResource(obsCondition)).thenReturn(condition);
+		
+		Condition result = fhirConditionService.get(OBS_UUID);
+		assertThat(result, notNullValue());
+		assertThat(result.getId(), equalTo(OBS_UUID));
+	}
+	
+	@Test
+	public void shouldThrowExceptionWhenGetMissingUuid() {
+		assertThrows(ResourceNotFoundException.class, () -> fhirConditionService.get(WRONG_OBS_CONDITION_UUID));
+	}
+	
+	@Test
+	public void create_shouldCreateNewCondition() {
+		when(translator.toFhirResource(obsCondition)).thenReturn(condition);
+		when(dao.createOrUpdate(obsCondition)).thenReturn(obsCondition);
+		when(translator.toOpenmrsType(condition)).thenReturn(obsCondition);
+		
+		org.hl7.fhir.r4.model.Condition result = fhirConditionService.create(condition);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getId(), notNullValue());
+		assertThat(result.getId(), equalTo(OBS_UUID));
 	}
 	
 	@Test
-	public void get_shouldThrowNotImplementedOperationException() {
-		assertThrows(NotImplementedOperationException.class, () -> conditionService.get(BAD_CONDITION_UUID));
+	public void update_shouldUpdateExistingObsCondition() {
+		when(dao.get(OBS_UUID)).thenReturn(obsCondition);
+		when(translator.toFhirResource(obsCondition)).thenReturn(condition);
+		when(dao.createOrUpdate(obsCondition)).thenReturn(obsCondition);
+		when(translator.toOpenmrsType(any(Obs.class), any(org.hl7.fhir.r4.model.Condition.class))).thenReturn(obsCondition);
+		
+		org.hl7.fhir.r4.model.Condition result = fhirConditionService.update(OBS_UUID, condition);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getId(), notNullValue());
+		assertThat(result.getId(), equalTo(OBS_UUID));
 	}
 	
 	@Test
-	public void create_shouldThrowNotImplementedOperationException() {
-		assertThrows(NotImplementedOperationException.class, () -> conditionService.create(condition));
+	public void update_shouldThrowExceptionWhenIdIsNull() {
+		org.hl7.fhir.r4.model.Condition condition = new org.hl7.fhir.r4.model.Condition();
+		
+		assertThrows(InvalidRequestException.class, () -> fhirConditionService.update(null, condition));
 	}
 	
 	@Test
-	public void update_shouldThrowNotImplementedOperationException() {
-		assertThrows(NotImplementedOperationException.class, () -> conditionService.update(BAD_CONDITION_UUID, condition));
+	public void update_shouldThrowExceptionWhenConditionIsNull() {
+		assertThrows(InvalidRequestException.class, () -> fhirConditionService.update(OBS_UUID, null));
+	}
+	
+	@Test
+	public void update_shouldThrowExceptionWhenConditionIdDoesNotMatchCurrentId() {
+		org.hl7.fhir.r4.model.Condition condition = new org.hl7.fhir.r4.model.Condition();
+		condition.setId(OBS_UUID);
+		
+		assertThrows(InvalidRequestException.class, () -> fhirConditionService.update(WRONG_OBS_CONDITION_UUID, condition));
 	}
 	
 	@Test
-	public void delete_shouldThrowNotImplementedOperationException() {
-		assertThrows(NotImplementedOperationException.class, () -> conditionService.delete(BAD_CONDITION_UUID));
+	public void delete_shouldDeleteExistingCondition() {
+		when(dao.delete(OBS_UUID)).thenReturn(obsCondition);
+		when(translator.toFhirResource(obsCondition)).thenReturn(condition);
+		
+		org.hl7.fhir.r4.model.Condition result = fhirConditionService.delete(OBS_UUID);
+		
+		assertThat(result, notNullValue());
 	}
 	
 	@Test
-	public void searchConditions_shouldThrowNotImplementedOperationException() {
-		assertThrows(NotImplementedOperationException.class,
-		    () -> conditionService.searchConditions(null, null, null, null, null, null, null, null, null, null));
+	public void delete_shouldThrowExceptionWhenIdIsNull() {
+		assertThrows(InvalidRequestException.class, () -> fhirConditionService.delete(null));
 	}
+	
+	@Test
+	public void searchConditions_shouldReturnTranslatedConditionReturnedByDao() {
+		ReferenceAndListParam patientReference = new ReferenceAndListParam();
+		patientReference.addValue(
+		    new ReferenceOrListParam().add(new ReferenceParam(org.hl7.fhir.r4.model.Patient.SP_GIVEN, "patient name")));
+		
+		TokenAndListParam codeList = new TokenAndListParam();
+		codeList.addValue(new TokenOrListParam().add(new TokenParam("test code")));
+		
+		TokenAndListParam clinicalList = new TokenAndListParam();
+		clinicalList.addValue(new TokenOrListParam().add(new TokenParam("test clinical")));
+		
+		DateRangeParam onsetDate = new DateRangeParam().setLowerBound("lower date").setUpperBound("upper date");
+		
+		QuantityAndListParam onsetAge = new QuantityAndListParam();
+		onsetAge.addValue(new QuantityOrListParam().add(new QuantityParam(12)));
+		
+		DateRangeParam recordDate = new DateRangeParam().setLowerBound("lower record date")
+		        .setUpperBound("upper record date");
+		
+		TokenAndListParam uuid = new TokenAndListParam().addAnd(new TokenParam(OBS_UUID));
+		
+		DateRangeParam lastUpdated = new DateRangeParam().setLowerBound(LAST_UPDATED_DATE).setUpperBound(LAST_UPDATED_DATE);
+		
+		SortSpec sort = new SortSpec("sort param");
+		
+		HashSet<Include> includes = new HashSet<>();
+		
+		SearchParameterMap theParams = new SearchParameterMap()
+		        .addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER, patientReference)
+		        .addParameter(FhirConstants.CODED_SEARCH_HANDLER, codeList)
+		        .addParameter(FhirConstants.QUANTITY_SEARCH_HANDLER, onsetAge)
+		        .addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER, "onsetDate", onsetDate)
+		        .addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER, "dateCreated", recordDate)
+		        .addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.ID_PROPERTY, uuid)
+		        .addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.LAST_UPDATED_PROPERTY, lastUpdated)
+		        .setSortSpec(sort);
+		
+		when(dao.getSearchResultUuids(any())).thenReturn(Collections.singletonList(OBS_UUID));
+		when(dao.getSearchResults(any(), any(), anyInt(), anyInt())).thenReturn(Collections.singletonList(obsCondition));
+		when(searchQuery.getQueryResults(any(), any(), any(), any())).thenReturn(
+		    new SearchQueryBundleProvider<>(theParams, dao, translator, globalPropertyService, searchQueryInclude));
+		when(searchQueryInclude.getIncludedResources(any(), any())).thenReturn(Collections.emptySet());
+		when(translator.toFhirResource(obsCondition)).thenReturn(condition);
+		
+		IBundleProvider result = fhirConditionService.searchConditions(patientReference, codeList, clinicalList, onsetDate,
+		    onsetAge, recordDate, uuid, lastUpdated, sort, includes);
+		
+		List<IBaseResource> resultList = get(result);
+		
+		assertThat(result, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList, hasSize(greaterThanOrEqualTo(1)));
+	}
+	
 }
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/search/ConditionSearchQueryTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/search/ConditionSearchQueryTest.java
new file mode 100644
index 000000000..262bcc553
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/search/ConditionSearchQueryTest.java
@@ -0,0 +1,871 @@
+/*
+ * 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.fhir2.api.search;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasProperty;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import ca.uhn.fhir.model.api.Include;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.DateParam;
+import ca.uhn.fhir.rest.param.DateRangeParam;
+import ca.uhn.fhir.rest.param.ParamPrefixEnum;
+import ca.uhn.fhir.rest.param.QuantityAndListParam;
+import ca.uhn.fhir.rest.param.QuantityOrListParam;
+import ca.uhn.fhir.rest.param.QuantityParam;
+import ca.uhn.fhir.rest.param.ReferenceAndListParam;
+import ca.uhn.fhir.rest.param.ReferenceOrListParam;
+import ca.uhn.fhir.rest.param.ReferenceParam;
+import ca.uhn.fhir.rest.param.TokenAndListParam;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import org.hamcrest.Matchers;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.Condition;
+import org.hl7.fhir.r4.model.Patient;
+import org.junit.Before;
+import org.junit.Test;
+import org.openmrs.api.ConceptService;
+import org.openmrs.api.PatientService;
+import org.openmrs.module.fhir2.FhirConstants;
+import org.openmrs.module.fhir2.TestFhirSpringConfiguration;
+import org.openmrs.module.fhir2.api.dao.FhirConditionDao;
+import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
+import org.openmrs.module.fhir2.api.translators.ConditionTranslator;
+import org.openmrs.module.fhir2.api.util.LocalDateTimeFactory;
+import org.openmrs.test.BaseModuleContextSensitiveTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+@ContextConfiguration(classes = TestFhirSpringConfiguration.class, inheritLocations = false)
+public class ConditionSearchQueryTest extends BaseModuleContextSensitiveTest {
+	
+	private static final String OBS_CONDITION_INITIAL_DATA_XML = "org/openmrs/module/fhir2/api/dao/impl/FhirObsConditionDaoImplTest_initial_data.xml";
+	
+	private static final int START_INDEX = 0;
+	
+	private static final int END_INDEX = 10;
+	
+	private static final String PATIENT_IDENTIFIER = "6TS-4";
+	
+	private static final String PATIENT_WRONG_IDENTIFIER = "Wrong Identifier";
+	
+	private static final String PATIENT_UUID = "5946f880-b197-400b-9caa-a3c661d23041";
+	
+	private static final String PATIENT_WRONG_UUID = "c2299800-cca9-11e0-9572-abcdef0c9a66";
+	
+	private static final String PATIENT_GIVEN_NAME = "Collet";
+	
+	private static final String PATIENT_WRONG_GIVEN_NAME = "Wrong given name";
+	
+	private static final String PATIENT_PARTIAL_NAME = "Test";
+	
+	private static final String PATIENT_FAMILY_NAME = "Chebaskwony";
+	
+	private static final String PATIENT_WRONG_FAMILY_NAME = "Wrong family name";
+	
+	private static final String PATIENT_NOT_FOUND_NAME = "Igor";
+	
+	private static final String ONSET_DATE_TIME = "2008-07-01T00:00:00";
+	
+	private static final String ONSET_START_DATE = "2008-05-01T00:00:00";
+	
+	private static final String ONSET_END_DATE = "2008-08-01T00:00:00";
+	
+	private static final String ONSET_DATE = "2008-07-01";
+	
+	private static final String RECORDED_DATE_TIME = "2008-08-18T14:09:35.0";
+	
+	private static final String DATE_CREATED = "2008-08-18T14:09:35.0";
+	
+	private static final String RECORDED_START_DATE = "2008-05-18T14:09:35.0";
+	
+	private static final String RECORDED_END_DATE = "2008-10-18T14:09:35.0";
+	
+	private static final String DATE_VOIDED = "2008-12-18T14:09:35.0";
+	
+	private static final String RECORDED_DATE = "2008-08-18";
+	
+	private static final String EXISTING_OBS_CONDITION_UUID = "86sgf-1f7d-4394-a316-0a458edf28c4";
+	
+	private static final String CODE_SYSTEM_1 = "http://made_up_concepts.info/sct";
+	
+	private static final String CODE_VALUE_1 = "C00";
+	
+	private static final String CONCEPT_ID_1 = "116128AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String CODE_SYSTEM_2 = "http://made_up_concepts.info/sct";
+	
+	private static final String CODE_VALUE_2 = "WGT234";
+	
+	private static final String CONCEPT_ID_2 = "c607c80f-1ea9-4da3-bb88-6276ce8868dd";
+	
+	@Autowired
+	private FhirConditionDao<org.openmrs.Obs> dao;
+	
+	@Autowired
+	private ConditionTranslator<org.openmrs.Obs> translator;
+	
+	@Autowired
+	private SearchQueryInclude<Condition> searchQueryInclude;
+	
+	@Autowired
+	private SearchQuery<org.openmrs.Obs, Condition, FhirConditionDao<org.openmrs.Obs>, ConditionTranslator<org.openmrs.Obs>, SearchQueryInclude<Condition>> searchQuery;
+	
+	@Autowired
+	PatientService patientService;
+	
+	@Autowired
+	ConceptService conceptService;
+	
+	@Autowired
+	private LocalDateTimeFactory localDateTimeFactory;
+	
+	@Before
+	public void setup() throws Exception {
+		executeDataSet(OBS_CONDITION_INITIAL_DATA_XML);
+	}
+	
+	private IBundleProvider search(SearchParameterMap theParams) {
+		return searchQuery.getQueryResults(theParams, dao, translator, searchQueryInclude);
+	}
+	
+	private List<IBaseResource> get(IBundleProvider results) {
+		return results.getResources(START_INDEX, END_INDEX);
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnConditionByPatientIdentifier() {
+		ReferenceParam patientReference = new ReferenceParam(Patient.SP_IDENTIFIER, PATIENT_IDENTIFIER);
+		ReferenceAndListParam patientList = new ReferenceAndListParam();
+		patientList.addAnd(new ReferenceOrListParam().addOr(patientReference));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    patientList);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertEquals(resultList.size(), 2);
+		assertThat(((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getSubject().getReference(),
+		    endsWith(PATIENT_UUID));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldSearchForConditionsByMultiplePatientIdentifierOr() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_IDENTIFIER);
+		patient.setChain(Patient.SP_IDENTIFIER);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_WRONG_IDENTIFIER);
+		badPatient.setChain(Patient.SP_IDENTIFIER);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient).add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertEquals(resultList.size(), 2);
+		assertThat(((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getSubject().getReference(),
+		    endsWith(PATIENT_UUID));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnEmptyListOfConditionsByMultiplePatientIdentifierAnd() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_IDENTIFIER);
+		patient.setChain(Patient.SP_IDENTIFIER);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_WRONG_IDENTIFIER);
+		badPatient.setChain(Patient.SP_IDENTIFIER);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient)).addAnd(new ReferenceOrListParam().add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, empty());
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnConditionByPatientUuid() {
+		ReferenceParam patientReference = new ReferenceParam(null, PATIENT_UUID);
+		ReferenceAndListParam patientList = new ReferenceAndListParam();
+		patientList.addAnd(new ReferenceOrListParam().addOr(patientReference));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    patientList);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertEquals(resultList.size(), 2);
+		assertThat(
+		    ((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getSubject().getReferenceElement().getIdPart(),
+		    equalTo(PATIENT_UUID));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldSearchForConditionsByMultiplePatientUuidOr() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_UUID);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_WRONG_UUID);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient).add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertEquals(resultList.size(), 2);
+		assertThat(
+		    ((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getSubject().getReferenceElement().getIdPart(),
+		    equalTo(PATIENT_UUID));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnEmptyListOfConditionsByMultiplePatientUuidAnd() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_UUID);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_WRONG_UUID);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient)).addAnd(new ReferenceOrListParam().add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, empty());
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnConditionByPatientGivenName() {
+		ReferenceParam patientReference = new ReferenceParam(Patient.SP_GIVEN, PATIENT_GIVEN_NAME);
+		ReferenceAndListParam patientList = new ReferenceAndListParam();
+		patientList.addValue(new ReferenceOrListParam().add(patientReference));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    patientList);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertEquals(resultList.size(), 2);
+		assertThat(
+		    ((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getSubject().getReferenceElement().getIdPart(),
+		    equalTo(PATIENT_UUID));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnUniqueConditionsByPatientGivenName() {
+		ReferenceParam patientReference = new ReferenceParam(Patient.SP_GIVEN, PATIENT_GIVEN_NAME);
+		ReferenceAndListParam patientList = new ReferenceAndListParam();
+		patientList.addValue(new ReferenceOrListParam().add(patientReference));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    patientList);
+		
+		IBundleProvider results = search(theParams);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.size(), equalTo(2));
+		Set<String> resultSet = new HashSet<>(dao.getSearchResultUuids(theParams));
+		assertThat(resultSet.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldSearchForConditionsByMultiplePatientGivenNameOr() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_GIVEN_NAME);
+		patient.setChain(Patient.SP_GIVEN);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_WRONG_GIVEN_NAME);
+		badPatient.setChain(Patient.SP_GIVEN);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient).add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertEquals(resultList.size(), 2);
+		
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnEmptyListOfConditionsByMultiplePatientGivenNameAnd() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_GIVEN_NAME);
+		patient.setChain(Patient.SP_GIVEN);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_WRONG_GIVEN_NAME);
+		badPatient.setChain(Patient.SP_GIVEN);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient)).addAnd(new ReferenceOrListParam().add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, empty());
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnEmptyListOfConditionByPatientNotFoundName() {
+		ReferenceParam patientReference = new ReferenceParam(Patient.SP_GIVEN, PATIENT_NOT_FOUND_NAME);
+		ReferenceAndListParam patientList = new ReferenceAndListParam();
+		patientList.addValue(new ReferenceOrListParam().add(patientReference));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    patientList);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, empty());
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnConditionByPatientFamilyName() {
+		ReferenceParam patientReference = new ReferenceParam(Patient.SP_FAMILY, PATIENT_FAMILY_NAME);
+		ReferenceAndListParam patientList = new ReferenceAndListParam();
+		patientList.addValue(new ReferenceOrListParam().add(patientReference));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    patientList);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList.size(), equalTo(2));
+		assertThat(
+		    ((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getSubject().getReferenceElement().getIdPart(),
+		    equalTo(PATIENT_UUID));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnUniqueConditionsByPatientFamilyName() {
+		ReferenceParam patientReference = new ReferenceParam(Patient.SP_FAMILY, PATIENT_FAMILY_NAME);
+		ReferenceAndListParam patientList = new ReferenceAndListParam();
+		patientList.addValue(new ReferenceOrListParam().add(patientReference));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    patientList);
+		
+		IBundleProvider results = search(theParams);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.size(), equalTo(2));
+		Set<String> resultSet = new HashSet<>(dao.getSearchResultUuids(theParams));
+		assertThat(resultSet.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForConditions_shouldSearchForConditionsByMultiplePatientFamilyNameOr() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_FAMILY_NAME);
+		patient.setChain(Patient.SP_FAMILY);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_WRONG_FAMILY_NAME);
+		badPatient.setChain(Patient.SP_FAMILY);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient).add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnEmptyListOfConditionsByMultiplePatientFamilyNameAnd() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_FAMILY_NAME);
+		patient.setChain(Patient.SP_FAMILY);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_WRONG_FAMILY_NAME);
+		badPatient.setChain(Patient.SP_FAMILY);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient)).addAnd(new ReferenceOrListParam().add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, empty());
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnConditionByPatientName() {
+		ReferenceParam patientReference = new ReferenceParam(Patient.SP_NAME, PATIENT_PARTIAL_NAME);
+		ReferenceAndListParam patientList = new ReferenceAndListParam();
+		patientList.addValue(new ReferenceOrListParam().add(patientReference));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    patientList);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(
+		    ((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getSubject().getReferenceElement().getIdPart(),
+		    equalTo(PATIENT_UUID));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnUniqueConditionsByPatientName() {
+		ReferenceParam patientReference = new ReferenceParam(Patient.SP_NAME, PATIENT_PARTIAL_NAME);
+		ReferenceAndListParam patientList = new ReferenceAndListParam();
+		patientList.addValue(new ReferenceOrListParam().add(patientReference));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    patientList);
+		
+		IBundleProvider results = search(theParams);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.size(), equalTo(2));
+		List<String> resultSet = dao.getSearchResultUuids(theParams);
+		assertThat(resultSet.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForConditions_shouldSearchForConditionsByMultiplePatientNameOr() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_PARTIAL_NAME);
+		patient.setChain(Patient.SP_NAME);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_NOT_FOUND_NAME);
+		badPatient.setChain(Patient.SP_NAME);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient).add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnEmptyListOfConditionsByMultiplePatientNameAnd() {
+		ReferenceAndListParam referenceParam = new ReferenceAndListParam();
+		ReferenceParam patient = new ReferenceParam();
+		
+		patient.setValue(PATIENT_PARTIAL_NAME);
+		patient.setChain(Patient.SP_NAME);
+		
+		ReferenceParam badPatient = new ReferenceParam();
+		
+		badPatient.setValue(PATIENT_NOT_FOUND_NAME);
+		badPatient.setChain(Patient.SP_NAME);
+		
+		referenceParam.addValue(new ReferenceOrListParam().add(patient)).addAnd(new ReferenceOrListParam().add(badPatient));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER,
+		    referenceParam);
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, empty());
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnConditionByOnsetDate() {
+		DateRangeParam onsetDate = new DateRangeParam(new DateParam("eq" + ONSET_DATE_TIME));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER,
+		    "obsDatetime", onsetDate);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(
+		    ((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getOnsetDateTimeType().getValue().toString(),
+		    containsString(ONSET_DATE));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnConditionByOnsetDateRange() {
+		DateRangeParam onsetDate = new DateRangeParam(new DateParam(ONSET_START_DATE), new DateParam(ONSET_END_DATE));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER,
+		    "obsDatetime", onsetDate);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(
+		    ((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getOnsetDateTimeType().getValue().toString(),
+		    containsString(ONSET_DATE));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnConditionByUnboundedOnsetDate() {
+		DateRangeParam onsetDate = new DateRangeParam(new DateParam("gt" + ONSET_START_DATE));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER,
+		    "obsDatetime", onsetDate);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnConditionByRecordedDate() {
+		DateRangeParam recordedDate = new DateRangeParam(new DateParam("eq" + RECORDED_DATE_TIME));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER,
+		    "dateCreated", recordedDate);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getRecordedDate().toString(),
+		    containsString(RECORDED_DATE));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnConditionByRecordedDateRange() {
+		DateRangeParam onsetDate = new DateRangeParam(new DateParam(RECORDED_START_DATE), new DateParam(RECORDED_END_DATE));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER,
+		    "dateCreated", onsetDate);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getRecordedDate().toString(),
+		    containsString(RECORDED_DATE));
+		assertThat(resultList.size(), equalTo(2));
+		
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnConditionByCode() {
+		TokenAndListParam listParam = new TokenAndListParam();
+		listParam.addValue(new TokenOrListParam().add(new TokenParam(CODE_SYSTEM_1, CODE_VALUE_1)));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.CODED_SEARCH_HANDLER, listParam);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getCode().getCodingFirstRep().getCode(),
+		    equalTo(CONCEPT_ID_1));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnMultipleConditionsByCodeList() {
+		TokenAndListParam listParam = new TokenAndListParam();
+		
+		listParam.addValue(new TokenOrListParam().add(new TokenParam(CODE_SYSTEM_1, CODE_VALUE_1))
+		        .add(new TokenParam(CODE_SYSTEM_2, CODE_VALUE_2)));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.CODED_SEARCH_HANDLER, listParam);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnConditionByCodeAndNoSystem() {
+		TokenAndListParam listParam = new TokenAndListParam();
+		listParam.addValue(new TokenOrListParam().add(new TokenParam(CONCEPT_ID_1)));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.CODED_SEARCH_HANDLER, listParam);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getCode().getCodingFirstRep().getCode(),
+		    equalTo(CONCEPT_ID_1));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnMultipleConditionsByCodeListAndNoSystem() {
+		TokenAndListParam listParam = new TokenAndListParam();
+		listParam.addValue(new TokenOrListParam().add(new TokenParam(CONCEPT_ID_1)).add(new TokenParam(CONCEPT_ID_2)));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.CODED_SEARCH_HANDLER, listParam);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList.size(), equalTo(2));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldSearchForConditionsByUuid() {
+		TokenAndListParam uuid = new TokenAndListParam().addAnd(new TokenParam(EXISTING_OBS_CONDITION_UUID));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.COMMON_SEARCH_HANDLER,
+		    FhirConstants.ID_PROPERTY, uuid);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList, hasSize(equalTo(1)));
+		assertThat(((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getIdElement().getIdPart(),
+		    equalTo(EXISTING_OBS_CONDITION_UUID));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldSearchForConditionsByLastUpdatedDateCreated() {
+		DateRangeParam lastUpdated = new DateRangeParam().setUpperBound(DATE_CREATED).setLowerBound(DATE_CREATED);
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.COMMON_SEARCH_HANDLER,
+		    FhirConstants.LAST_UPDATED_PROPERTY, lastUpdated);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList, hasSize(equalTo(2)));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldSearchForConditionsByMatchingUuidAndLastUpdated() {
+		TokenAndListParam uuid = new TokenAndListParam().addAnd(new TokenParam(EXISTING_OBS_CONDITION_UUID));
+		DateRangeParam lastUpdated = new DateRangeParam().setUpperBound(DATE_CREATED).setLowerBound(DATE_CREATED);
+		
+		SearchParameterMap theParams = new SearchParameterMap()
+		        .addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.ID_PROPERTY, uuid)
+		        .addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.LAST_UPDATED_PROPERTY, lastUpdated);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, not(empty()));
+		assertThat(resultList, hasSize(equalTo(1)));
+		assertThat(((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getIdElement().getIdPart(),
+		    equalTo(EXISTING_OBS_CONDITION_UUID));
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldReturnEmptyListByMismatchingUuidAndLastUpdated() {
+		TokenAndListParam uuid = new TokenAndListParam().addAnd(new TokenParam(EXISTING_OBS_CONDITION_UUID));
+		DateRangeParam lastUpdated = new DateRangeParam().setUpperBound(DATE_VOIDED).setLowerBound(DATE_VOIDED);
+		
+		SearchParameterMap theParams = new SearchParameterMap()
+		        .addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.ID_PROPERTY, uuid)
+		        .addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.LAST_UPDATED_PROPERTY, lastUpdated);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, empty());
+	}
+	
+	@Test
+	public void searchForObsConditions_shouldAddNotNullPatientToReturnedResults() {
+		HashSet<Include> includes = new HashSet<>();
+		Include include = new Include("Condition:patient");
+		includes.add(include);
+		
+		TokenAndListParam uuid = new TokenAndListParam().addAnd(new TokenParam(EXISTING_OBS_CONDITION_UUID));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.INCLUDE_SEARCH_HANDLER, includes)
+		        .addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.ID_PROPERTY, uuid);
+		
+		IBundleProvider results = search(theParams);
+		assertThat(results.size(), Matchers.equalTo(1));
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, Matchers.notNullValue());
+		assertThat(resultList.size(), Matchers.equalTo(2)); // included resource added as part of the result list
+		
+		org.hl7.fhir.r4.model.Condition returnedCondition = (org.hl7.fhir.r4.model.Condition) resultList.iterator().next();
+		assertThat(resultList, hasItem(allOf(is(instanceOf(Patient.class)),
+		    hasProperty("id", Matchers.equalTo(returnedCondition.getSubject().getReferenceElement().getIdPart())))));
+	}
+	
+	@Test
+	public void searchForConditions_shouldReturnConditionByOnsetAgeEqualHour() {
+		QuantityOrListParam orList = new QuantityOrListParam();
+		orList.addOr(new QuantityParam(ParamPrefixEnum.EQUAL, 2, "", "h"));
+		QuantityAndListParam onsetAgeParam = new QuantityAndListParam().addAnd(orList);
+		
+		when(localDateTimeFactory.now()).thenReturn(LocalDateTime.of(2008, Month.JULY, 1, 3, 0, 0));
+		
+		SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.QUANTITY_SEARCH_HANDLER,
+		    onsetAgeParam);
+		
+		IBundleProvider results = search(theParams);
+		
+		List<IBaseResource> resultList = get(results);
+		
+		assertThat(results, notNullValue());
+		assertThat(resultList, hasSize(greaterThanOrEqualTo(1)));
+		assertThat(((org.hl7.fhir.r4.model.Condition) resultList.iterator().next()).getIdElement().getIdPart(),
+		    equalTo(EXISTING_OBS_CONDITION_UUID));
+	}
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ConditionTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ConditionTranslatorImplTest.java
new file mode 100644
index 000000000..19518f02f
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ConditionTranslatorImplTest.java
@@ -0,0 +1,302 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import org.exparity.hamcrest.date.DateMatchers;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matchers;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.hl7.fhir.r4.model.Coding;
+import org.hl7.fhir.r4.model.Condition;
+import org.hl7.fhir.r4.model.DateTimeType;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.Provenance;
+import org.hl7.fhir.r4.model.Reference;
+import org.hl7.fhir.r4.model.Resource;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.Concept;
+import org.openmrs.Obs;
+import org.openmrs.Patient;
+import org.openmrs.User;
+import org.openmrs.api.ConceptService;
+import org.openmrs.module.fhir2.FhirConstants;
+import org.openmrs.module.fhir2.api.translators.ConceptTranslator;
+import org.openmrs.module.fhir2.api.translators.ConditionClinicalStatusTranslator;
+import org.openmrs.module.fhir2.api.translators.PatientReferenceTranslator;
+import org.openmrs.module.fhir2.api.translators.PractitionerReferenceTranslator;
+import org.openmrs.module.fhir2.api.translators.ProvenanceTranslator;
+import org.openmrs.module.fhir2.api.util.FhirUtils;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ConditionTranslatorImplTest {
+	
+	private static final String CONDITION_UUID = "36aa91ad-66f3-455b-b28a-71beb6ca3195";
+	
+	private static final Integer CONDITION_ID = 1284;
+	
+	private static final String PATIENT_UUID = "fc8b217b-2ed4-4dde-b9f7-a5334347e7ca";
+	
+	private static final String PATIENT_REF = "Patient/" + PATIENT_UUID;
+	
+	private static final String SYSTEM = "https://openconceptlab.org/orgs/CIEL/sources/CIEL";
+	
+	private static final Integer CODE = 102309;
+	
+	private static final String CONCEPT_UUID = "31d754f5-3e9e-4ca3-805c-87f97a1f5e4b";
+	
+	private static final String PRACTITIONER_UUID = "2ffb1a5f-bcd3-4243-8f40-78edc2642789";
+	
+	private static final String PRACTITIONER_REFERENCE = FhirConstants.PRACTITIONER + "/" + PRACTITIONER_UUID;
+	
+	@Mock
+	private PatientReferenceTranslator patientReferenceTranslator;
+	
+	@Mock
+	private ProvenanceTranslator<Obs> provenanceTranslator;
+	
+	@Mock
+	ConceptService conceptService;
+	
+	@Mock
+	private ConceptTranslator conceptTranslator;
+	
+	@Mock
+	private PractitionerReferenceTranslator<User> creatorReferenceTranslator;
+	
+	@Mock
+	private ConditionClinicalStatusTranslator<Obs> conditionClinicalStatusTranslator;
+	
+	private ConditionTranslatorImpl conditionTranslator;
+	
+	private Condition fhirCondition;
+	
+	private Obs openmrsCondition;
+	
+	private Patient patient;
+	
+	private Reference patientRef;
+	
+	private Concept concept;
+	
+	@Before
+	public void setup() {
+		conditionTranslator = new ConditionTranslatorImpl();
+		conditionTranslator.setPatientReferenceTranslator(patientReferenceTranslator);
+		conditionTranslator.setConceptTranslator(conceptTranslator);
+		conditionTranslator.setPractitionerReferenceTranslator(creatorReferenceTranslator);
+		conditionTranslator.setProvenanceTranslator(provenanceTranslator);
+		conditionTranslator.setConceptService(conceptService);
+		conditionTranslator.setConditionClinicalStatusTranslator(conditionClinicalStatusTranslator);
+		
+		patient = new Patient();
+		patient.setUuid(PATIENT_UUID);
+		
+		patientRef = new Reference();
+		patientRef.setReference(PATIENT_REF);
+		
+		concept = new Concept();
+		concept.setUuid(CONDITION_UUID);
+		concept.setConceptId(CONDITION_ID);
+		
+		Concept valueCoded = new Concept();
+		concept.setUuid(CONDITION_UUID);
+		concept.setConceptId(CODE);
+		
+		openmrsCondition = new Obs();
+		openmrsCondition.setUuid(CONDITION_UUID);
+		openmrsCondition.setPerson(patient);
+		openmrsCondition.setConcept(concept);
+		openmrsCondition.setValueCoded(valueCoded);
+		
+		fhirCondition = new Condition();
+		fhirCondition.setId(CONDITION_UUID);
+		fhirCondition.setSubject(patientRef);
+	}
+	
+	@Test
+	public void shouldTranslateConditionIdToFhirType() {
+		Condition condition = conditionTranslator.toFhirResource(openmrsCondition);
+		assertThat(condition, notNullValue());
+		assertThat(condition.getId(), equalTo(CONDITION_UUID));
+	}
+	
+	@Test
+	public void shouldTranslateConditionUuidToOpenMrsType() {
+		when(conceptService.getConceptByUuid(FhirConstants.CONDITION_OBSERVATION_CONCEPT_UUID)).thenReturn(concept);
+		Obs obsCondition = conditionTranslator.toOpenmrsType(fhirCondition);
+		assertThat(obsCondition, notNullValue());
+		assertThat(obsCondition.getUuid(), equalTo(CONDITION_UUID));
+	}
+	
+	@Test(expected = NullPointerException.class)
+	public void toFhirResource_shouldThrowExceptionIfConditionToTranslateIsNull() {
+		conditionTranslator.toFhirResource(null);
+	}
+	
+	@Test(expected = InternalErrorException.class)
+	public void toFhirOpenmrsType_shouldThrowExceptionIfConceptProblemListIsNotFoun() {
+		when(patientReferenceTranslator.toOpenmrsType(patientRef)).thenReturn(patient);
+		Obs obsCondition = conditionTranslator.toOpenmrsType(fhirCondition);
+		assertThat(obsCondition, notNullValue());
+		assertThat(obsCondition.getPerson(), notNullValue());
+		assertThat(obsCondition.getPerson().getUuid(), equalTo(PATIENT_UUID));
+	}
+	
+	@Test
+	public void shouldTranslateConditionSubjectToOpenMrsType() {
+		when(conceptService.getConceptByUuid(FhirConstants.CONDITION_OBSERVATION_CONCEPT_UUID)).thenReturn(concept);
+		when(patientReferenceTranslator.toOpenmrsType(patientRef)).thenReturn(patient);
+		Obs obsCondition = conditionTranslator.toOpenmrsType(fhirCondition);
+		assertThat(obsCondition, notNullValue());
+		assertThat(obsCondition.getPerson(), notNullValue());
+		assertThat(obsCondition.getPerson().getUuid(), equalTo(PATIENT_UUID));
+	}
+	
+	@Test
+	public void shouldTranslateConditionPatientToFhirType() {
+		when(patientReferenceTranslator.toFhirResource(patient)).thenReturn(patientRef);
+		Condition condition = conditionTranslator.toFhirResource(openmrsCondition);
+		assertThat(condition, notNullValue());
+		assertThat(condition.getSubject(), notNullValue());
+		assertThat(condition.getSubject(), equalTo(patientRef));
+		assertThat(condition.getSubject().getReference(), equalTo(PATIENT_REF));
+	}
+	
+	@Test
+	public void shouldTranslateOpenMrsConditionOnsetDateToFhirType() {
+		openmrsCondition.setObsDatetime(new Date());
+		org.hl7.fhir.r4.model.Condition condition = conditionTranslator.toFhirResource(openmrsCondition);
+		assertThat(condition, notNullValue());
+		assertThat(condition.getOnsetDateTimeType().getValue(), notNullValue());
+		assertThat(condition.getOnsetDateTimeType().getValue(), DateMatchers.sameDay(new Date()));
+	}
+	
+	@Test
+	public void shouldTranslateFhirConditionOnsetToOpenMrsOnsetDate() {
+		when(conceptService.getConceptByUuid(FhirConstants.CONDITION_OBSERVATION_CONCEPT_UUID)).thenReturn(concept);
+		DateTimeType theDateTime = new DateTimeType();
+		theDateTime.setValue(new Date());
+		fhirCondition.setOnset(theDateTime);
+		Obs condition = conditionTranslator.toOpenmrsType(fhirCondition);
+		assertThat(condition, notNullValue());
+		assertThat(condition.getObsDatetime(), notNullValue());
+		assertThat(condition.getObsDatetime(), DateMatchers.sameDay(new Date()));
+	}
+	
+	@Test
+	public void shouldTranslateConditionCodeToOpenMrsConcept() {
+		CodeableConcept codeableConcept = new CodeableConcept();
+		Coding coding = new Coding();
+		coding.setCode(CODE.toString());
+		coding.setSystem(SYSTEM);
+		codeableConcept.addCoding(coding);
+		fhirCondition.setCode(codeableConcept);
+		Concept concept = new Concept();
+		concept.setUuid(CONCEPT_UUID);
+		concept.setConceptId(CODE);
+		when(conceptService.getConceptByUuid(FhirConstants.CONDITION_OBSERVATION_CONCEPT_UUID)).thenReturn(concept);
+		when(conceptTranslator.toOpenmrsType(codeableConcept)).thenReturn(concept);
+		Obs condition = conditionTranslator.toOpenmrsType(fhirCondition);
+		assertThat(condition, notNullValue());
+		assertThat(condition.getValueCoded(), notNullValue());
+		assertThat(condition.getValueCoded().getConceptId(), equalTo(CODE));
+	}
+	
+	@Test
+	public void shouldTranslateConditionConceptToFhirType() {
+		Concept concept = new Concept();
+		concept.setUuid(CONCEPT_UUID);
+		concept.setConceptId(CODE);
+		CodeableConcept codeableConcept = new CodeableConcept();
+		Coding coding = new Coding();
+		coding.setCode(CODE.toString());
+		coding.setSystem(SYSTEM);
+		codeableConcept.addCoding(coding);
+		openmrsCondition.setValueCoded(concept);
+		when(conceptTranslator.toFhirResource(concept)).thenReturn(codeableConcept);
+		org.hl7.fhir.r4.model.Condition condition = conditionTranslator.toFhirResource(openmrsCondition);
+		assertThat(condition, notNullValue());
+		assertThat(condition.getCode(), notNullValue());
+		assertThat(condition.getCode().getCoding(), not(Collections.emptyList()));
+		assertThat(condition.getCode().getCoding().get(0).getCode(), equalTo(CODE.toString()));
+		assertThat(condition.getCode().getCoding().get(0).getSystem(), equalTo(SYSTEM));
+	}
+	
+	@Test
+	public void shouldTranslateConditionDateCreatedToRecordedDateFhirType() {
+		openmrsCondition.setDateCreated(new Date());
+		org.hl7.fhir.r4.model.Condition condition = conditionTranslator.toFhirResource(openmrsCondition);
+		assertThat(condition, notNullValue());
+		assertThat(condition.getRecordedDate(), notNullValue());
+		assertThat(condition.getRecordedDate(), DateMatchers.sameDay(new Date()));
+	}
+	
+	@Test
+	public void shouldTranslateConditionRecorderToOpenmrsUser() {
+		Reference userRef = new Reference();
+		userRef.setReference(FhirConstants.PRACTITIONER + "/" + PRACTITIONER_UUID);
+		fhirCondition.setRecorder(userRef);
+		User user = new User();
+		user.setUuid(PRACTITIONER_UUID);
+		when(conceptService.getConceptByUuid(FhirConstants.CONDITION_OBSERVATION_CONCEPT_UUID)).thenReturn(concept);
+		when(creatorReferenceTranslator.toOpenmrsType(userRef)).thenReturn(user);
+		Obs condition = conditionTranslator.toOpenmrsType(fhirCondition);
+		assertThat(condition, notNullValue());
+		assertThat(condition.getCreator(), notNullValue());
+		assertThat(condition.getCreator().getUuid(), equalTo(PRACTITIONER_UUID));
+	}
+	
+	@Test
+	public void shouldTranslateConditionCreatorToRecorderFhirType() {
+		User user = new User();
+		user.setUuid(PRACTITIONER_UUID);
+		Reference userRef = new Reference();
+		userRef.setReference(PRACTITIONER_REFERENCE);
+		openmrsCondition.setCreator(user);
+		when(creatorReferenceTranslator.toFhirResource(user)).thenReturn(userRef);
+		org.hl7.fhir.r4.model.Condition condition = conditionTranslator.toFhirResource(openmrsCondition);
+		assertThat(condition, notNullValue());
+		assertThat(condition.getRecorder(), notNullValue());
+		assertThat(condition.getRecorder().getReference(), equalTo(PRACTITIONER_REFERENCE));
+	}
+	
+	@Test
+	public void shouldAddProvenanceToConditionResource() {
+		Provenance provenance = new Provenance();
+		provenance.setId(new IdType(FhirUtils.newUuid()));
+		when(provenanceTranslator.getCreateProvenance(openmrsCondition)).thenReturn(provenance);
+		when(provenanceTranslator.getUpdateProvenance(openmrsCondition)).thenReturn(provenance);
+		
+		org.hl7.fhir.r4.model.Condition result = conditionTranslator.toFhirResource(openmrsCondition);
+		List<Resource> resources = result.getContained();
+		assertThat(resources, Matchers.notNullValue());
+		assertThat(resources, Matchers.not(empty()));
+		assertThat(resources.stream().findAny().isPresent(), CoreMatchers.is(true));
+		assertThat(resources.stream().findAny().get().isResource(), CoreMatchers.is(true));
+		assertThat(resources.stream().findAny().get().getResourceType().name(),
+		    Matchers.equalTo(Provenance.class.getSimpleName()));
+	}
+}
diff --git a/integration-tests-2.2/src/test/java/org/openmrs/module/fhir2/provider/r3/ConditionResourceProviderIntegrationTest.java b/integration-tests-2.2/src/test/java/org/openmrs/module/fhir2/provider/r3/ConditionResourceProviderIntegrationTest.java
index a5d6d917a..86ce645a7 100644
--- a/integration-tests-2.2/src/test/java/org/openmrs/module/fhir2/provider/r3/ConditionResourceProviderIntegrationTest.java
+++ b/integration-tests-2.2/src/test/java/org/openmrs/module/fhir2/provider/r3/ConditionResourceProviderIntegrationTest.java
@@ -182,6 +182,7 @@ public void shouldCreateNewPatientAsJson() throws Exception {
 		assertThat(condition, notNullValue());
 		assertThat(condition.getIdElement().getIdPart(), notNullValue());
 		assertThat(condition.getClinicalStatus(), notNullValue());
+		assertThat(condition.getOnsetDateTimeType(), notNullValue());
 		assertThat(condition.getClinicalStatus().toCode(), equalTo("active"));
 		assertThat(condition.getCode(), notNullValue());
 		assertThat(condition.getCode().getCoding(),
@@ -221,6 +222,7 @@ public void shouldCreateNewConditionAsXML() throws Exception {
 		assertThat(condition, notNullValue());
 		assertThat(condition.getIdElement().getIdPart(), notNullValue());
 		assertThat(condition.getClinicalStatus(), notNullValue());
+		assertThat(condition.getOnsetDateTimeType(), notNullValue());
 		assertThat(condition.getClinicalStatus().toCode(), equalTo("active"));
 		assertThat(condition.getCode().getCoding(),
 		    hasItem(hasProperty("code", equalTo("116128AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))));
diff --git a/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r3/ConditionFhirResourceProviderIntegrationTest.java b/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r3/ConditionFhirResourceProviderIntegrationTest.java
new file mode 100644
index 000000000..3caa77dc0
--- /dev/null
+++ b/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r3/ConditionFhirResourceProviderIntegrationTest.java
@@ -0,0 +1,441 @@
+/*
+ * 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.fhir2.providers.r3;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInRelativeOrder;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.everyItem;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasProperty;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.startsWith;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.sql.Date;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.List;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import org.apache.commons.io.IOUtils;
+import org.hl7.fhir.dstu3.model.Bundle;
+import org.hl7.fhir.dstu3.model.Condition;
+import org.hl7.fhir.dstu3.model.OperationOutcome;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+public class ConditionFhirResourceProviderIntegrationTest extends BaseFhirR3IntegrationTest<ConditionFhirResourceProvider, Condition> {
+	
+	private static final String CONDITION_DATA_SET_FILE = "org/openmrs/module/fhir2/api/dao/impl/FhirObsConditionDaoImplTest_initial_data.xml";
+	
+	private static final String JSON_CREATE_CONDITION_DOCUMENT = "org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.json";
+	
+	private static final String XML_CREATE_CONDITION_DOCUMENT = "org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.xml";
+	
+	private static final String CONDITION_UUID = "86sgf-1f7d-4394-a316-0a458edf28c4";
+	
+	private static final String WRONG_CONDITION_UUID = "950d965d-a935-429f-945f-75a502a90188";
+	
+	private static final String CONDITION_SUBJECT_UUID = "da7f524f-27ce-4bb2-86d6-6d1d05312bd5";
+	
+	private static final String EXISTING_OBS_CONDITION_SUBJECT_UUID = "5946f880-b197-400b-9caa-a3c661d23041";
+	
+	@Getter(AccessLevel.PUBLIC)
+	@Autowired
+	private ConditionFhirResourceProvider resourceProvider;
+	
+	@Before
+	@Override
+	public void setup() throws Exception {
+		super.setup();
+		executeDataSet(CONDITION_DATA_SET_FILE);
+	}
+	
+	@Test
+	public void shouldReturnConditionAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		assertThat(condition, notNullValue());
+		assertThat(condition.getIdElement().getIdPart(), equalTo(CONDITION_UUID));
+		
+		assertThat(condition.getOnsetDateTimeType().getValue(),
+		    equalTo(Date.from(LocalDateTime.of(2008, 07, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant())));
+		
+		assertThat(condition.hasSubject(), is(true));
+		assertThat(condition.getSubject().getReference(), equalTo("Patient/" + EXISTING_OBS_CONDITION_SUBJECT_UUID));
+		
+		assertThat(condition, validResource());
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenConditionNotFoundAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + WRONG_CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnConditionAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		assertThat(condition, notNullValue());
+		assertThat(condition.getIdElement().getIdPart(), equalTo(CONDITION_UUID));
+		
+		assertThat(condition.getOnsetDateTimeType().getValue(),
+		    equalTo(Date.from(LocalDateTime.of(2008, 07, 01, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant())));
+		
+		assertThat(condition.hasSubject(), is(true));
+		assertThat(condition.getSubject().getReference(), equalTo("Patient/" + EXISTING_OBS_CONDITION_SUBJECT_UUID));
+		
+		assertThat(condition, validResource());
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenConditionNotFoundAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + WRONG_CONDITION_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldCreateNewConditionAsJson() throws Exception {
+		String jsonCondition;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(JSON_CREATE_CONDITION_DOCUMENT)) {
+			assertThat(is, notNullValue());
+			jsonCondition = IOUtils.toString(is, StandardCharsets.UTF_8);
+			assertThat(jsonCondition, notNullValue());
+		}
+		
+		MockHttpServletResponse response = post("/Condition").accept(FhirMediaTypes.JSON).jsonContent(jsonCondition).go();
+		
+		assertThat(response, isCreated());
+		assertThat(response.getHeader("Location"), containsString("/Condition/"));
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentType(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		assertThat(condition, notNullValue());
+		assertThat(condition.getIdElement().getIdPart(), notNullValue());
+		assertThat(condition.getCode(), notNullValue());
+		assertThat(condition.getCode().getCoding(),
+		    hasItem(hasProperty("code", equalTo("116128AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))));
+		assertThat(condition.getSubject(), notNullValue());
+		assertThat(condition.getSubject().getReference(), endsWith(CONDITION_SUBJECT_UUID));
+		
+		assertThat(condition, validResource());
+		
+		response = get("/Condition/" + condition.getIdElement().getIdPart()).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		
+		Condition newCondition = readResponse(response);
+		
+		assertThat(newCondition.getId(), equalTo(condition.getId()));
+	}
+	
+	@Test
+	public void shouldCreateNewConditionAsXML() throws Exception {
+		String xmlCondition;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(XML_CREATE_CONDITION_DOCUMENT)) {
+			assertThat(is, notNullValue());
+			xmlCondition = IOUtils.toString(is, StandardCharsets.UTF_8);
+			assertThat(xmlCondition, notNullValue());
+		}
+		
+		MockHttpServletResponse response = post("/Condition").accept(FhirMediaTypes.XML).xmlContext(xmlCondition).go();
+		
+		assertThat(response, isCreated());
+		assertThat(response.getHeader("Location"), containsString("/Condition/"));
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentType(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		assertThat(condition, notNullValue());
+		assertThat(condition.getIdElement().getIdPart(), notNullValue());
+		assertThat(condition.getOnsetDateTimeType(), notNullValue());
+		assertThat(condition.getCode().getCoding(),
+		    hasItem(hasProperty("code", equalTo("116128AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))));
+		assertThat(condition.getSubject().getReference(), endsWith(CONDITION_SUBJECT_UUID));
+		
+		assertThat(condition, validResource());
+		
+		response = get("/Condition/" + condition.getIdElement().getIdPart()).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		
+		Condition newCondition = readResponse(response);
+		
+		assertThat(newCondition.getId(), equalTo(condition.getId()));
+	}
+	
+	@Test
+	public void shouldReturnBadRequestWhenDocumentIdDoesNotMatchConditionIdAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), equalTo(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		condition.setId(WRONG_CONDITION_UUID);
+		
+		response = put("/Condition/" + CONDITION_UUID).jsonContent(toJson(condition)).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isBadRequest());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenUpdatingNonExistentConditionAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), equalTo(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		condition.setId(WRONG_CONDITION_UUID);
+		
+		response = put("/Condition/" + WRONG_CONDITION_UUID).jsonContent(toJson(condition)).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnBadRequestWhenDocumentIdDoesNotMatchConditionIdAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), equalTo(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		condition.setId(WRONG_CONDITION_UUID);
+		
+		response = put("/Condition/" + CONDITION_UUID).xmlContext(toXML(condition)).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isBadRequest());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenUpdatingNonExistentConditionAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), equalTo(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		condition.setId(WRONG_CONDITION_UUID);
+		
+		response = put("/Condition/" + WRONG_CONDITION_UUID).xmlContext(toXML(condition)).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldDeleteExistingCondition() throws Exception {
+		MockHttpServletResponse response = delete("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		
+		response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, statusEquals(HttpStatus.GONE));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenDeletingNonExistentCondition() throws Exception {
+		MockHttpServletResponse response = delete("/Condition/" + WRONG_CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldSearchForAllConditionsAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition").accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Bundle results = readBundleResponse(response);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.getType(), equalTo(Bundle.BundleType.SEARCHSET));
+		assertThat(results.hasEntry(), is(true));
+		
+		List<Bundle.BundleEntryComponent> entries = results.getEntry();
+		
+		assertThat(entries, everyItem(hasProperty("fullUrl", startsWith("http://localhost/ws/fhir2/R3/Condition/"))));
+		assertThat(entries, everyItem(hasResource(instanceOf(Condition.class))));
+		assertThat(entries, everyItem(hasResource(validResource())));
+		assertThat(entries.size(), equalTo(2));
+	}
+	
+	@Test
+	public void shouldReturnSortedAndFilteredSearchResultsForConditionsAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition?clinical-status=active&onset-date=2008&_sort=-onset-date")
+		        .accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Bundle results = readBundleResponse(response);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.getType(), equalTo(Bundle.BundleType.SEARCHSET));
+		assertThat(results.hasEntry(), is(true));
+		
+		List<Bundle.BundleEntryComponent> entries = results.getEntry();
+		
+		assertThat(entries.size(), equalTo(2));
+		assertThat(entries,
+		    containsInRelativeOrder(
+		        hasResource(hasProperty(
+		            "onsetDateTimeType",
+		            hasProperty(
+		                "value",
+		                equalTo(
+		                    Date.from(LocalDateTime.of(2008, 7, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()))))),
+		        hasResource(hasProperty("onsetDateTimeType", hasProperty("value", equalTo(
+		            Date.from(LocalDateTime.of(2008, 7, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant())))))));
+	}
+	
+	@Test
+	public void shouldSearchForAllConditionsAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition").accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Bundle results = readBundleResponse(response);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.getType(), equalTo(Bundle.BundleType.SEARCHSET));
+		assertThat(results.hasEntry(), is(true));
+		
+		List<Bundle.BundleEntryComponent> entries = results.getEntry();
+		
+		assertThat(entries, everyItem(hasProperty("fullUrl", startsWith("http://localhost/ws/fhir2/R3/Condition/"))));
+		assertThat(entries, everyItem(hasResource(instanceOf(Condition.class))));
+		assertThat(entries, everyItem(hasResource(validResource())));
+		assertThat(entries.size(), equalTo(2));
+	}
+	
+	@Test
+	public void shouldReturnSortedAndFilteredSearchResultsForConditionsAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition?clinical-status=active&onset-date=2008&_sort=-onset-date")
+		        .accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Bundle results = readBundleResponse(response);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.getType(), equalTo(Bundle.BundleType.SEARCHSET));
+		assertThat(results.hasEntry(), is(true));
+		
+		List<Bundle.BundleEntryComponent> entries = results.getEntry();
+		
+		assertThat(entries.size(), equalTo(2));
+		assertThat(entries,
+		    containsInRelativeOrder(
+		        hasResource(hasProperty(
+		            "onsetDateTimeType",
+		            hasProperty(
+		                "value",
+		                equalTo(
+		                    Date.from(LocalDateTime.of(2008, 7, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()))))),
+		        hasResource(hasProperty("onsetDateTimeType", hasProperty("value", equalTo(
+		            Date.from(LocalDateTime.of(2008, 7, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant())))))));
+	}
+	
+}
diff --git a/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r4/ConditionFhirResourceProviderIntegrationTest.java b/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r4/ConditionFhirResourceProviderIntegrationTest.java
new file mode 100644
index 000000000..ba58fd9b0
--- /dev/null
+++ b/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r4/ConditionFhirResourceProviderIntegrationTest.java
@@ -0,0 +1,436 @@
+/*
+ * 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.fhir2.providers.r4;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInRelativeOrder;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.everyItem;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasProperty;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.startsWith;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.sql.Date;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.List;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import org.apache.commons.io.IOUtils;
+import org.hl7.fhir.r4.model.Bundle;
+import org.hl7.fhir.r4.model.Condition;
+import org.hl7.fhir.r4.model.OperationOutcome;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+public class ConditionFhirResourceProviderIntegrationTest extends BaseFhirR4IntegrationTest<ConditionFhirResourceProvider, Condition> {
+	
+	private static final String CONDITION_UUID = "86sgf-1f7d-4394-a316-0a458edf28c4";
+	
+	private static final String OBS_CONDITION_INITIAL_DATA_XML = "org/openmrs/module/fhir2/api/dao/impl/FhirObsConditionDaoImplTest_initial_data.xml";
+	
+	private static final String CONDITION_SUBJECT_UUID = "da7f524f-27ce-4bb2-86d6-6d1d05312bd5";
+	
+	private static final String EXISTING_OBS_CONDITION_SUBJECT_UUID = "5946f880-b197-400b-9caa-a3c661d23041";
+	
+	private static final String WRONG_CONDITION_UUID = "950d965d-a935-429f-945f-75a502a90188";
+	
+	private static final String JSON_CREATE_CONDITION_DOCUMENT = "org/openmrs/module/fhir2/providers/ConditionWebTest_create.json";
+	
+	private static final String XML_CREATE_CONDITION_DOCUMENT = "org/openmrs/module/fhir2/providers/ConditionWebTest_create.xml";
+	
+	@Getter(AccessLevel.PUBLIC)
+	@Autowired
+	private ConditionFhirResourceProvider resourceProvider;
+	
+	@Before
+	public void setUp() throws Exception {
+		super.setup();
+		executeDataSet(OBS_CONDITION_INITIAL_DATA_XML);
+	}
+	
+	@Test
+	public void shouldReturnConditionAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		assertThat(condition, notNullValue());
+		assertThat(condition.getIdElement().getIdPart(), equalTo(CONDITION_UUID));
+		
+		assertThat(condition.getOnsetDateTimeType().getValue(),
+		    equalTo(Date.from(LocalDateTime.of(2008, 07, 01, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant())));
+		
+		assertThat(condition.hasSubject(), is(true));
+		assertThat(condition.getSubject().getReference(), equalTo("Patient/" + EXISTING_OBS_CONDITION_SUBJECT_UUID));
+		
+		assertThat(condition, validResource());
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenConditionNotFoundAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + WRONG_CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnConditionAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		assertThat(condition, notNullValue());
+		assertThat(condition.getIdElement().getIdPart(), equalTo(CONDITION_UUID));
+		
+		assertThat(condition.getOnsetDateTimeType().getValue(),
+		    equalTo(Date.from(LocalDateTime.of(2008, 07, 01, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant())));
+		
+		assertThat(condition.hasSubject(), is(true));
+		assertThat(condition.getSubject().getReference(), equalTo("Patient/" + EXISTING_OBS_CONDITION_SUBJECT_UUID));
+		
+		assertThat(condition, validResource());
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenConditionNotFoundAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + WRONG_CONDITION_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldCreateNewConditionAsJson() throws Exception {
+		String jsonCondition;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(JSON_CREATE_CONDITION_DOCUMENT)) {
+			assertThat(is, notNullValue());
+			jsonCondition = IOUtils.toString(is, StandardCharsets.UTF_8);
+			assertThat(jsonCondition, notNullValue());
+		}
+		
+		MockHttpServletResponse response = post("/Condition").accept(FhirMediaTypes.JSON).jsonContent(jsonCondition).go();
+		
+		assertThat(response, isCreated());
+		assertThat(response.getHeader("Location"), containsString("/Condition/"));
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentType(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		assertThat(condition, notNullValue());
+		assertThat(condition.getIdElement().getIdPart(), notNullValue());
+		assertThat(condition.getCode(), notNullValue());
+		assertThat(condition.getCode().getCoding(),
+		    hasItem(hasProperty("code", equalTo("116128AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))));
+		assertThat(condition.getSubject(), notNullValue());
+		assertThat(condition.getSubject().getReference(), endsWith(CONDITION_SUBJECT_UUID));
+		
+		assertThat(condition, validResource());
+		
+		response = get("/Condition/" + condition.getIdElement().getIdPart()).accept(FhirMediaTypes.JSON).go();
+		assertThat(response, isOk());
+		Condition newCondition = readResponse(response);
+		assertThat(newCondition.getId(), equalTo(condition.getId()));
+	}
+	
+	@Test
+	public void shouldCreateNewConditionAsXML() throws Exception {
+		String xmlCondition;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(XML_CREATE_CONDITION_DOCUMENT)) {
+			assertThat(is, notNullValue());
+			xmlCondition = IOUtils.toString(is, StandardCharsets.UTF_8);
+			assertThat(xmlCondition, notNullValue());
+		}
+		
+		MockHttpServletResponse response = post("/Condition").accept(FhirMediaTypes.XML).xmlContext(xmlCondition).go();
+		
+		assertThat(response, isCreated());
+		assertThat(response.getHeader("Location"), containsString("/Condition/"));
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentType(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		assertThat(condition, notNullValue());
+		assertThat(condition.getIdElement().getIdPart(), notNullValue());
+		assertThat(condition.getCode().getCoding(),
+		    hasItem(hasProperty("code", equalTo("116128AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))));
+		assertThat(condition.getSubject().getReference(), endsWith(CONDITION_SUBJECT_UUID));
+		
+		assertThat(condition, validResource());
+		
+		response = get("/Condition/" + condition.getIdElement().getIdPart()).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		
+		Condition newCondition = readResponse(response);
+		
+		assertThat(newCondition.getId(), equalTo(condition.getId()));
+	}
+	
+	@Test
+	public void shouldReturnBadRequestWhenDocumentIdDoesNotMatchConditionIdAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), equalTo(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		condition.setId(WRONG_CONDITION_UUID);
+		
+		response = put("/Condition/" + CONDITION_UUID).jsonContent(toJson(condition)).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isBadRequest());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenUpdatingNonExistentConditionAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), equalTo(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		condition.setId(WRONG_CONDITION_UUID);
+		
+		response = put("/Condition/" + WRONG_CONDITION_UUID).jsonContent(toJson(condition)).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnBadRequestWhenDocumentIdDoesNotMatchConditionIdAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), equalTo(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		condition.setId(WRONG_CONDITION_UUID);
+		
+		response = put("/Condition/" + CONDITION_UUID).xmlContext(toXML(condition)).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isBadRequest());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenUpdatingNonExistentConditionAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), equalTo(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Condition condition = readResponse(response);
+		
+		condition.setId(WRONG_CONDITION_UUID);
+		
+		response = put("/Condition/" + WRONG_CONDITION_UUID).xmlContext(toXML(condition)).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldDeleteExistingCondition() throws Exception {
+		MockHttpServletResponse response = delete("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		
+		response = get("/Condition/" + CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, statusEquals(HttpStatus.GONE));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenDeletingNonExistentCondition() throws Exception {
+		MockHttpServletResponse response = delete("/Condition/" + WRONG_CONDITION_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldSearchForAllConditionsAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition").accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Bundle results = readBundleResponse(response);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.getType(), equalTo(Bundle.BundleType.SEARCHSET));
+		assertThat(results.hasEntry(), is(true));
+		
+		List<Bundle.BundleEntryComponent> entries = results.getEntry();
+		
+		assertThat(entries, everyItem(hasProperty("fullUrl", startsWith("http://localhost/ws/fhir2/R4/Condition/"))));
+		assertThat(entries, everyItem(hasResource(instanceOf(Condition.class))));
+		assertThat(entries, everyItem(hasResource(validResource())));
+		assertThat(entries.size(), equalTo(2));
+	}
+	
+	@Test
+	public void shouldReturnSortedAndFilteredSearchResultsForConditionsAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Condition?clinical-status=active&onset-date=2008&_sort=-onset-date")
+		        .accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Bundle results = readBundleResponse(response);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.getType(), equalTo(Bundle.BundleType.SEARCHSET));
+		assertThat(results.hasEntry(), is(true));
+		
+		List<Bundle.BundleEntryComponent> entries = results.getEntry();
+		
+		assertThat(entries.size(), equalTo(2));
+		assertThat(entries,
+		    containsInRelativeOrder(
+		        hasResource(hasProperty(
+		            "onsetDateTimeType",
+		            hasProperty(
+		                "value",
+		                equalTo(
+		                    Date.from(LocalDateTime.of(2008, 7, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()))))),
+		        hasResource(hasProperty("onsetDateTimeType", hasProperty("value", equalTo(
+		            Date.from(LocalDateTime.of(2008, 7, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant())))))));
+	}
+	
+	@Test
+	public void shouldSearchForAllConditionsAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition").accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Bundle results = readBundleResponse(response);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.getType(), equalTo(Bundle.BundleType.SEARCHSET));
+		assertThat(results.hasEntry(), is(true));
+		
+		List<Bundle.BundleEntryComponent> entries = results.getEntry();
+		
+		assertThat(entries, everyItem(hasProperty("fullUrl", startsWith("http://localhost/ws/fhir2/R4/Condition/"))));
+		assertThat(entries, everyItem(hasResource(instanceOf(Condition.class))));
+		assertThat(entries, everyItem(hasResource(validResource())));
+		assertThat(entries.size(), equalTo(2));
+	}
+	
+	@Test
+	public void shouldReturnSortedAndFilteredSearchResultsForConditionsAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Condition?clinical-status=active&onset-date=2008&_sort=-onset-date")
+		        .accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Bundle results = readBundleResponse(response);
+		
+		assertThat(results, notNullValue());
+		assertThat(results.getType(), equalTo(Bundle.BundleType.SEARCHSET));
+		assertThat(results.hasEntry(), is(true));
+		
+		List<Bundle.BundleEntryComponent> entries = results.getEntry();
+		
+		assertThat(entries.size(), equalTo(2));
+		assertThat(entries,
+		    containsInRelativeOrder(
+		        hasResource(hasProperty(
+		            "onsetDateTimeType",
+		            hasProperty(
+		                "value",
+		                equalTo(
+		                    Date.from(LocalDateTime.of(2008, 7, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()))))),
+		        hasResource(hasProperty("onsetDateTimeType", hasProperty("value", equalTo(
+		            Date.from(LocalDateTime.of(2008, 7, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant())))))));
+	}
+	
+}
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirObsConditionDaoImplTest_initial_data.xml b/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirObsConditionDaoImplTest_initial_data.xml
new file mode 100644
index 000000000..0b921663a
--- /dev/null
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirObsConditionDaoImplTest_initial_data.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+    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.
+-->
+<dataset>
+    <concept concept_id="116128" retired="false" datatype_id="4" class_id="4" is_set="false" creator="1" date_created="2008-08-15 15:27:51.0" version="" uuid="116128AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"/>
+	<concept_description concept_description_id="116128" concept_id="116128" description="Malaria" locale="en_GB" creator="1" date_created="2008-08-15 15:27:51.0" uuid="4639FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/>
+	<concept_name concept_name_id="116128" concept_id="116128" name="Malaria" locale="en_GB" concept_name_type="FULLY_SPECIFIED" locale_preferred="1" voided="false" creator="1" date_created="2008-08-15 15:27:51.0" uuid="16603BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"/>
+	<concept concept_id="1284" retired="false" datatype_id="4" class_id="4" is_set="false" creator="1" date_created="2008-08-15 15:27:51.0" version="" uuid="1284AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"/>
+	<concept_description concept_description_id="1284" concept_id="1284" description="PROBLEM LIST" locale="en_GB" creator="1" date_created="2008-08-15 15:27:51.0" uuid="1284FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"/>
+	<concept_name concept_name_id="1284" concept_id="1284" name="PROBLEM LIST" locale="en_GB" concept_name_type="FULLY_SPECIFIED" locale_preferred="1" voided="false" creator="1" date_created="2008-08-15 15:27:51.0" uuid="1284BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"/>
+    <concept_map_type concept_map_type_id="1" name="SAME-AS" creator="1" date_created="2020-04-02 00:00:00.0" changed_by="1" is_hidden="false" retired="false" retired_by="1" uuid="35543629-7d8c-11e1-909d-c80aa9edcf4ex"/>
+    <concept_reference_source concept_source_id="1" name="ICD-10-WHO" description="SNOMED Preferred mapping" hl7_code="SCT" creator="1" date_created="2009-11-19 20:19:16.0" retired="false" retired_by="1" uuid="1ADDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" />
+    <concept_reference_term concept_reference_term_id="1" concept_source_id="1" code="C00" creator="1" date_created="2010-04-25 00:00:00.0" changed_by="1" retired="false" retired_by="1" uuid="8211fc83-6c63-38c1-b2ce-108aeee34ea6"/>
+    <concept_reference_map concept_map_id="93972" creator="1" date_created="2010-04-25 11:39:31.0" concept_id="116128" uuid="93972ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" concept_reference_term_id="1" concept_map_type_id="1" changed_by="1"/>
+    <obs obs_id="30" person_id="7" concept_id="1284" encounter_id="3" obs_datetime="2008-07-01 00:00:00.0" location_id="1"  comments="" creator="1" date_created="2008-08-18 14:09:35.0" voided="false" value_coded="116128"   uuid="86sgf-1f7d-4394-a316-0a458edf28c4"/>
+	<obs obs_id="31" person_id="7" concept_id="1284" encounter_id="3" obs_datetime="2008-07-01 00:00:00.0" location_id="1"  comments="" creator="1" date_created="2008-08-18 14:09:35.0" voided="false" value_coded="116128"   uuid="86sgf-1f7d-4394-a316-0a458edf28c7"/>
+    <obs obs_id="32" person_id="7" concept_id="116128" encounter_id="3" obs_datetime="2008-07-01 10:00:00.0" location_id="1"  comments="" creator="1" date_created="2008-08-18 14:09:35.0" voided="false"  value_coded="116128" uuid="942ec003-a55d-43c4-ac7a-bd6d1ba63382"/>
+    <obs obs_id="33" person_id="7" concept_id="1284" encounter_id="3" obs_datetime="2008-07-01 10:00:00.0" location_id="1"  comments="" creator="1" date_voided="2010-09-03 14:09:35.0" date_created="2008-08-18 14:09:35.0" voided="true"  value_coded="116128" uuid="94dhs003-a55d-43c4-ac7a-bd6d1ba63388"/>	
+    <fhir_concept_source fhir_concept_source_id="1" name="Some Made-up Terminology" url="http://made_up_concepts.info/sct" concept_source_id="1" creator="1" date_created="2005-01-01 00:00:00.0" retired="0" uuid="0d30bea3-4ba2-4ab4-ac7b-5525840bde20" />	
+</dataset>
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.json b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.json
index 01dc52966..e50376b37 100644
--- a/test-data/src/test/resources/org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.json
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.json
@@ -22,5 +22,5 @@
     },
     "display": "Horatio Hornblower (OpenMRS ID:101-6)"
   },
-  "recordedDate": "2019-06-18T06:37:26+03:00"
+  "onsetDateTime": "2019-06-18T06:37:26+03:00"
 }
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.xml b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.xml
index 9fb064c25..2b947bc5f 100644
--- a/test-data/src/test/resources/org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.xml
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/ConditionWebTest_create_r3.xml
@@ -27,5 +27,5 @@
         </identifier>
         <display value="Horatio Hornblower (OpenMRS ID:101-6)"/>
     </subject>
-    <recordedDate value="2019-06-18T06:37:26+03:00"/>
-</Condition>
\ No newline at end of file
+    <onsetDateTime value="2019-06-18T06:37:26+03:00"/>
+</Condition>

From 355e5ce87e46f9d15a66d6f2d9e5a0fa6692f5a5 Mon Sep 17 00:00:00 2001
From: Ankit kumar <49350053+theanandankit@users.noreply.github.com>
Date: Mon, 1 Feb 2021 20:16:47 +0530
Subject: [PATCH 08/18] FM-189: Add Timing.TimingRepeatComponent.DurationUnit
 property (#310)

---
 .../fhir2/api/mappings/DurationUnitMap.java   |  49 ++++
 .../translators/DurationUnitTranslator.java   |  21 ++
 .../impl/DurationUnitTranslatorImpl.java      |  46 ++++
 .../fhir2/model/FhirDurationUnitMap.java      |  90 +++++++
 api/src/main/resources/liquibase.xml          | 219 ++++++++++++++++++
 .../impl/DurationUnitTranslatorImplTest.java  | 153 ++++++++++++
 ...urationUnitTranslatorTest_initial_data.xml |  19 ++
 7 files changed, 597 insertions(+)
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/mappings/DurationUnitMap.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/translators/DurationUnitTranslator.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImpl.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/model/FhirDurationUnitMap.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImplTest.java
 create mode 100644 test-data/src/test/resources/org/openmrs/module/fhir2/mapping/FhirDurationUnitTranslatorTest_initial_data.xml

diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/mappings/DurationUnitMap.java b/api/src/main/java/org/openmrs/module/fhir2/api/mappings/DurationUnitMap.java
new file mode 100644
index 000000000..7561e4b47
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/mappings/DurationUnitMap.java
@@ -0,0 +1,49 @@
+/*
+ * 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.fhir2.api.mappings;
+
+import static org.hibernate.criterion.Restrictions.eq;
+
+import javax.annotation.Nonnull;
+
+import lombok.AccessLevel;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.hibernate.NonUniqueResultException;
+import org.hibernate.SessionFactory;
+import org.hibernate.criterion.Projections;
+import org.hl7.fhir.r4.model.Timing;
+import org.openmrs.module.fhir2.model.FhirDurationUnitMap;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@Setter(AccessLevel.PUBLIC)
+public class DurationUnitMap {
+	
+	@Autowired
+	@Qualifier("sessionFactory")
+	private SessionFactory sessionFactory;
+	
+	public Timing.UnitsOfTime getDurationUnit(@Nonnull String conceptUuid) {
+		
+		try {
+			return (Timing.UnitsOfTime) sessionFactory.getCurrentSession().createCriteria(FhirDurationUnitMap.class)
+			        .createAlias("concept", "c").add(eq("c.uuid", conceptUuid))
+			        .setProjection(Projections.property("unit_of_time")).uniqueResult();
+		}
+		catch (NonUniqueResultException e) {
+			log.error("Exception caught while trying to load DurationUnit for concept '{}'", conceptUuid, e);
+		}
+		return null;
+	}
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/DurationUnitTranslator.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/DurationUnitTranslator.java
new file mode 100644
index 000000000..0e4750c65
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/DurationUnitTranslator.java
@@ -0,0 +1,21 @@
+/*
+ * 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.fhir2.api.translators;
+
+import javax.annotation.Nonnull;
+
+import org.hl7.fhir.r4.model.Timing;
+import org.openmrs.Concept;
+
+public interface DurationUnitTranslator extends ToFhirTranslator<Concept, Timing.UnitsOfTime> {
+	
+	@Override
+	Timing.UnitsOfTime toFhirResource(@Nonnull Concept concept);
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImpl.java
new file mode 100644
index 000000000..530cd7d81
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImpl.java
@@ -0,0 +1,46 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import javax.annotation.Nonnull;
+
+import lombok.AccessLevel;
+import lombok.Setter;
+import org.hl7.fhir.r4.model.Timing;
+import org.openmrs.Concept;
+import org.openmrs.module.fhir2.api.mappings.DurationUnitMap;
+import org.openmrs.module.fhir2.api.translators.DurationUnitTranslator;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@Setter(AccessLevel.PACKAGE)
+public class DurationUnitTranslatorImpl implements DurationUnitTranslator {
+	
+	Timing.UnitsOfTime unitsOfTime;
+	
+	@Autowired
+	private DurationUnitMap durationUnitMap;
+	
+	@Override
+	public Timing.UnitsOfTime toFhirResource(@Nonnull Concept concept) {
+		
+		if (concept.getUuid() == null) {
+			return null;
+		}
+		
+		unitsOfTime = durationUnitMap.getDurationUnit(concept.getUuid());
+		
+		if (unitsOfTime == null) {
+			return Timing.UnitsOfTime.NULL;
+		}
+		
+		return unitsOfTime;
+	}
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/model/FhirDurationUnitMap.java b/api/src/main/java/org/openmrs/module/fhir2/model/FhirDurationUnitMap.java
new file mode 100644
index 000000000..92e854eee
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/model/FhirDurationUnitMap.java
@@ -0,0 +1,90 @@
+/*
+ * 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.fhir2.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import java.util.Date;
+import java.util.UUID;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hibernate.search.annotations.Field;
+import org.hl7.fhir.r4.model.Timing;
+import org.openmrs.Auditable;
+import org.openmrs.Concept;
+import org.openmrs.Retireable;
+import org.openmrs.User;
+
+@Data
+@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
+@Entity
+@Table(name = "fhir_duration_unit_map")
+public class FhirDurationUnitMap implements Auditable, Retireable {
+	
+	@EqualsAndHashCode.Include
+	@Id
+	@GeneratedValue(strategy = GenerationType.AUTO)
+	@Column(name = "duration_unit_map_id")
+	private Integer id;
+	
+	@ManyToOne(optional = false)
+	@JoinColumn(nullable = false, name = "concept_id")
+	private Concept concept;
+	
+	@Column(nullable = false, name = "unit_of_time")
+	@Enumerated(EnumType.STRING)
+	private Timing.UnitsOfTime unit_of_time;
+	
+	@ManyToOne(optional = false)
+	@JoinColumn(name = "creator", updatable = false)
+	protected User creator;
+	
+	@Column(name = "date_created", nullable = false, updatable = false)
+	private Date dateCreated;
+	
+	@ManyToOne
+	@JoinColumn(name = "changed_by")
+	private User changedBy;
+	
+	@Column(name = "date_changed")
+	private Date dateChanged;
+	
+	@Column(name = "retired", nullable = false)
+	@Field
+	private Boolean retired = Boolean.FALSE;
+	
+	@Column(name = "date_retired")
+	private Date dateRetired;
+	
+	@ManyToOne
+	@JoinColumn(name = "retired_by")
+	private User retiredBy;
+	
+	@Column(name = "retire_reason")
+	private String retireReason;
+	
+	@Column(name = "uuid", unique = true, nullable = false, length = 36)
+	private String uuid = UUID.randomUUID().toString();
+	
+	@Override
+	public Boolean isRetired() {
+		return retired;
+	}
+}
diff --git a/api/src/main/resources/liquibase.xml b/api/src/main/resources/liquibase.xml
index a6d18b98e..fcdd7783c 100644
--- a/api/src/main/resources/liquibase.xml
+++ b/api/src/main/resources/liquibase.xml
@@ -696,4 +696,223 @@
         ]]>
         </sql>
     </changeSet>
+
+	<changeSet id="add_fhir_duration_unit_map_20200930" author="ibacher">
+		<preConditions onFail="MARK_RAN" onError="WARN">
+			<not>
+				<tableExists tableName="fhir_duration_unit_map"/>
+			</not>
+		</preConditions>
+		<createTable tableName="fhir_duration_unit_map">
+			<column name="duration_unit_map_id" type="int" autoIncrement="true">
+				<constraints primaryKey="true"/>
+			</column>
+			<column name="concept_id" type="int"/>
+			<column name="unit_of_time" type="varchar(20)">
+				<constraints nullable="false"/>
+			</column>
+			<column name="creator" type="int">
+				<constraints nullable="false"/>
+			</column>
+			<column name="date_created" type="datetime">
+				<constraints nullable="false"/>
+			</column>
+			<column name="changed_by" type="int"/>
+			<column name="date_changed" type="datetime"/>
+			<column name="retired" type="boolean" defaultValueBoolean="false">
+				<constraints nullable="false"/>
+			</column>
+			<column name="retired_by" type="int"/>
+			<column name="date_retired" type="datetime"/>
+			<column name="retired_reason" type="varchar(255)" defaultValue="null"/>
+			<column name="uuid" type="char(36)">
+				<constraints nullable="false" unique="true"/>
+			</column>
+		</createTable>
+		<addForeignKeyConstraint constraintName="fhir_duration_unit_map_creator"
+		                         baseTableName="fhir_duration_unit_map" baseColumnNames="creator"
+		                         referencedTableName="users" referencedColumnNames="user_id"/>
+		<addForeignKeyConstraint constraintName="fhir_duration_unit_map_changed_by"
+		                         baseTableName="fhir_duration_unit_map" baseColumnNames="changed_by"
+		                         referencedTableName="users" referencedColumnNames="user_id"/>
+		<addForeignKeyConstraint constraintName="fhir_duration_unit_map_retired_by"
+		                         baseTableName="fhir_duration_unit_map" baseColumnNames="retired_by"
+		                         referencedTableName="users" referencedColumnNames="user_id"/>
+		<addForeignKeyConstraint constraintName="fhir_duration_unit_map_concept"
+		                         baseTableName="fhir_duration_unit_map" baseColumnNames="concept_id"
+		                         referencedTableName="concept" referencedColumnNames="concept_id"/>
+		<createIndex tableName="fhir_duration_unit_map"
+		             indexName="fhir_duration_unit_map_unit_of_time">
+			<column name="unit_of_time"/>
+		</createIndex>
+	</changeSet>
+
+	<changeSet id="add_default_duration_unit_20200930" author="ibacher" dbms="mysql,mariadb">
+		<preConditions onFail="MARK_RAN" onError="WARN">
+			<and>
+                <tableExists tableName="concept"/>
+				<tableExists tableName="fhir_duration_unit_map"/>
+				<sqlCheck expectedResult="0">
+					select count(*) from fhir_duration_unit_map
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '162583AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1733AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1822AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1072AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1073AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1074AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1734AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+			</and>
+		</preConditions>
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'S', 1, now(), uuid()
+            from concept
+            where uuid = '162583AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'MIN', 1, now(), uuid()
+            from concept
+            where uuid = '1733AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'H', 1, now(), uuid()
+            from concept
+            where uuid = '1822AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'D', 1, now(), uuid()
+            from concept
+            where uuid = '1072AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'WK', 1, now(), uuid()
+            from concept
+            where uuid = '1073AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'MO', 1, now(), uuid()
+            from concept
+            where uuid = '1074AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'A' ,1, now(), uuid()
+            from concept
+            where uuid = '1734AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+	</changeSet>
+
+	<changeSet id="add_default_duration_unit_20200930" author="ibacher" dbms="postgresql">
+		<preConditions onFail="MARK_RAN" onError="WARN">
+			<and>
+				<tableExists tableName="fhir_duration_unit_map"/>
+				<sqlCheck expectedResult="0">
+					select count(*) from fhir_duration_unit_map
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '162583AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1733AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1072AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1073AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1073AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1074AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+				<sqlCheck expectedResult="1">
+					select 1 from concept where uuid = '1734AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+				</sqlCheck>
+
+			</and>
+		</preConditions>
+		<!-- UUID function taken from here: https://stackoverflow.com/a/21327318 -->
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, "S", 1, now(), uuid_in(overlay(overlay(md5(random()::text || ':' || clock_timestamp()::text) placing '4' from 13) placing to_hex(floor(random()*(11-8+1) + 8)::int)::text from 17)::cstring)
+            from concept
+            where uuid = '162583AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'MIN', 1, now(), uuid_in(overlay(overlay(md5(random()::text || ':' || clock_timestamp()::text) placing '4' from 13) placing to_hex(floor(random()*(11-8+1) + 8)::int)::text from 17)::cstring)
+            from concept
+            where uuid = '1733AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'H', 1, now(), uuid_in(overlay(overlay(md5(random()::text || ':' || clock_timestamp()::text) placing '4' from 13) placing to_hex(floor(random()*(11-8+1) + 8)::int)::text from 17)::cstring)
+            from concept
+            where uuid = '1822AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'D', 1, now(), uuid_in(overlay(overlay(md5(random()::text || ':' || clock_timestamp()::text) placing '4' from 13) placing to_hex(floor(random()*(11-8+1) + 8)::int)::text from 17)::cstring)
+            from concept
+            where uuid = '1072AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'WK', 1, now(), uuid_in(overlay(overlay(md5(random()::text || ':' || clock_timestamp()::text) placing '4' from 13) placing to_hex(floor(random()*(11-8+1) + 8)::int)::text from 17)::cstring)
+            from concept
+            where uuid = '1073AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'MO', 1, now(), uuid_in(overlay(overlay(md5(random()::text || ':' || clock_timestamp()::text) placing '4' from 13) placing to_hex(floor(random()*(11-8+1) + 8)::int)::text from 17)::cstring)
+            from concept
+            where uuid = '1074AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+
+		<sql><![CDATA[
+            insert into fhir_duration_unit_map (concept_id, unit_of_time, creator, date_created, uuid)
+            select concept_id, 'A', 1, now(), uuid_in(overlay(overlay(md5(random()::text || ':' || clock_timestamp()::text) placing '4' from 13) placing to_hex(floor(random()*(11-8+1) + 8)::int)::text from 17)::cstring)
+            from concept
+            where uuid = '1734AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+        ]]>
+		</sql>
+	</changeSet>
 </databaseChangeLog>
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImplTest.java
new file mode 100644
index 000000000..5dc621f3b
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImplTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.hibernate.SessionFactory;
+import org.hl7.fhir.r4.model.Timing;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.openmrs.Concept;
+import org.openmrs.module.fhir2.TestFhirSpringConfiguration;
+import org.openmrs.module.fhir2.api.mappings.DurationUnitMap;
+import org.openmrs.test.BaseModuleContextSensitiveTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+@ContextConfiguration(classes = TestFhirSpringConfiguration.class, inheritLocations = false)
+public class DurationUnitTranslatorImplTest extends BaseModuleContextSensitiveTest {
+	
+	private static final String DURATION_UNIT_CONCEPT_DATA = "org/openmrs/module/fhir2/mapping/FhirDurationUnitTranslatorTest_initial_data.xml";
+	
+	private static final String SECONDS_UUID = "162583AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String MINUTES_UUID = "1733AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String HOUR_UUID = "1822AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String DAYS_UUID = "1072AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String WEEKS_UUID = "1073AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String MONTHS_UUID = "1074AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String YEARS_UUID = "1734AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String WRONG_UUID = "2909AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	@Mock
+	private DurationUnitMap durationUnitMap;
+	
+	@Autowired
+	private SessionFactory sessionFactory;
+	
+	private Concept concept;
+	
+	private Timing.UnitsOfTime result;
+	
+	private DurationUnitTranslatorImpl durationUnitTranslator;
+	
+	@Before
+	public void setup() throws Exception {
+		durationUnitTranslator = new DurationUnitTranslatorImpl();
+		durationUnitMap = new DurationUnitMap();
+		concept = new Concept();
+		durationUnitTranslator.setDurationUnitMap(durationUnitMap);
+		durationUnitMap.setSessionFactory(sessionFactory);
+		
+		executeDataSet(DURATION_UNIT_CONCEPT_DATA);
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateDrugOrderToUnitTimeIsNull() {
+		concept.setUuid(WRONG_UUID);
+		
+		result = durationUnitTranslator.toFhirResource(concept);
+		
+		assertThat(result, notNullValue());
+		assertThat(result, equalTo(Timing.UnitsOfTime.NULL));
+		
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateDrugOrderToUnitsOfTimeIsSeconds() {
+		concept.setUuid(SECONDS_UUID);
+		
+		result = durationUnitTranslator.toFhirResource(concept);
+		
+		assertThat(result, notNullValue());
+		assertThat(result, equalTo(Timing.UnitsOfTime.S));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateDrugOrderToUnitsOfTimeIsMinutes() {
+		concept.setUuid(MINUTES_UUID);
+		
+		result = durationUnitTranslator.toFhirResource(concept);
+		
+		assertThat(result, notNullValue());
+		assertThat(result, equalTo(Timing.UnitsOfTime.MIN));
+		
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateDrugOrderToUnitsOfTimeIsHours() {
+		concept.setUuid(HOUR_UUID);
+		
+		result = durationUnitTranslator.toFhirResource(concept);
+		
+		assertThat(result, notNullValue());
+		assertThat(result, equalTo(Timing.UnitsOfTime.H));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateDrugOrderToUnitsOfTimeIsDays() {
+		concept.setUuid(DAYS_UUID);
+		
+		result = durationUnitTranslator.toFhirResource(concept);
+		
+		assertThat(result, notNullValue());
+		assertThat(result, equalTo(Timing.UnitsOfTime.D));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateDrugOrderToUnitsOfTimeIsWeeks() {
+		concept.setUuid(WEEKS_UUID);
+		
+		result = durationUnitTranslator.toFhirResource(concept);
+		
+		assertThat(result, notNullValue());
+		assertThat(result, equalTo(Timing.UnitsOfTime.WK));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateDrugOrderToUnitsOfTimeIsMonths() {
+		concept.setUuid(MONTHS_UUID);
+		
+		result = durationUnitTranslator.toFhirResource(concept);
+		
+		assertThat(result, notNullValue());
+		assertThat(result, equalTo(Timing.UnitsOfTime.MO));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateDrugOrderToUnitsOfTimeIsYears() {
+		concept.setUuid(YEARS_UUID);
+		
+		result = durationUnitTranslator.toFhirResource(concept);
+		
+		assertThat(result, notNullValue());
+		assertThat(result, equalTo(Timing.UnitsOfTime.A));
+	}
+}
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/mapping/FhirDurationUnitTranslatorTest_initial_data.xml b/test-data/src/test/resources/org/openmrs/module/fhir2/mapping/FhirDurationUnitTranslatorTest_initial_data.xml
new file mode 100644
index 000000000..a5d7d4e34
--- /dev/null
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/mapping/FhirDurationUnitTranslatorTest_initial_data.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<dataset>
+	<concept concept_id = "162583" retired= "0" datatype_id= "4" class_id= "11" is_set= "0" creator= "1" date_created= "2014-09-18 15:14:53" changed_by= "1" date_changed= "2016-01-27 01:41:48" retired_by= "1" uuid= "162583AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" />
+	<concept concept_id = "1733" retired= "0" datatype_id= "4" class_id= "11" is_set= "0" creator= "1" date_created= "2009-06-24 16:57:06" changed_by= "1" date_changed= "2014-12-01 21:22:02" retired_by= "1" uuid= "1733AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" />
+	<concept concept_id = "1822" retired= "0" datatype_id= "4" class_id= "11" is_set= "0" creator= "1" date_created= "2009-07-06 17:27:04" changed_by= "1" date_changed= "2014-12-01 21:22:19" retired_by= "1" uuid= "1822AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" />
+	<concept concept_id = "1072" retired= "0" datatype_id= "4" class_id= "11" is_set= "0" creator= "1" date_created= "2005-01-06 00:00:00" changed_by= "1" date_changed= "2016-04-07 04:19:51" retired_by= "1" uuid= "1072AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" />
+	<concept concept_id = "1073" retired= "0" datatype_id= "4" class_id= "11" is_set= "0" creator= "1" date_created= "2005-01-06 00:00:00" changed_by= "1" date_changed= "2016-04-07 04:19:51" retired_by= "1" uuid= "1073AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" />
+	<concept concept_id = "1074" retired= "0" datatype_id= "4" class_id= "11" is_set= "0" creator= "1" date_created= "2005-01-06 00:00:00" changed_by= "1" date_changed= "2016-04-07 04:18:42" retired_by= "1" uuid= "1074AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" />
+	<concept concept_id = "1734" retired= "0" datatype_id= "4" class_id= "11" is_set= "0" creator= "1" date_created= "2009-06-24 16:57:26" changed_by= "1" date_changed= "2016-07-21 02:13:50" retired_by= "1" uuid= "1734AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" />
+
+	<fhir_duration_unit_map duration_unit_map_id= "1" concept_id= "162583" unit_of_time= "S" creator= "1" date_created= "2020-12-18 00:00:00" retired= "0" uuid= "dd12f704-411a-11eb-81d3-84fdd1f54722" />
+	<fhir_duration_unit_map duration_unit_map_id= "2" concept_id= "1733" unit_of_time= "MIN" creator= "1" date_created= "2020-12-18 00:00:00" retired= "0" uuid= "dd130d5e-411a-11eb-81d3-84fdd1f54722" />
+	<fhir_duration_unit_map duration_unit_map_id= "3" concept_id= "1822" unit_of_time= "H" creator= "1" date_created= "2020-12-18 00:00:00" retired= "0" uuid= "dd131c04-411a-11eb-81d3-84fdd1f54722" />
+	<fhir_duration_unit_map duration_unit_map_id= "4" concept_id= "1072" unit_of_time= "D" creator= "1" date_created= "2020-12-18 00:00:00" retired= "0" uuid= "dd137515-411a-11eb-81d3-84fdd1f54722" />
+	<fhir_duration_unit_map duration_unit_map_id= "5" concept_id= "1073" unit_of_time= "WK" creator= "1" date_created= "2020-12-18 00:00:00" retired= "0" uuid= "dd137eb6-411a-11eb-81d3-84fdd1f54722" />
+	<fhir_duration_unit_map duration_unit_map_id= "6" concept_id= "1074" unit_of_time= "MO" creator= "1" date_created= "2020-12-18 00:00:00" retired= "0" uuid= "dd138741-411a-11eb-81d3-84fdd1f54722" />
+	<fhir_duration_unit_map duration_unit_map_id= "7" concept_id= "1734" unit_of_time= "A" creator= "1" date_created= "2020-12-18 00:00:00" retired= "0" uuid= "dd138fa6-411a-11eb-81d3-84fdd1f54722" />
+</dataset>

From 4a4d7a058873d14b1f51974d3847051fd73fec37 Mon Sep 17 00:00:00 2001
From: Ankit kumar <49350053+theanandankit@users.noreply.github.com>
Date: Mon, 1 Feb 2021 20:20:14 +0530
Subject: [PATCH 09/18] FM2-326: Add Tests for
 ObservationCategoryTranslatorImpl (#313)

---
 .../api/mappings/ObservationCategoryMap.java  |   3 +
 .../ObservationCategoryTranslatorImpl.java    |   3 +
 ...ObservationCategoryTranslatorImplTest.java | 110 ++++++++++++++++++
 ...irObservationCategoryTest_initial_data.xml |  10 ++
 4 files changed, 126 insertions(+)
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ObservationCategoryTranslatorImplTest.java
 create mode 100644 test-data/src/test/resources/org/openmrs/module/fhir2/mapping/FhirObservationCategoryTest_initial_data.xml

diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/mappings/ObservationCategoryMap.java b/api/src/main/java/org/openmrs/module/fhir2/api/mappings/ObservationCategoryMap.java
index f30a48089..3ed01f176 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/mappings/ObservationCategoryMap.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/mappings/ObservationCategoryMap.java
@@ -13,6 +13,8 @@
 
 import javax.annotation.Nonnull;
 
+import lombok.AccessLevel;
+import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.hibernate.HibernateException;
 import org.hibernate.SessionFactory;
@@ -24,6 +26,7 @@
 
 @Component
 @Slf4j
+@Setter(AccessLevel.PUBLIC)
 public class ObservationCategoryMap {
 	
 	@Autowired
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ObservationCategoryTranslatorImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ObservationCategoryTranslatorImpl.java
index 4f6462233..0cc55c823 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ObservationCategoryTranslatorImpl.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/ObservationCategoryTranslatorImpl.java
@@ -11,6 +11,8 @@
 
 import javax.annotation.Nonnull;
 
+import lombok.AccessLevel;
+import lombok.Setter;
 import org.apache.commons.lang3.StringUtils;
 import org.hl7.fhir.r4.model.CodeableConcept;
 import org.openmrs.Concept;
@@ -21,6 +23,7 @@
 import org.springframework.stereotype.Component;
 
 @Component
+@Setter(AccessLevel.PACKAGE)
 public class ObservationCategoryTranslatorImpl implements ObservationCategoryTranslator {
 	
 	@Autowired
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ObservationCategoryTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ObservationCategoryTranslatorImplTest.java
new file mode 100644
index 000000000..60d7f5d82
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ObservationCategoryTranslatorImplTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.hibernate.SessionFactory;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.openmrs.Concept;
+import org.openmrs.ConceptClass;
+import org.openmrs.module.fhir2.TestFhirSpringConfiguration;
+import org.openmrs.module.fhir2.api.mappings.ObservationCategoryMap;
+import org.openmrs.test.BaseModuleContextSensitiveTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+@ContextConfiguration(classes = TestFhirSpringConfiguration.class, inheritLocations = false)
+public class ObservationCategoryTranslatorImplTest extends BaseModuleContextSensitiveTest {
+	
+	private final String OBSERVATION_CATEGORY_CONCEPT_CLASS_DATA = "org/openmrs/module/fhir2/mapping/FhirObservationCategoryTest_initial_data.xml";
+	
+	private final String LABORATORY_CONCEPT_CLASS_UUID = "8d4907b2-c2cc-11de-8d13-0010c6dffd0f";
+	
+	private final String PROCEDURE_CONCEPT_CLASS_UUID = "8d490bf4-c2cc-11de-8d13-0010c6dffd0f";
+	
+	private final String EXAM_CONCEPT_CLASS_UUID = "8d491a9a-c2cc-11de-8d13-0010c6dffd0f";
+	
+	@Mock
+	private ObservationCategoryMap categoryMap;
+	
+	@Autowired
+	private SessionFactory sessionFactory;
+	
+	private Concept concept;
+	
+	private CodeableConcept codeableConcept;
+	
+	private ObservationCategoryTranslatorImpl observationCategoryTranslator;
+	
+	@Before
+	public void setup() throws Exception {
+		observationCategoryTranslator = new ObservationCategoryTranslatorImpl();
+		categoryMap = new ObservationCategoryMap();
+		concept = new Concept();
+		categoryMap.setSessionFactory(sessionFactory);
+		observationCategoryTranslator.setCategoryMap(categoryMap);
+		
+		executeDataSet(OBSERVATION_CATEGORY_CONCEPT_CLASS_DATA);
+	}
+	
+	@Test
+	public void shouldTranslateConceptClassToCodeableConceptIsnull() {
+		ConceptClass conceptClass = new ConceptClass();
+		// wrong uuid which is not in dataSet
+		conceptClass.setUuid("0");
+		concept.setConceptClass(conceptClass);
+		
+		codeableConcept = observationCategoryTranslator.toFhirResource(concept);
+		
+		assertThat(codeableConcept, equalTo(null));
+	}
+	
+	@Test
+	public void shouldTranslateConceptClassToCodeableConceptIsLaboratory() {
+		ConceptClass conceptClass = new ConceptClass();
+		conceptClass.setUuid(LABORATORY_CONCEPT_CLASS_UUID);
+		concept.setConceptClass(conceptClass);
+		
+		codeableConcept = observationCategoryTranslator.toFhirResource(concept);
+		
+		assertThat(codeableConcept, notNullValue());
+		assertThat(codeableConcept.getCoding().get(0).getDisplay(), equalTo("Laboratory"));
+	}
+	
+	@Test
+	public void shouldTranslateConceptClassToCodeableConceptIsProcedure() {
+		ConceptClass conceptClass = new ConceptClass();
+		conceptClass.setUuid(PROCEDURE_CONCEPT_CLASS_UUID);
+		concept.setConceptClass(conceptClass);
+		
+		codeableConcept = observationCategoryTranslator.toFhirResource(concept);
+		
+		assertThat(codeableConcept, notNullValue());
+		assertThat(codeableConcept.getCoding().get(0).getDisplay(), equalTo("Procedure"));
+	}
+	
+	@Test
+	public void shouldTranslateConceptClassToCodeableConceptIsExam() {
+		ConceptClass conceptClass = new ConceptClass();
+		conceptClass.setUuid(EXAM_CONCEPT_CLASS_UUID);
+		concept.setConceptClass(conceptClass);
+		
+		codeableConcept = observationCategoryTranslator.toFhirResource(concept);
+		
+		assertThat(codeableConcept, notNullValue());
+		assertThat(codeableConcept.getCoding().get(0).getDisplay(), equalTo("Exam"));
+	}
+}
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/mapping/FhirObservationCategoryTest_initial_data.xml b/test-data/src/test/resources/org/openmrs/module/fhir2/mapping/FhirObservationCategoryTest_initial_data.xml
new file mode 100644
index 000000000..d1f7f9c38
--- /dev/null
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/mapping/FhirObservationCategoryTest_initial_data.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<dataset>
+	<fhir_observation_category_map observation_category_map_id="1" concept_class_id="1" observation_category="laboratory" creator="1" date_created="2020-12-16 19:20:53" retired="0" uuid="b6b107d8-3fa5-11eb-81d3-84fdd1f54722"/>
+	<fhir_observation_category_map observation_category_map_id="2" concept_class_id="2" observation_category="procedure" creator="1" date_created="2020-12-16 19:20:53" retired="0" uuid="b6b13299-3fa5-11eb-81d3-84fdd1f54722"/>
+	<fhir_observation_category_map observation_category_map_id="3" concept_class_id="5" observation_category="exam" creator="1" date_created="2020-12-16 19:20:53" retired="0" uuid="b6b16756-3fa5-11eb-81d3-84fdd1f54722"/>
+	<concept_class concept_class_id="1" name="Test" description="Acq. during patient encounter (vitals, labs, etc.)" creator="1" date_created="2004-02-02 00:00:00" retired="0" uuid="8d4907b2-c2cc-11de-8d13-0010c6dffd0f"/>
+	<concept_class concept_class_id="2" name="Procedure" description="Describes a clinical procedure" creator="1" date_created="2004-02-02 00:00:00" retired="0" uuid="8d490bf4-c2cc-11de-8d13-0010c6dffd0f"/>
+	<concept_class concept_class_id="5" name="Finding" description="Practitioner observation/finding" creator="1" date_created="2004-02-02 00:00:00" retired="0" uuid="8d491a9a-c2cc-11de-8d13-0010c6dffd0f"/>
+</dataset>

From a39cba64f816fc67e47dc3c617b2109245e3c11b Mon Sep 17 00:00:00 2001
From: Ankit kumar <49350053+theanandankit@users.noreply.github.com>
Date: Thu, 4 Feb 2021 11:29:49 +0530
Subject: [PATCH 10/18] FM2-340: Add Support of DurationUnit for the
 MedicationRequestTimingComponent (#322)

* Add Support of DurationUnit for the TimingRepeatComponent

* Small change in MedicationRequestTimingComponentTranslatorImpl

* Improve integration test of MedicationRequestFhirResourceProvider

* Small change in Test Data
---
 .../impl/DurationUnitTranslatorImpl.java      |   2 +
 ...nRequestTimingComponentTranslatorImpl.java |  21 ++-
 ...uestTimingComponentTranslatorImplTest.java | 122 ++++++++++++++++++
 ...rMedicationRequestDaoImpl_initial_data.xml |  23 +++-
 4 files changed, 156 insertions(+), 12 deletions(-)

diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImpl.java
index 530cd7d81..39ac9e5cb 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImpl.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/DurationUnitTranslatorImpl.java
@@ -19,7 +19,9 @@
 import org.openmrs.module.fhir2.api.mappings.DurationUnitMap;
 import org.openmrs.module.fhir2.api.translators.DurationUnitTranslator;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
 
+@Component
 @Setter(AccessLevel.PACKAGE)
 public class DurationUnitTranslatorImpl implements DurationUnitTranslator {
 	
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTimingComponentTranslatorImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTimingComponentTranslatorImpl.java
index 1049d25ca..64ac0fe1e 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTimingComponentTranslatorImpl.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTimingComponentTranslatorImpl.java
@@ -11,29 +11,36 @@
 
 import javax.annotation.Nonnull;
 
+import lombok.AccessLevel;
+import lombok.Setter;
 import org.hl7.fhir.r4.model.Timing;
 import org.openmrs.DrugOrder;
 import org.openmrs.OrderFrequency;
+import org.openmrs.module.fhir2.api.translators.DurationUnitTranslator;
 import org.openmrs.module.fhir2.api.translators.MedicationRequestTimingComponentTranslator;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
+@Setter(AccessLevel.PACKAGE)
 public class MedicationRequestTimingComponentTranslatorImpl implements MedicationRequestTimingComponentTranslator {
 	
+	@Autowired
+	private DurationUnitTranslator durationUnitTranslator;
+	
 	@Override
 	public Timing.TimingRepeatComponent toFhirResource(@Nonnull DrugOrder drugOrder) {
 		if (drugOrder == null) {
 			return null;
 		}
 		Timing.TimingRepeatComponent repeatComponent = new Timing.TimingRepeatComponent();
-		if (drugOrder.getDuration() != null)
+		if (drugOrder.getDuration() != null) {
 			repeatComponent.setDuration(drugOrder.getDuration());
-		/*
-		 * TODO
-		 * Figure out how to map DurationUnit to UnitsOfTime since openMrs duration units is concept
-		 * which differs across implementation. Make use of concept mappings
-		 */
-		//repeatComponent.setDurationUnit(drugOrder.getDurationUnits());
+		}
+		
+		if (drugOrder.getDurationUnits() != null) {
+			repeatComponent.setDurationUnit(durationUnitTranslator.toFhirResource(drugOrder.getDurationUnits()));
+		}
 		OrderFrequency frequency = drugOrder.getFrequency();
 		if (frequency != null) {
 			if (frequency.getFrequencyPerDay() != null) {
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTimingComponentTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTimingComponentTranslatorImplTest.java
index 5a9d28855..c4937ae2e 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTimingComponentTranslatorImplTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/MedicationRequestTimingComponentTranslatorImplTest.java
@@ -13,6 +13,7 @@
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
+import static org.mockito.Mockito.when;
 
 import java.math.BigDecimal;
 
@@ -20,13 +21,35 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.Concept;
 import org.openmrs.DrugOrder;
 import org.openmrs.OrderFrequency;
+import org.openmrs.module.fhir2.api.translators.DurationUnitTranslator;
 
 @RunWith(MockitoJUnitRunner.class)
 public class MedicationRequestTimingComponentTranslatorImplTest {
 	
+	private static final String SECONDS_UUID = "162583AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String MINUTES_UUID = "1733AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String HOUR_UUID = "1822AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String DAYS_UUID = "1072AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String WEEKS_UUID = "1073AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String MONTHS_UUID = "1074AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String YEARS_UUID = "1734AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	private static final String WRONG_UUID = "2909AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+	
+	@Mock
+	private DurationUnitTranslator durationUnitTranslator;
+	
 	private static final int DURATION = 2;
 	
 	private static final double FREQUENCY_PER_DAY = 3.0;
@@ -35,10 +58,14 @@ public class MedicationRequestTimingComponentTranslatorImplTest {
 	
 	private DrugOrder drugOrder;
 	
+	private Concept concept;
+	
 	@Before
 	public void setup() {
 		requestTimingComponentTranslator = new MedicationRequestTimingComponentTranslatorImpl();
+		requestTimingComponentTranslator.setDurationUnitTranslator(durationUnitTranslator);
 		
+		concept = new Concept();
 		drugOrder = new DrugOrder();
 		drugOrder.setDuration(DURATION);
 		
@@ -103,4 +130,99 @@ public void toFhirResource_shouldReturnNullIfDrugOrderIsNull() {
 		assertThat(requestTimingComponentTranslator.toFhirResource(null), nullValue());
 	}
 	
+	@Test
+	public void toFhirResource_shouldTranslateConceptToUnitOfTimeSeconds() {
+		concept.setUuid(SECONDS_UUID);
+		drugOrder.setDurationUnits(concept);
+		
+		when(durationUnitTranslator.toFhirResource(concept)).thenReturn(Timing.UnitsOfTime.S);
+		Timing.TimingRepeatComponent result = requestTimingComponentTranslator.toFhirResource(drugOrder);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getDurationUnit(), equalTo(Timing.UnitsOfTime.S));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateConceptToUnitOfTimeMinutes() {
+		concept.setUuid(MINUTES_UUID);
+		drugOrder.setDurationUnits(concept);
+		
+		when(durationUnitTranslator.toFhirResource(concept)).thenReturn(Timing.UnitsOfTime.MIN);
+		Timing.TimingRepeatComponent result = requestTimingComponentTranslator.toFhirResource(drugOrder);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getDurationUnit(), equalTo(Timing.UnitsOfTime.MIN));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateConceptToUnitOfTimeHours() {
+		concept.setUuid(HOUR_UUID);
+		drugOrder.setDurationUnits(concept);
+		
+		when(durationUnitTranslator.toFhirResource(concept)).thenReturn(Timing.UnitsOfTime.H);
+		Timing.TimingRepeatComponent result = requestTimingComponentTranslator.toFhirResource(drugOrder);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getDurationUnit(), equalTo(Timing.UnitsOfTime.H));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateConceptToUnitOfTimeDays() {
+		concept.setUuid(DAYS_UUID);
+		drugOrder.setDurationUnits(concept);
+		
+		when(durationUnitTranslator.toFhirResource(concept)).thenReturn(Timing.UnitsOfTime.D);
+		Timing.TimingRepeatComponent result = requestTimingComponentTranslator.toFhirResource(drugOrder);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getDurationUnit(), equalTo(Timing.UnitsOfTime.D));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateConceptToUnitOfTimeWeeks() {
+		concept.setUuid(WEEKS_UUID);
+		drugOrder.setDurationUnits(concept);
+		
+		when(durationUnitTranslator.toFhirResource(concept)).thenReturn(Timing.UnitsOfTime.WK);
+		Timing.TimingRepeatComponent result = requestTimingComponentTranslator.toFhirResource(drugOrder);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getDurationUnit(), equalTo(Timing.UnitsOfTime.WK));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateConceptToUnitOfTimeMonths() {
+		concept.setUuid(MONTHS_UUID);
+		drugOrder.setDurationUnits(concept);
+		
+		when(durationUnitTranslator.toFhirResource(concept)).thenReturn(Timing.UnitsOfTime.MO);
+		Timing.TimingRepeatComponent result = requestTimingComponentTranslator.toFhirResource(drugOrder);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getDurationUnit(), equalTo(Timing.UnitsOfTime.MO));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateConceptToUnitOfTimeYears() {
+		concept.setUuid(YEARS_UUID);
+		drugOrder.setDurationUnits(concept);
+		
+		when(durationUnitTranslator.toFhirResource(concept)).thenReturn(Timing.UnitsOfTime.A);
+		Timing.TimingRepeatComponent result = requestTimingComponentTranslator.toFhirResource(drugOrder);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getDurationUnit(), equalTo(Timing.UnitsOfTime.A));
+	}
+	
+	@Test
+	public void toFhirResource_shouldTranslateConceptToUnitOfTimeSecondNull() {
+		concept.setUuid(WRONG_UUID);
+		drugOrder.setDurationUnits(concept);
+		
+		when(durationUnitTranslator.toFhirResource(concept)).thenReturn(Timing.UnitsOfTime.NULL);
+		Timing.TimingRepeatComponent result = requestTimingComponentTranslator.toFhirResource(drugOrder);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getDurationUnit(), equalTo(Timing.UnitsOfTime.NULL));
+	}
 }
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationRequestDaoImpl_initial_data.xml b/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationRequestDaoImpl_initial_data.xml
index bd28672de..52146e9c4 100644
--- a/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationRequestDaoImpl_initial_data.xml
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationRequestDaoImpl_initial_data.xml
@@ -14,7 +14,7 @@
     <fhir_concept_source fhir_concept_source_id="1" name="SNOMED CT" url="http://snomed.info/sct" concept_source_id="2" creator="1" date_created="2005-01-01 00:00:00.0" retired="0" uuid="0d30bea3-4ba2-4ab4-ac7b-5525840bde20" />
     <fhir_concept_source fhir_concept_source_id="2" name="CIEL" url="https://openconceptlab.org/orgs/CIEL/sources/CIEL" concept_source_id="21" creator="1" date_created="2005-01-01 00:00:00.0" retired="0" uuid="b824bbee-d5aa-4ede-b538-f8b0107a74e4" />
     <fhir_concept_source fhir_concept_source_id="3" name="LOINC" url="http://loinc.org" concept_source_id="6" creator="1" date_created="2005-01-01 00:00:00.0" retired="0" uuid="30a5aa84-2df5-46da-aed7-451bafe5593b" />
-   
+
     <!--Patient Information -->
     <person person_id="102" gender="M" dead="false" creator="1" birthdate_estimated="0" date_created="2008-08-15 15:57:09.0"
             voided="false" uuid="86526ed5-3c11-11de-a0ba-001e3766667a"/>
@@ -38,6 +38,12 @@
     <concept concept_id="4022" retired="false" datatype_id="4" class_id="11" is_set="false" creator="501"
              date_created="2004-08-12 00:00:00.0" version="" changed_by="1" date_changed="2005-02-25 11:43:43.0"
              uuid="c902c80f-1yz9-4da3-bb88-8122ce889111"/>
+    <concept concept_id="162583" retired="0" datatype_id="4" class_id="11" is_set="0" creator="1"
+             date_created="2014-09-18 15:14:53" changed_by="1" date_changed="2016-01-27 01:41:48" retired_by="1"
+             uuid="162583AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"/>
+    <concept concept_id="1733" retired="0" datatype_id="4" class_id="11" is_set="0" creator="1"
+             date_created="2009-06-24 16:57:06" changed_by="1" date_changed="2014-12-01 21:22:02" retired_by="1"
+             uuid="1733AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"/>
 
     <concept_name concept_id="4020" concept_name_id="1420" name="Bedaquiline" locale="en" creator="1"
                   date_created="2004-08-12 00:00:00.0" concept_name_type="FULLY_SPECIFIED" locale_preferred="1"
@@ -80,15 +86,22 @@
     <drug drug_id="2003" concept_id="4022" dosage_form="51" name="Rifampicin" combination="true" creator="1"
           date_created="2005-02-24 00:00:00.0" retired="false" uuid="yuf00b94-26fe-102b-80bc-0017a4787b12"/>
 
-    <drug_order order_id="1005" drug_inventory_id="2001" duration="6" duration_units="27" dispense_as_written="true"
+    <drug_order order_id="1005" drug_inventory_id="2001" duration="6" duration_units="162583" dispense_as_written="true"
                 as_needed="false"
                 frequency="2"/>
-    <drug_order order_id="1006" drug_inventory_id="2002" duration="6" duration_units="27" dispense_as_written="true"
+    <drug_order order_id="1006" drug_inventory_id="2002" duration="6" duration_units="162583" dispense_as_written="true"
                 as_needed="false"
                 frequency="1"/>
-    <drug_order order_id="1007" drug_inventory_id="2003" duration="6" duration_units="27" dispense_as_written="true"
+    <drug_order order_id="1007" drug_inventory_id="2003" duration="6" duration_units="162583" dispense_as_written="true"
                 as_needed="false"
                 frequency="2"/>
-    <drug_order order_id="8" drug_inventory_id="2003" duration="8" duration_units="29" dispense_as_written="true"
+    <drug_order order_id="8" drug_inventory_id="2003" duration="8" duration_units="1733" dispense_as_written="true"
                 as_needed="false" frequency="3"/>
+
+    <fhir_duration_unit_map duration_unit_map_id="1" concept_id="162583" unit_of_time="S" creator="1"
+                            date_created="2020-12-18 00:00:00" retired="0" uuid="dd12f704-411a-11eb-81d3-84fdd1f54722"/>
+
+    <fhir_duration_unit_map duration_unit_map_id="2" concept_id="1733" unit_of_time="MIN" creator="1"
+                            date_created="2020-12-18 00:00:00" retired="0" uuid="dd130d5e-411a-11eb-81d3-84fdd1f54722"/>
+
 </dataset>

From 3a9194218d120f6c44d8709186e05109eb948e5b Mon Sep 17 00:00:00 2001
From: Ian <ian_bacher@brown.edu>
Date: Fri, 5 Feb 2021 10:36:13 -0500
Subject: [PATCH 11/18] Mark FHIR2 aware of some basic configuration modules

---
 omod/src/main/resources/config.xml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/omod/src/main/resources/config.xml b/omod/src/main/resources/config.xml
index 077dacbc1..5136336fd 100644
--- a/omod/src/main/resources/config.xml
+++ b/omod/src/main/resources/config.xml
@@ -47,6 +47,8 @@
 
 	<aware_of_modules>
 		<aware_of_module>org.openmrs.module.legacyui</aware_of_module>
+		<aware_of_module>org.openmrs.module.referencemetadata</aware_of_module>
+		<aware_of_module>org.openmrs.module.initializer</aware_of_module>
 	</aware_of_modules>
 
 	<servlet>

From f49d99f5b051719fbfc5f372829798ca9089637b Mon Sep 17 00:00:00 2001
From: "Kipchumba C. Bett" <kbett68@gmail.com>
Date: Fri, 5 Feb 2021 20:33:09 +0300
Subject: [PATCH 12/18] FM2-337: Implement Group resource (#319)

---
 .../impl/GroupMemberTranslatorImpl_2_1.java   |  91 ++++
 .../impl/GroupTranslatorImpl_2_1.java         |  83 ++++
 .../GroupMemberTranslatorImpl_2_1Test.java    | 243 +++++++++++
 .../impl/GroupTranslatorImpl_2_1Test.java     | 199 +++++++++
 .../openmrs/module/fhir2/FhirConstants.java   |   2 +
 .../module/fhir2/api/FhirGroupService.java    |  14 +
 .../module/fhir2/api/FhirPatientService.java  |   6 +
 .../module/fhir2/api/dao/FhirGroupDao.java    |  39 ++
 .../module/fhir2/api/dao/FhirPatientDao.java  |   4 +
 .../fhir2/api/dao/impl/BaseFhirDao.java       |   2 +-
 .../fhir2/api/dao/impl/FhirGroupDaoImpl.java  |  22 +
 .../api/dao/impl/FhirPatientDaoImpl.java      |   7 +
 .../fhir2/api/impl/FhirGroupServiceImpl.java  |  35 ++
 .../api/impl/FhirPatientServiceImpl.java      |  16 +
 .../translators/GroupMemberTranslator.java    |  45 ++
 .../api/translators/GroupTranslator.java      |  46 ++
 .../translators/impl/BaseGroupTranslator.java |  70 +++
 .../impl/GroupMemberTranslatorImpl.java       |  60 +++
 .../translators/impl/GroupTranslatorImpl.java |  70 +++
 .../r3/GroupFhirResourceProvider.java         |  87 ++++
 .../r4/GroupFhirResourceProvider.java         |  85 ++++
 .../api/dao/impl/FhirGroupDaoImplTest.java    | 106 +++++
 .../api/impl/FhirGroupServiceImplTest.java    | 158 +++++++
 .../api/impl/FhirPatientServiceImplTest.java  |  27 ++
 .../impl/GroupMemberTranslatorImplTest.java   |  94 ++++
 .../impl/GroupTranslatorImplTest.java         | 200 +++++++++
 .../r3/GroupFhirResourceProviderTest.java     | 166 +++++++
 .../r4/GroupFhirResourceProviderTest.java     | 163 +++++++
 ...upFhirResourceProviderIntegrationTest.java | 390 +++++++++++++++++
 ...upFhirResourceProviderIntegrationTest.java | 404 ++++++++++++++++++
 .../FhirCohortDaoImplTest_initial_data.xml    |  14 +
 .../fhir2/providers/GroupWebTest_create.json  |  27 ++
 .../fhir2/providers/GroupWebTest_create.xml   |  29 ++
 .../fhir2/providers/GroupWebTest_update.json  |  28 ++
 .../fhir2/providers/GroupWebTest_update.xml   |  30 ++
 35 files changed, 3061 insertions(+), 1 deletion(-)
 create mode 100644 api-2.1/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl_2_1.java
 create mode 100644 api-2.1/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl_2_1.java
 create mode 100644 api-2.1/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl_2_1Test.java
 create mode 100644 api-2.1/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl_2_1Test.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/FhirGroupService.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/dao/FhirGroupDao.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirGroupDaoImpl.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirGroupServiceImpl.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/translators/GroupMemberTranslator.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/translators/GroupTranslator.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/BaseGroupTranslator.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProvider.java
 create mode 100644 api/src/main/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProvider.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirGroupDaoImplTest.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirGroupServiceImplTest.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImplTest.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImplTest.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProviderTest.java
 create mode 100644 api/src/test/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProviderTest.java
 create mode 100644 integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProviderIntegrationTest.java
 create mode 100644 integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProviderIntegrationTest.java
 create mode 100644 test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirCohortDaoImplTest_initial_data.xml
 create mode 100644 test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_create.json
 create mode 100644 test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_create.xml
 create mode 100644 test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_update.json
 create mode 100644 test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_update.xml

diff --git a/api-2.1/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl_2_1.java b/api-2.1/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl_2_1.java
new file mode 100644
index 000000000..56c85bc76
--- /dev/null
+++ b/api-2.1/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl_2_1.java
@@ -0,0 +1,91 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.apache.commons.lang3.Validate.notNull;
+
+import javax.annotation.Nonnull;
+
+import lombok.AccessLevel;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.hl7.fhir.r4.model.Group;
+import org.hl7.fhir.r4.model.Period;
+import org.openmrs.CohortMembership;
+import org.openmrs.Patient;
+import org.openmrs.annotation.OpenmrsProfile;
+import org.openmrs.module.fhir2.api.dao.FhirPatientDao;
+import org.openmrs.module.fhir2.api.translators.GroupMemberTranslator;
+import org.openmrs.module.fhir2.api.translators.PatientReferenceTranslator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@Setter(AccessLevel.MODULE)
+@OpenmrsProfile(openmrsPlatformVersion = "2.1.* - 2.*")
+public class GroupMemberTranslatorImpl_2_1 implements GroupMemberTranslator<CohortMembership> {
+	
+	@Autowired
+	private PatientReferenceTranslator patientReferenceTranslator;
+	
+	@Autowired
+	private FhirPatientDao patientDao;
+	
+	@Override
+	public Group.GroupMemberComponent toFhirResource(@Nonnull CohortMembership cohortMember) {
+		notNull(cohortMember, "CohortMember object should not be null");
+		Group.GroupMemberComponent groupMemberComponent = new Group.GroupMemberComponent();
+		groupMemberComponent.setId(cohortMember.getUuid());
+		groupMemberComponent.setInactive(!cohortMember.isActive());
+		
+		Patient patient = patientDao.getPatientById(cohortMember.getPatientId());
+		if (patient != null) {
+			groupMemberComponent.setEntity(patientReferenceTranslator.toFhirResource(patient));
+		}
+		
+		Period period = new Period();
+		period.setStart(cohortMember.getStartDate());
+		period.setEnd(cohortMember.getEndDate());
+		groupMemberComponent.setPeriod(period);
+		
+		return groupMemberComponent;
+	}
+	
+	@Override
+	public CohortMembership toOpenmrsType(@Nonnull Group.GroupMemberComponent groupMemberComponent) {
+		notNull(groupMemberComponent, "GroupMemberComponent object should not be null");
+		return toOpenmrsType(new CohortMembership(), groupMemberComponent);
+	}
+	
+	@Override
+	public CohortMembership toOpenmrsType(@Nonnull CohortMembership existingCohort,
+	        @Nonnull Group.GroupMemberComponent groupMemberComponent) {
+		notNull(groupMemberComponent, "GroupMemberComponent object should not be null");
+		notNull(existingCohort, "ExistingCohort object should not be null");
+		
+		if (groupMemberComponent.hasEntity()) {
+			existingCohort
+			        .setPatientId(patientReferenceTranslator.toOpenmrsType(groupMemberComponent.getEntity()).getPatientId());
+		}
+		
+		if (groupMemberComponent.hasPeriod()) {
+			existingCohort.setStartDate(groupMemberComponent.getPeriod().getStart());
+			existingCohort.setEndDate(groupMemberComponent.getPeriod().getEnd());
+		}
+		
+		if (groupMemberComponent.hasInactive()) {
+			existingCohort.setVoided(groupMemberComponent.getInactive());
+			existingCohort.setVoidReason("Voided via FHIR API");
+		}
+		
+		return existingCohort;
+	}
+}
diff --git a/api-2.1/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl_2_1.java b/api-2.1/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl_2_1.java
new file mode 100644
index 000000000..3b935ba7e
--- /dev/null
+++ b/api-2.1/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl_2_1.java
@@ -0,0 +1,83 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.apache.commons.lang3.Validate.notNull;
+
+import javax.annotation.Nonnull;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import lombok.AccessLevel;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.hl7.fhir.r4.model.Group;
+import org.openmrs.Cohort;
+import org.openmrs.CohortMembership;
+import org.openmrs.annotation.OpenmrsProfile;
+import org.openmrs.module.fhir2.api.translators.GroupMemberTranslator;
+import org.openmrs.module.fhir2.api.translators.GroupTranslator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Primary
+@Component
+@Setter(AccessLevel.MODULE)
+@OpenmrsProfile(openmrsPlatformVersion = "2.1.* - 2.*")
+public class GroupTranslatorImpl_2_1 extends BaseGroupTranslator implements GroupTranslator {
+	
+	@Autowired
+	private GroupMemberTranslator<CohortMembership> groupMemberTranslator;
+	
+	@Override
+	public Group toFhirResource(@Nonnull Cohort cohort) {
+		notNull(cohort, "Cohort object should not be null");
+		Group group = super.toFhirResource(cohort);
+		
+		Collection<CohortMembership> memberships = cohort.getMemberships();
+		log.info("Number of members {} ", memberships.size());
+		group.setQuantity(memberships.size());
+		memberships.forEach(membership -> group.addMember(groupMemberTranslator.toFhirResource(membership)));
+		
+		return group;
+	}
+	
+	@Override
+	public Cohort toOpenmrsType(@Nonnull Group group) {
+		notNull(group, "Group resource should not be null");
+		return toOpenmrsType(new Cohort(), group);
+	}
+	
+	@Override
+	public Cohort toOpenmrsType(@Nonnull Cohort existingCohort, @Nonnull Group group) {
+		notNull(group, "group resource object should not be null");
+		notNull(existingCohort, "ExistingCohort object should not be null");
+		
+		Cohort finalExistingCohort = super.toOpenmrsType(existingCohort, group);
+		
+		if (group.hasMember()) {
+			Set<CohortMembership> memberships = new HashSet<>();
+			group.getMember().forEach(member -> memberships.add(this.setCohort(existingCohort, member)));
+			existingCohort.setMemberships(memberships);
+		}
+		
+		return finalExistingCohort;
+	}
+	
+	private CohortMembership setCohort(Cohort cohort, Group.GroupMemberComponent groupMember) {
+		CohortMembership cohortMembership = groupMemberTranslator.toOpenmrsType(groupMember);
+		cohortMembership.setCohort(cohort);
+		return cohortMembership;
+	}
+}
diff --git a/api-2.1/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl_2_1Test.java b/api-2.1/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl_2_1Test.java
new file mode 100644
index 000000000..d601b01c1
--- /dev/null
+++ b/api-2.1/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl_2_1Test.java
@@ -0,0 +1,243 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.time.Instant;
+import java.util.Date;
+
+import org.exparity.hamcrest.date.DateMatchers;
+import org.hl7.fhir.r4.model.Group;
+import org.hl7.fhir.r4.model.Period;
+import org.hl7.fhir.r4.model.Reference;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.CohortMembership;
+import org.openmrs.Patient;
+import org.openmrs.module.fhir2.api.dao.FhirPatientDao;
+import org.openmrs.module.fhir2.api.translators.PatientReferenceTranslator;
+import org.openmrs.module.fhir2.api.translators.PatientTranslator;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GroupMemberTranslatorImpl_2_1Test {
+	
+	private static final String COHORT_UUID = "787e12bd-314e-4cc4-9b4d-1cdff9be9545";
+	
+	private static final String COHORT_NAME = " John's patientList";
+	
+	@Mock
+	private PatientReferenceTranslator patientReferenceTranslator;
+	
+	@Mock
+	private FhirPatientDao patientDao;
+	
+	@Mock
+	private PatientTranslator patientTranslator;
+	
+	private GroupMemberTranslatorImpl_2_1 groupMemberTranslator;
+	
+	@Before
+	public void setup() {
+		groupMemberTranslator = new GroupMemberTranslatorImpl_2_1();
+		groupMemberTranslator.setPatientDao(patientDao);
+		groupMemberTranslator.setPatientReferenceTranslator(patientReferenceTranslator);
+	}
+	
+	@Test
+	public void shouldTranslateCohortMemberUuidToFHIRType() {
+		CohortMembership cohortMembership = mock(CohortMembership.class);
+		Patient patient = mock(Patient.class);
+		Reference patientReference = mock(Reference.class);
+		
+		when(patientReferenceTranslator.toFhirResource(patient)).thenReturn(patientReference);
+		when(patientDao.getPatientById(anyInt())).thenReturn(patient);
+		when(cohortMembership.getUuid()).thenReturn(COHORT_UUID);
+		
+		Group.GroupMemberComponent component = groupMemberTranslator.toFhirResource(cohortMembership);
+		assertThat(component, notNullValue());
+		assertThat(component.hasId(), notNullValue());
+		assertThat(component.getId(), is(COHORT_UUID));
+	}
+	
+	@Test
+	public void shouldTranslateCohortMemberInactiveToFHIRType() {
+		CohortMembership cohortMembership = mock(CohortMembership.class);
+		Patient patient = mock(Patient.class);
+		Reference patientReference = mock(Reference.class);
+		
+		when(patientReferenceTranslator.toFhirResource(patient)).thenReturn(patientReference);
+		when(patientDao.getPatientById(anyInt())).thenReturn(patient);
+		when(cohortMembership.isActive()).thenReturn(true);
+		
+		Group.GroupMemberComponent component = groupMemberTranslator.toFhirResource(cohortMembership);
+		assertThat(component, notNullValue());
+		assertThat(component.hasInactive(), is(true));
+		assertThat(component.getInactive(), is(false));
+	}
+	
+	@Test
+	public void shouldTranslateCohortMemberStartDateToFHIRType() {
+		CohortMembership cohortMembership = mock(CohortMembership.class);
+		Patient patient = mock(Patient.class);
+		Reference patientReference = mock(Reference.class);
+		
+		when(patientReferenceTranslator.toFhirResource(patient)).thenReturn(patientReference);
+		when(patientDao.getPatientById(anyInt())).thenReturn(patient);
+		when(cohortMembership.getStartDate()).thenReturn(Date.from(Instant.now()));
+		
+		Group.GroupMemberComponent component = groupMemberTranslator.toFhirResource(cohortMembership);
+		assertThat(component, notNullValue());
+		assertThat(component.hasPeriod(), is(true));
+		assertThat(component.getPeriod().getStart(), notNullValue());
+		assertThat(Date.from(Instant.now()), DateMatchers.sameDay(component.getPeriod().getStart()));
+	}
+	
+	@Test
+	public void shouldTranslateCohortMemberEndDateToFHIRType() {
+		CohortMembership cohortMembership = mock(CohortMembership.class);
+		Patient patient = mock(Patient.class);
+		Reference patientReference = mock(Reference.class);
+		
+		when(patientReferenceTranslator.toFhirResource(patient)).thenReturn(patientReference);
+		when(patientDao.getPatientById(anyInt())).thenReturn(patient);
+		when(cohortMembership.getEndDate()).thenReturn(Date.from(Instant.now()));
+		
+		Group.GroupMemberComponent component = groupMemberTranslator.toFhirResource(cohortMembership);
+		assertThat(component, notNullValue());
+		assertThat(component.hasPeriod(), is(true));
+		assertThat(component.getPeriod().getEnd(), notNullValue());
+		assertThat(Date.from(Instant.now()), DateMatchers.sameDay(component.getPeriod().getEnd()));
+	}
+	
+	@Test
+	public void shouldTranslateCohortMemberToFHIRGroupEntity() {
+		CohortMembership cohortMembership = mock(CohortMembership.class);
+		Patient patient = mock(Patient.class);
+		Reference patientReference = mock(Reference.class);
+		
+		when(patientReferenceTranslator.toFhirResource(patient)).thenReturn(patientReference);
+		when(patientDao.getPatientById(anyInt())).thenReturn(patient);
+		when(cohortMembership.getPatientId()).thenReturn(1);
+		
+		Group.GroupMemberComponent component = groupMemberTranslator.toFhirResource(cohortMembership);
+		assertThat(component, notNullValue());
+		assertThat(component.hasEntity(), is(true));
+		assertThat(component.getEntity(), is(patientReference));
+	}
+	
+	@Test
+	public void shouldGroupEntityToCohortPatientIdOpenMRSType() {
+		Reference patientReference = mock(Reference.class);
+		Patient patient = mock(Patient.class);
+		Group.GroupMemberComponent component = mock(Group.GroupMemberComponent.class);
+		
+		when(component.hasEntity()).thenReturn(true);
+		when(component.getEntity()).thenReturn(patientReference);
+		when(patient.getPatientId()).thenReturn(1);
+		when(patientReferenceTranslator.toOpenmrsType(patientReference)).thenReturn(patient);
+		
+		CohortMembership membership = groupMemberTranslator.toOpenmrsType(component);
+		assertThat(membership, notNullValue());
+		assertThat(membership.getPatientId(), notNullValue());
+		assertThat(membership.getPatientId(), equalTo(1));
+	}
+	
+	@Test
+	public void shouldGroupPeriodToCohortStartAndEndDateOpenMRSType() {
+		Period period = mock(Period.class);
+		Group.GroupMemberComponent component = mock(Group.GroupMemberComponent.class);
+		
+		when(component.hasPeriod()).thenReturn(true);
+		when(component.getPeriod()).thenReturn(period);
+		when(period.getStart()).thenReturn(Date.from(Instant.now()));
+		when(period.getEnd()).thenReturn(Date.from(Instant.now()));
+		
+		CohortMembership membership = groupMemberTranslator.toOpenmrsType(component);
+		assertThat(membership, notNullValue());
+		assertThat(membership.getStartDate(), notNullValue());
+		assertThat(membership.getEndDate(), notNullValue());
+		assertThat(membership.getStartDate(), DateMatchers.sameDay(Date.from(Instant.now())));
+		assertThat(membership.getEndDate(), DateMatchers.sameDay(Date.from(Instant.now())));
+	}
+	
+	@Test
+	public void shouldUpdatedGroupEntityOrCohortMembersOpenMRSType() {
+		Reference patientReference = mock(Reference.class);
+		Patient patient = mock(Patient.class);
+		Group.GroupMemberComponent component = mock(Group.GroupMemberComponent.class);
+		
+		when(component.hasEntity()).thenReturn(true);
+		when(component.getEntity()).thenReturn(patientReference);
+		when(patient.getPatientId()).thenReturn(4);
+		when(patientReferenceTranslator.toOpenmrsType(patientReference)).thenReturn(patient);
+		
+		//Existing cohortMembership with patient id 3
+		CohortMembership cohortMembership = new CohortMembership();
+		cohortMembership.setPatientId(3);
+		
+		CohortMembership membership = groupMemberTranslator.toOpenmrsType(cohortMembership, component);
+		assertThat(membership, notNullValue());
+		assertThat(membership.getPatientId(), notNullValue());
+		// Updated cohortMembership with patient id 4
+		assertThat(membership.getPatientId(), equalTo(4));
+	}
+	
+	@Test
+	public void shouldUpdateCohortMembershipEndDate() {
+		Period period = mock(Period.class);
+		Group.GroupMemberComponent component = mock(Group.GroupMemberComponent.class);
+		
+		// Existing cohortMembership
+		CohortMembership cohortMembership = new CohortMembership();
+		cohortMembership.setEndDate(Date.from(Instant.parse("2020-12-04T08:07:00Z")));
+		
+		when(component.hasPeriod()).thenReturn(true);
+		when(component.getPeriod()).thenReturn(period);
+		
+		// Mocked updated date is today
+		when(period.getEnd()).thenReturn(Date.from(Instant.now()));
+		
+		CohortMembership membership = groupMemberTranslator.toOpenmrsType(component);
+		assertThat(membership, notNullValue());
+		assertThat(membership.getEndDate(), notNullValue());
+		assertThat(membership.getEndDate(), DateMatchers.sameDay(Date.from(Instant.now())));
+	}
+	
+	@Test
+	public void shouldUpdateCohortMembershipStartDate() {
+		Period period = mock(Period.class);
+		Group.GroupMemberComponent component = mock(Group.GroupMemberComponent.class);
+		
+		// Existing cohortMembership
+		CohortMembership cohortMembership = new CohortMembership();
+		cohortMembership.setStartDate(Date.from(Instant.parse("2020-12-04T08:07:00Z")));
+		
+		when(component.hasPeriod()).thenReturn(true);
+		when(component.getPeriod()).thenReturn(period);
+		
+		// Mocked updated date is today
+		when(period.getStart()).thenReturn(Date.from(Instant.now()));
+		
+		CohortMembership membership = groupMemberTranslator.toOpenmrsType(component);
+		assertThat(membership, notNullValue());
+		assertThat(membership.getStartDate(), notNullValue());
+		assertThat(membership.getStartDate(), DateMatchers.sameDay(Date.from(Instant.now())));
+	}
+}
diff --git a/api-2.1/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl_2_1Test.java b/api-2.1/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl_2_1Test.java
new file mode 100644
index 000000000..08916989b
--- /dev/null
+++ b/api-2.1/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl_2_1Test.java
@@ -0,0 +1,199 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+
+import org.hl7.fhir.r4.model.Group;
+import org.hl7.fhir.r4.model.Reference;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.Cohort;
+import org.openmrs.CohortMembership;
+import org.openmrs.module.fhir2.api.translators.GroupMemberTranslator;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GroupTranslatorImpl_2_1Test {
+	
+	private static final String COHORT_UUID = "787e12bd-314e-4cc4-9b4d-1cdff9be9545";
+	
+	private static final String NEW_COHORT_UUID = "787e12bd-314e-4cc4-9b4d-1cdff9be9545";
+	
+	private static final String COHORT_NAME = "Patient with VL > 2";
+	
+	@Mock
+	private GroupMemberTranslator<CohortMembership> groupMemberTranslator;
+	
+	private GroupTranslatorImpl_2_1 groupTranslator;
+	
+	@Before
+	public void setup() {
+		groupTranslator = new GroupTranslatorImpl_2_1();
+		groupTranslator.setGroupMemberTranslator(groupMemberTranslator);
+	}
+	
+	@Test
+	public void shouldTranslateUuidToIdFHIRType() {
+		Cohort cohort = mock(Cohort.class);
+		when(cohort.getUuid()).thenReturn(COHORT_UUID);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.getId(), is(COHORT_UUID));
+	}
+	
+	@Test
+	public void shouldTranslateNameToNameFHIRType() {
+		Cohort cohort = mock(Cohort.class);
+		when(cohort.getName()).thenReturn(COHORT_NAME);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.getName(), is(COHORT_NAME));
+	}
+	
+	@Test
+	public void shouldTranslateNameOpenMRSTypeToNameFHIRType() {
+		Group group = mock(Group.class);
+		when(group.hasName()).thenReturn(true);
+		when(group.getName()).thenReturn("Mr. Moon's patient list");
+		
+		Cohort cohort = groupTranslator.toOpenmrsType(group);
+		assertThat(cohort, notNullValue());
+		assertThat(cohort.getName(), notNullValue());
+		assertThat(cohort.getName(), is("Mr. Moon's patient list"));
+	}
+	
+	@Test
+	public void shouldReturnUpdatedNameOpenMRSType() {
+		Cohort cohort = new Cohort();
+		cohort.setName("Moon's patient list");
+		
+		Group group = mock(Group.class);
+		when(group.hasName()).thenReturn(true);
+		when(group.getName()).thenReturn("Mr. Moon's patient list");
+		
+		Cohort updateCohort = groupTranslator.toOpenmrsType(cohort, group);
+		assertThat(updateCohort, notNullValue());
+		assertThat(updateCohort.getName(), notNullValue());
+		assertThat(updateCohort.getName(), is("Mr. Moon's patient list"));
+	}
+	
+	@Test
+	public void shouldTranslateIsVoidedToIsActiveFHIRType() {
+		Cohort cohort = mock(Cohort.class);
+		when(cohort.getVoided()).thenReturn(false);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.getActive(), is(true));
+	}
+	
+	@Test
+	public void shouldTranslateActiveFHIRTypeToIsVoidedOpenMRSType() {
+		Group group = mock(Group.class);
+		when(group.hasActive()).thenReturn(true);
+		when(group.getActive()).thenReturn(true);
+		
+		Cohort cohort = groupTranslator.toOpenmrsType(group);
+		assertThat(cohort, notNullValue());
+		assertThat(cohort.getVoided(), is(false));
+	}
+	
+	@Test
+	public void shouldUpdateIsVoidedOpenMRSType() {
+		Cohort cohort = new Cohort();
+		cohort.setVoided(false);
+		
+		Group group = mock(Group.class);
+		when(group.hasActive()).thenReturn(true);
+		when(group.getActive()).thenReturn(false);
+		
+		Cohort updateCohort = groupTranslator.toOpenmrsType(cohort, group);
+		assertThat(updateCohort, notNullValue());
+		assertThat(updateCohort.getVoided(), is(true));
+	}
+	
+	@Test
+	public void shouldTranslateGroupTypeToAlwaysPerson() {
+		Cohort cohort = mock(Cohort.class);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.getType(), is(Group.GroupType.PERSON));
+	}
+	
+	@Test
+	public void shouldTranslateCohortMembersToFHIRGroupMembers() {
+		Cohort cohort = mock(Cohort.class);
+		CohortMembership cohortMembership = mock(CohortMembership.class);
+		Reference patientReference = mock(Reference.class);
+		Group.GroupMemberComponent groupMemberComponent = mock(Group.GroupMemberComponent.class);
+		
+		when(cohort.getMemberships()).thenReturn(Arrays.asList(cohortMembership, cohortMembership));
+		when(groupMemberTranslator.toFhirResource(cohortMembership)).thenReturn(groupMemberComponent);
+		when(groupMemberComponent.hasEntity()).thenReturn(true);
+		when(groupMemberComponent.getEntity()).thenReturn(patientReference);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.hasMember(), is(true));
+		assertThat(group.getMemberFirstRep().hasEntity(), is(true));
+		assertThat(group.getMemberFirstRep().getEntity(), is(patientReference));
+	}
+	
+	@Test
+	public void shouldTranslateFHIRGroupMembersToOpenMRSCohortMembers() {
+		Group group = mock(Group.class);
+		CohortMembership cohortMembership = mock(CohortMembership.class);
+		Group.GroupMemberComponent groupMemberComponent = mock(Group.GroupMemberComponent.class);
+		
+		when(group.hasMember()).thenReturn(true);
+		when(group.getMember()).thenReturn(Arrays.asList(groupMemberComponent, groupMemberComponent));
+		when(groupMemberTranslator.toOpenmrsType(groupMemberComponent)).thenReturn(cohortMembership);
+		
+		Cohort cohort = groupTranslator.toOpenmrsType(group);
+		assertThat(cohort, notNullValue());
+		assertThat(cohort.getMemberships().isEmpty(), is(false));
+		assertThat(cohort.getMemberships(), hasSize(1));
+		assertThat(cohort.getMemberships().iterator().next(), is(cohortMembership));
+	}
+	
+	@Test
+	public void shouldUpdateMemberList() {
+		CohortMembership cohortMembership = mock(CohortMembership.class);
+		Group.GroupMemberComponent component = mock(Group.GroupMemberComponent.class);
+		
+		Cohort existingCohort = new Cohort();
+		existingCohort.setUuid(COHORT_UUID);
+		existingCohort.setVoided(false);
+		existingCohort.setMemberships(Arrays.asList(cohortMembership, cohortMembership));
+		
+		Group group = mock(Group.class);
+		when(group.hasMember()).thenReturn(true);
+		when(groupMemberTranslator.toOpenmrsType(component)).thenReturn(cohortMembership);
+		when(group.getMember()).thenReturn(Arrays.asList(component, component));
+		
+		Cohort updateCohort = groupTranslator.toOpenmrsType(existingCohort, group);
+		assertThat(updateCohort, notNullValue());
+		assertThat(updateCohort.getMemberships(), notNullValue());
+		assertThat(updateCohort.getMemberships(), hasSize(1));
+	}
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java b/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java
index 451c7d266..2bb6f6269 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java
@@ -75,6 +75,8 @@ public class FhirConstants {
 	
 	public static final String OPENMRS_FHIR_EXT_NAME = OPENMRS_FHIR_EXT_PREFIX + "/name";
 	
+	public static final String OPENMRS_FHIR_EXT_GROUP_DESCRIPTION = OPENMRS_FHIR_EXT_PREFIX + "/group/description";
+	
 	public static final String OPENMRS_FHIR_EXT_ADDRESS = OPENMRS_FHIR_EXT_PREFIX + "/address";
 	
 	public static final String OPENMRS_FHIR_EXT_NON_CODED_CONDITION = OPENMRS_FHIR_EXT_PREFIX + "/non-coded-condition";
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/FhirGroupService.java b/api/src/main/java/org/openmrs/module/fhir2/api/FhirGroupService.java
new file mode 100644
index 000000000..f6ef77ffe
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/FhirGroupService.java
@@ -0,0 +1,14 @@
+/*
+ * 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.fhir2.api;
+
+import org.hl7.fhir.r4.model.Group;
+
+public interface FhirGroupService extends FhirService<Group> {}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/FhirPatientService.java b/api/src/main/java/org/openmrs/module/fhir2/api/FhirPatientService.java
index 19521ae2c..5c92c93fb 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/FhirPatientService.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/FhirPatientService.java
@@ -11,7 +11,9 @@
 
 import javax.annotation.Nonnull;
 
+import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 
 import ca.uhn.fhir.model.api.Include;
 import ca.uhn.fhir.rest.api.SortSpec;
@@ -28,6 +30,10 @@ public interface FhirPatientService extends FhirService<Patient> {
 	@Override
 	Patient get(@Nonnull String uuid);
 	
+	List<Patient> getPatientsByIds(@Nonnull Collection<Integer> ids);
+	
+	Patient getById(@Nonnull Integer id);
+	
 	PatientIdentifierType getPatientIdentifierTypeByIdentifier(Identifier identifier);
 	
 	IBundleProvider searchForPatients(StringAndListParam name, StringAndListParam given, StringAndListParam family,
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/FhirGroupDao.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/FhirGroupDao.java
new file mode 100644
index 000000000..ccf3cc0f3
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/FhirGroupDao.java
@@ -0,0 +1,39 @@
+/*
+ * 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.fhir2.api.dao;
+
+import javax.annotation.Nonnull;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.openmrs.Cohort;
+import org.openmrs.annotation.Authorized;
+import org.openmrs.util.PrivilegeConstants;
+
+public interface FhirGroupDao extends FhirDao<Cohort> {
+	
+	@Override
+	@Authorized(PrivilegeConstants.GET_PATIENT_COHORTS)
+	Cohort get(@Nonnull String uuid);
+	
+	@Override
+	@Authorized(PrivilegeConstants.GET_PATIENT_COHORTS)
+	List<Cohort> get(@Nonnull Collection<String> uuids);
+	
+	@Override
+	@Authorized(PrivilegeConstants.ADD_COHORTS)
+	Cohort createOrUpdate(@Nonnull Cohort newEntry);
+	
+	@Override
+	@Authorized(PrivilegeConstants.DELETE_COHORTS)
+	Cohort delete(@Nonnull String uuid);
+	
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/FhirPatientDao.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/FhirPatientDao.java
index 27ca088d5..7932340e0 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/FhirPatientDao.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/FhirPatientDao.java
@@ -11,6 +11,7 @@
 
 import javax.annotation.Nonnull;
 
+import java.util.Collection;
 import java.util.List;
 
 import org.openmrs.Patient;
@@ -24,6 +25,9 @@ public interface FhirPatientDao extends FhirDao<Patient> {
 	@Authorized(PrivilegeConstants.GET_PATIENTS)
 	Patient getPatientById(@Nonnull Integer id);
 	
+	@Authorized(PrivilegeConstants.GET_PATIENTS)
+	List<Patient> getPatientsByIds(@Nonnull Collection<Integer> ids);
+	
 	@Override
 	@Authorized(PrivilegeConstants.GET_PATIENTS)
 	Patient get(@Nonnull String uuid);
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java
index 6243696dd..43465c9f6 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java
@@ -107,7 +107,7 @@ public T get(@Nonnull String uuid) {
 	@Override
 	@Transactional(readOnly = true)
 	@SuppressWarnings("unchecked")
-	public List<T> get(Collection<String> uuids) {
+	public List<T> get(@Nonnull Collection<String> uuids) {
 		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(typeToken.getRawType());
 		criteria.add(in("uuid", uuids));
 		
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirGroupDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirGroupDaoImpl.java
new file mode 100644
index 000000000..182e6a7e1
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirGroupDaoImpl.java
@@ -0,0 +1,22 @@
+/*
+ * 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.fhir2.api.dao.impl;
+
+import lombok.AccessLevel;
+import lombok.Setter;
+import org.openmrs.Cohort;
+import org.openmrs.module.fhir2.api.dao.FhirGroupDao;
+import org.springframework.stereotype.Component;
+
+@Component
+@Setter(AccessLevel.PACKAGE)
+public class FhirGroupDaoImpl extends BaseFhirDao<Cohort> implements FhirGroupDao {
+	
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImpl.java
index e283c7525..efb873329 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImpl.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImpl.java
@@ -17,6 +17,7 @@
 
 import javax.annotation.Nonnull;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Optional;
@@ -44,6 +45,12 @@ public Patient getPatientById(@Nonnull Integer id) {
 		        .uniqueResult();
 	}
 	
+	@Override
+	@SuppressWarnings("unchecked")
+	public List<Patient> getPatientsByIds(@Nonnull Collection<Integer> ids) {
+		return getSessionFactory().getCurrentSession().createCriteria(Patient.class).add(in("id", ids)).list();
+	}
+	
 	@Override
 	@SuppressWarnings("unchecked")
 	public PatientIdentifierType getPatientIdentifierTypeByNameOrUuid(String name, String uuid) {
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirGroupServiceImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirGroupServiceImpl.java
new file mode 100644
index 000000000..1319349b2
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirGroupServiceImpl.java
@@ -0,0 +1,35 @@
+/*
+ * 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.fhir2.api.impl;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import org.hl7.fhir.r4.model.Group;
+import org.openmrs.Cohort;
+import org.openmrs.module.fhir2.api.FhirGroupService;
+import org.openmrs.module.fhir2.api.dao.FhirGroupDao;
+import org.openmrs.module.fhir2.api.translators.GroupTranslator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional
+@Setter(AccessLevel.PACKAGE)
+@Getter(AccessLevel.PROTECTED)
+public class FhirGroupServiceImpl extends BaseFhirService<Group, Cohort> implements FhirGroupService {
+	
+	@Autowired
+	private FhirGroupDao dao;
+	
+	@Autowired
+	private GroupTranslator translator;
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirPatientServiceImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirPatientServiceImpl.java
index dbb181c5e..a2a05f938 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirPatientServiceImpl.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/impl/FhirPatientServiceImpl.java
@@ -9,7 +9,12 @@
  */
 package org.openmrs.module.fhir2.api.impl;
 
+import javax.annotation.Nonnull;
+
+import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
 
 import ca.uhn.fhir.model.api.Include;
 import ca.uhn.fhir.rest.api.SortSpec;
@@ -53,6 +58,17 @@ public class FhirPatientServiceImpl extends BaseFhirService<Patient, org.openmrs
 	@Autowired
 	private SearchQuery<org.openmrs.Patient, Patient, FhirPatientDao, PatientTranslator, SearchQueryInclude<Patient>> searchQuery;
 	
+	@Override
+	public List<Patient> getPatientsByIds(@Nonnull Collection<Integer> ids) {
+		List<org.openmrs.Patient> patients = dao.getPatientsByIds(ids);
+		return patients.stream().map(translator::toFhirResource).collect(Collectors.toList());
+	}
+	
+	@Override
+	public Patient getById(@Nonnull Integer id) {
+		return translator.toFhirResource(dao.getPatientById(id));
+	}
+	
 	@Override
 	@Transactional(readOnly = true)
 	public PatientIdentifierType getPatientIdentifierTypeByIdentifier(Identifier identifier) {
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/GroupMemberTranslator.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/GroupMemberTranslator.java
new file mode 100644
index 000000000..570587a43
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/GroupMemberTranslator.java
@@ -0,0 +1,45 @@
+/*
+ * 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.fhir2.api.translators;
+
+import javax.annotation.Nonnull;
+
+import org.hl7.fhir.r4.model.Group;
+
+public interface GroupMemberTranslator<T> extends OpenmrsFhirUpdatableTranslator<T, Group.GroupMemberComponent> {
+	
+	/**
+	 * Maps an OpenMRS data element to a FHIR resource
+	 *
+	 * @param cohortMember the OpenMRS data element to translate
+	 * @return the corresponding FHIR resource
+	 */
+	@Override
+	Group.GroupMemberComponent toFhirResource(@Nonnull T cohortMember);
+	
+	/**
+	 * Maps a FHIR resource to an OpenMRS data element
+	 *
+	 * @param groupMemberComponent the FHIR resource to translate
+	 * @return the corresponding OpenMRS data element
+	 */
+	@Override
+	T toOpenmrsType(@Nonnull Group.GroupMemberComponent groupMemberComponent);
+	
+	/**
+	 * Maps a FHIR resource to an existing OpenMRS data element
+	 *
+	 * @param existingCohort the existingObject to update
+	 * @param groupMemberComponent the resource to map
+	 * @return an updated version of the existingObject
+	 */
+	@Override
+	T toOpenmrsType(@Nonnull T existingCohort, @Nonnull Group.GroupMemberComponent groupMemberComponent);
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/GroupTranslator.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/GroupTranslator.java
new file mode 100644
index 000000000..36313e6e2
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/GroupTranslator.java
@@ -0,0 +1,46 @@
+/*
+ * 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.fhir2.api.translators;
+
+import javax.annotation.Nonnull;
+
+import org.hl7.fhir.r4.model.Group;
+import org.openmrs.Cohort;
+
+public interface GroupTranslator extends OpenmrsFhirUpdatableTranslator<Cohort, Group> {
+	
+	/**
+	 * Maps an OpenMRS cohort to a FHIR group resource
+	 *
+	 * @param cohort the OpenMRS cohort to translate
+	 * @return the corresponding FHIR resource
+	 */
+	@Override
+	Group toFhirResource(@Nonnull Cohort cohort);
+	
+	/**
+	 * Maps a FHIR group resource to an OpenMRS cohort
+	 *
+	 * @param group the FHIR group resource to translate
+	 * @return the corresponding OpenMRS cohort
+	 */
+	@Override
+	Cohort toOpenmrsType(@Nonnull Group group);
+	
+	/**
+	 * Maps a FHIR group resource to an existing OpenMRS cohort
+	 *
+	 * @param existingCohort the existingCohort to update
+	 * @param group the group resource to map
+	 * @return an updated version of the existing cohort
+	 */
+	@Override
+	Cohort toOpenmrsType(@Nonnull Cohort existingCohort, @Nonnull Group group);
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/BaseGroupTranslator.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/BaseGroupTranslator.java
new file mode 100644
index 000000000..2584f6cdf
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/BaseGroupTranslator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.apache.commons.lang3.Validate.notNull;
+
+import javax.annotation.Nonnull;
+
+import org.hl7.fhir.r4.model.Extension;
+import org.hl7.fhir.r4.model.Group;
+import org.hl7.fhir.r4.model.StringType;
+import org.openmrs.Cohort;
+import org.openmrs.module.fhir2.FhirConstants;
+
+public abstract class BaseGroupTranslator {
+	
+	public Group toFhirResource(@Nonnull Cohort cohort) {
+		notNull(cohort, "Cohort object should not be null");
+		Group group = new Group();
+		group.setId(cohort.getUuid());
+		group.setActive(!cohort.getVoided());
+		
+		/*
+		 * Apparently, cohort.description is a required field
+		 */
+		group.addExtension(new Extension().setUrl(FhirConstants.OPENMRS_FHIR_EXT_GROUP_DESCRIPTION)
+		        .setValue(new StringType(cohort.getDescription())));
+		
+		// Not sure about this, It's either actual or descriptive
+		// I will set actual - true temporarily as it required - valid resource.
+		group.setActual(true);
+		
+		// Set to always person for now
+		group.setType(Group.GroupType.PERSON);
+		group.setName(cohort.getName());
+		
+		return group;
+	}
+	
+	public Cohort toOpenmrsType(@Nonnull Cohort existingCohort, @Nonnull Group group) {
+		notNull(group, "group resource object should not be null");
+		notNull(existingCohort, "ExistingCohort object should not be null");
+		
+		if (group.hasId()) {
+			existingCohort.setUuid(group.getId());
+		}
+		
+		if (group.hasName()) {
+			existingCohort.setName(group.getName());
+		}
+		
+		if (group.hasActive()) {
+			existingCohort.setVoided(!group.getActive());
+		}
+		
+		Extension extension = group.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_GROUP_DESCRIPTION);
+		if (extension != null && extension.hasValue()) {
+			existingCohort.setDescription(extension.getValue().toString());
+		}
+		
+		return existingCohort;
+	}
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl.java
new file mode 100644
index 000000000..61c639091
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.apache.commons.lang3.Validate.notNull;
+
+import javax.annotation.Nonnull;
+
+import lombok.AccessLevel;
+import lombok.Setter;
+import org.hl7.fhir.r4.model.Group;
+import org.openmrs.annotation.OpenmrsProfile;
+import org.openmrs.module.fhir2.api.dao.FhirPatientDao;
+import org.openmrs.module.fhir2.api.translators.GroupMemberTranslator;
+import org.openmrs.module.fhir2.api.translators.PatientReferenceTranslator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+@Setter(AccessLevel.MODULE)
+@OpenmrsProfile(openmrsPlatformVersion = "2.0.* - 2.1")
+public class GroupMemberTranslatorImpl implements GroupMemberTranslator<Integer> {
+	
+	@Autowired
+	private FhirPatientDao patientDao;
+	
+	@Autowired
+	private PatientReferenceTranslator patientReferenceTranslator;
+	
+	@Override
+	public Group.GroupMemberComponent toFhirResource(@Nonnull Integer memberId) {
+		notNull(memberId, "MemberId should not be null");
+		return new Group.GroupMemberComponent()
+		        .setEntity(patientReferenceTranslator.toFhirResource(patientDao.getPatientById(memberId)));
+	}
+	
+	@Override
+	public Integer toOpenmrsType(@Nonnull Group.GroupMemberComponent groupMemberComponent) {
+		notNull(groupMemberComponent, "GroupComponent object cannot not be null");
+		return toOpenmrsType(-1, groupMemberComponent);
+	}
+	
+	@Override
+	public Integer toOpenmrsType(@Nonnull Integer existingMemberId,
+	        @Nonnull Group.GroupMemberComponent groupMemberComponent) {
+		notNull(existingMemberId, "Existing memberId should not be null");
+		notNull(groupMemberComponent, "GroupMemberComponent Object should not be null");
+		if (groupMemberComponent.hasEntity()) {
+			existingMemberId = patientReferenceTranslator.toOpenmrsType(groupMemberComponent.getEntity()).getPatientId();
+		}
+		return existingMemberId;
+	}
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl.java
new file mode 100644
index 000000000..338adecf9
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImpl.java
@@ -0,0 +1,70 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.apache.commons.lang3.Validate.notNull;
+
+import javax.annotation.Nonnull;
+
+import java.util.Set;
+
+import lombok.AccessLevel;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.hl7.fhir.r4.model.Group;
+import org.openmrs.Cohort;
+import org.openmrs.annotation.OpenmrsProfile;
+import org.openmrs.module.fhir2.api.translators.GroupMemberTranslator;
+import org.openmrs.module.fhir2.api.translators.GroupTranslator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@Setter(AccessLevel.MODULE)
+@OpenmrsProfile(openmrsPlatformVersion = "2.0.*")
+public class GroupTranslatorImpl extends BaseGroupTranslator implements GroupTranslator {
+	
+	@Autowired
+	private GroupMemberTranslator<Integer> groupMemberTranslator;
+	
+	@Override
+	public Group toFhirResource(@Nonnull Cohort cohort) {
+		notNull(cohort, "Cohort object should not be null");
+		Group group = super.toFhirResource(cohort);
+		
+		Set<Integer> memberIds = cohort.getMemberIds();
+		log.info("Number of members {} ", memberIds.size());
+		group.setQuantity(cohort.size());
+		memberIds.forEach(id -> group.addMember(groupMemberTranslator.toFhirResource(id)));
+		
+		return group;
+	}
+	
+	@Override
+	public Cohort toOpenmrsType(@Nonnull Group group) {
+		notNull(group, "Group resource should not be null");
+		return toOpenmrsType(new Cohort(), group);
+	}
+	
+	@Override
+	public Cohort toOpenmrsType(@Nonnull Cohort existingCohort, @Nonnull Group group) {
+		notNull(group, "group resource object should not be null");
+		notNull(existingCohort, "ExistingCohort object should not be null");
+		
+		Cohort finalExistingCohort = super.toOpenmrsType(existingCohort, group);
+		
+		if (group.hasMember()) {
+			group.getMember().forEach(member -> finalExistingCohort.addMember(groupMemberTranslator.toOpenmrsType(member)));
+		}
+		
+		return finalExistingCohort;
+	}
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProvider.java b/api/src/main/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProvider.java
new file mode 100644
index 000000000..25f38b863
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProvider.java
@@ -0,0 +1,87 @@
+/*
+ * 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.fhir2.providers.r3;
+
+import javax.annotation.Nonnull;
+
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.Delete;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Read;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import lombok.AccessLevel;
+import lombok.Setter;
+import org.hl7.fhir.convertors.conv30_40.Group30_40;
+import org.hl7.fhir.dstu3.model.Group;
+import org.hl7.fhir.dstu3.model.IdType;
+import org.hl7.fhir.dstu3.model.OperationOutcome;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.openmrs.module.fhir2.api.FhirGroupService;
+import org.openmrs.module.fhir2.providers.util.FhirProviderUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+@Component("GroupFhirR3ResourceProvider")
+@Qualifier("fhirR3Resources")
+@Setter(AccessLevel.PACKAGE)
+public class GroupFhirResourceProvider implements IResourceProvider {
+	
+	@Autowired
+	private FhirGroupService groupService;
+	
+	@Override
+	public Class<? extends IBaseResource> getResourceType() {
+		return Group.class;
+	}
+	
+	@Read
+	public Group getGroupByUuid(@IdParam @Nonnull IdType id) {
+		org.hl7.fhir.r4.model.Group group = groupService.get(id.getIdPart());
+		if (group == null) {
+			throw new ResourceNotFoundException("Could not find Group with Id " + id.getIdPart());
+		}
+		return Group30_40.convertGroup(group);
+	}
+	
+	@Create
+	@SuppressWarnings("unused")
+	public MethodOutcome createGroup(@ResourceParam Group group) {
+		return FhirProviderUtils.buildCreate(Group30_40.convertGroup(groupService.create(Group30_40.convertGroup(group))));
+	}
+	
+	@Update
+	@SuppressWarnings("unused")
+	public MethodOutcome updateGroup(@IdParam IdType id, @ResourceParam Group group) {
+		if (id == null || id.getIdPart() == null) {
+			throw new InvalidRequestException("id must be specified to update");
+		}
+		
+		group.setId(id.getIdPart());
+		
+		return FhirProviderUtils
+		        .buildUpdate(Group30_40.convertGroup(groupService.update(id.getIdPart(), Group30_40.convertGroup(group))));
+	}
+	
+	@Delete
+	@SuppressWarnings("unused")
+	public OperationOutcome deleteGroup(@IdParam @Nonnull IdType id) {
+		org.hl7.fhir.r4.model.Group group = (groupService.delete(id.getIdPart()));
+		if (group == null) {
+			throw new ResourceNotFoundException("Could not find group to update with id " + id.getIdPart());
+		}
+		return FhirProviderUtils.buildDelete(Group30_40.convertGroup(group));
+	}
+}
diff --git a/api/src/main/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProvider.java b/api/src/main/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProvider.java
new file mode 100644
index 000000000..cc872146f
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProvider.java
@@ -0,0 +1,85 @@
+/*
+ * 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.fhir2.providers.r4;
+
+import javax.annotation.Nonnull;
+
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.Delete;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Read;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import lombok.AccessLevel;
+import lombok.Setter;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.Group;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.OperationOutcome;
+import org.openmrs.module.fhir2.api.FhirGroupService;
+import org.openmrs.module.fhir2.providers.util.FhirProviderUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+@Component("GroupFhirR4ResourceProvider")
+@Qualifier("fhirResources")
+@Setter(AccessLevel.PACKAGE)
+public class GroupFhirResourceProvider implements IResourceProvider {
+	
+	@Autowired
+	private FhirGroupService groupService;
+	
+	@Override
+	public Class<? extends IBaseResource> getResourceType() {
+		return Group.class;
+	}
+	
+	@Read
+	public Group getGroupByUuid(@IdParam @Nonnull IdType id) {
+		Group group = groupService.get(id.getIdPart());
+		if (group == null) {
+			throw new ResourceNotFoundException("Could not find Group with Id " + id.getIdPart());
+		}
+		return group;
+	}
+	
+	@Create
+	@SuppressWarnings("unused")
+	public MethodOutcome createGroup(@ResourceParam Group group) {
+		return FhirProviderUtils.buildCreate(groupService.create(group));
+	}
+	
+	@Update
+	@SuppressWarnings("unused")
+	public MethodOutcome updateGroup(@IdParam IdType id, @ResourceParam Group group) {
+		if (id == null || id.getIdPart() == null) {
+			throw new InvalidRequestException("id must be specified to update");
+		}
+		
+		group.setId(id.getIdPart());
+		
+		return FhirProviderUtils.buildUpdate(groupService.update(id.getIdPart(), group));
+	}
+	
+	@Delete
+	@SuppressWarnings("unused")
+	public OperationOutcome deleteGroup(@IdParam @Nonnull IdType id) {
+		Group group = groupService.delete(id.getIdPart());
+		if (group == null) {
+			throw new ResourceNotFoundException("Could not find group to update with id " + id.getIdPart());
+		}
+		return FhirProviderUtils.buildDelete(group);
+	}
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirGroupDaoImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirGroupDaoImplTest.java
new file mode 100644
index 000000000..b52e4f37c
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirGroupDaoImplTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.fhir2.api.dao.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+import org.hibernate.SessionFactory;
+import org.junit.Before;
+import org.junit.Test;
+import org.openmrs.Cohort;
+import org.openmrs.module.fhir2.TestFhirSpringConfiguration;
+import org.openmrs.test.BaseModuleContextSensitiveTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.test.context.ContextConfiguration;
+
+@ContextConfiguration(classes = TestFhirSpringConfiguration.class, inheritLocations = false)
+public class FhirGroupDaoImplTest extends BaseModuleContextSensitiveTest {
+	
+	private static final String COHORT_UUID = "985ff1a2-c2ef-49fd-836f-8a1d936d9ef9";
+	
+	private static final String NEW_COHORT_UUID = "111ff1a2-c2ef-49fd-836f-8a1d936d9ef0";
+	
+	private static final String BAD_COHORT_UUID = "005ff1a0-c2ef-49fd-836f-8a1d936d9ef7";
+	
+	private static final String COHORT_INITIAL_DATA_XML = "org/openmrs/module/fhir2/api/dao/impl/FhirCohortDaoImplTest_initial_data.xml";
+	
+	private static final String COHORT_NAME = "John's patientList";
+	
+	private FhirGroupDaoImpl dao;
+	
+	@Autowired
+	@Qualifier("sessionFactory")
+	private SessionFactory sessionFactory;
+	
+	@Before
+	public void setup() throws Exception {
+		dao = new FhirGroupDaoImpl();
+		dao.setSessionFactory(sessionFactory);
+		executeDataSet(COHORT_INITIAL_DATA_XML);
+	}
+	
+	@Test
+	public void getCohortByUuid_shouldReturnMatchingCohort() {
+		Cohort cohort = dao.get(COHORT_UUID);
+		assertThat(cohort, notNullValue());
+		assertThat(cohort.getUuid(), equalTo(COHORT_UUID));
+		assertThat(cohort.getName(), equalTo(COHORT_NAME));
+	}
+	
+	@Test
+	public void getCohortByWithWrongUuid_shouldReturnNullCohort() {
+		Cohort cohort = dao.get(BAD_COHORT_UUID);
+		assertThat(cohort, nullValue());
+	}
+	
+	@Test
+	public void shouldSaveGroup() {
+		Cohort cohort = new Cohort();
+		cohort.setUuid(NEW_COHORT_UUID);
+		cohort.setName(COHORT_NAME);
+		cohort.setDescription("Test cohort");
+		
+		Cohort result = dao.createOrUpdate(cohort);
+		assertThat(result, notNullValue());
+		assertThat(result.getUuid(), equalTo(NEW_COHORT_UUID));
+		assertThat(result.getName(), equalTo(COHORT_NAME));
+	}
+	
+	@Test
+	public void shouldUpdateGroupCorrectly() {
+		Cohort cohort = dao.get(COHORT_UUID);
+		cohort.setName("Update cohort name");
+		
+		Cohort result = dao.createOrUpdate(cohort);
+		assertThat(result, notNullValue());
+		assertThat(result.getName(), equalTo("Update cohort name"));
+	}
+	
+	@Test
+	public void shouldDeleteGroup() {
+		Cohort result = dao.delete(COHORT_UUID);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getVoided(), is(true));
+		assertThat(result.getVoidReason(), equalTo("Voided via FHIR API"));
+	}
+	
+	@Test
+	public void shouldReturnNullIfGroupToDeleteDoesNotExist() {
+		Cohort result = dao.delete(BAD_COHORT_UUID);
+		
+		assertThat(result, nullValue());
+	}
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirGroupServiceImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirGroupServiceImplTest.java
new file mode 100644
index 000000000..2f55c0d81
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirGroupServiceImplTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.fhir2.api.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import org.hl7.fhir.r4.model.Group;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.Cohort;
+import org.openmrs.module.fhir2.api.dao.FhirGroupDao;
+import org.openmrs.module.fhir2.api.translators.GroupTranslator;
+
+@RunWith(MockitoJUnitRunner.class)
+public class FhirGroupServiceImplTest {
+	
+	private static final String COHORT_UUID = "1359f03d-55d9-4961-b8f8-9a59eddc1f59";
+	
+	private static final String BAD_COHORT_UUID = "02ed36f0-6167-4372-a641-d27b92f7deae";
+	
+	@Mock
+	private FhirGroupDao dao;
+	
+	@Mock
+	private GroupTranslator translator;
+	
+	private FhirGroupServiceImpl groupService;
+	
+	private Group group;
+	
+	private Cohort cohort;
+	
+	@Before
+	public void setup() {
+		groupService = new FhirGroupServiceImpl() {
+			
+			@Override
+			protected void validateObject(Cohort object) {
+			}
+		};
+		groupService.setDao(dao);
+		groupService.setTranslator(translator);
+		
+		group = new Group();
+		group.setId(COHORT_UUID);
+		
+		cohort = new Cohort();
+		cohort.setUuid(COHORT_UUID);
+	}
+	
+	@Test
+	public void getGroupByUuid_shouldGetGroupByUuid() {
+		when(dao.get(COHORT_UUID)).thenReturn(cohort);
+		when(translator.toFhirResource(cohort)).thenReturn(group);
+		
+		Group group = groupService.get(COHORT_UUID);
+		assertThat(group, notNullValue());
+		assertThat(group.getId(), notNullValue());
+		assertThat(group.getId(), equalTo(COHORT_UUID));
+	}
+	
+	@Test
+	public void getGroupByUuid_shouldThrowResourceNotFoundWhenCalledWithUnknownUuid() {
+		assertThrows(ResourceNotFoundException.class, () -> groupService.get(BAD_COHORT_UUID));
+	}
+	
+	@Test
+	public void shouldSaveNewGroup() {
+		Cohort cohort = new Cohort();
+		cohort.setUuid(COHORT_UUID);
+		
+		Group group = new Group();
+		group.setId(COHORT_UUID);
+		
+		when(translator.toFhirResource(cohort)).thenReturn(group);
+		when(translator.toOpenmrsType(group)).thenReturn(cohort);
+		when(dao.createOrUpdate(cohort)).thenReturn(cohort);
+		
+		Group result = groupService.create(group);
+		assertThat(result, notNullValue());
+		assertThat(result.getId(), equalTo(COHORT_UUID));
+	}
+	
+	@Test(expected = InvalidRequestException.class)
+	public void updateGroupShouldThrowInvalidRequestExceptionIfIdIsNull() {
+		Group group = new Group();
+		group.setId(COHORT_UUID);
+		
+		groupService.update(null, group);
+	}
+	
+	@Test(expected = InvalidRequestException.class)
+	public void updateGroupShouldThrowInvalidRequestExceptionIfIdIsBad() {
+		Group group = new Group();
+		group.setId(COHORT_UUID);
+		
+		groupService.update(BAD_COHORT_UUID, group);
+	}
+	
+	@Test(expected = ResourceNotFoundException.class)
+	public void updateGroupShouldThrowResourceNotFoundException() {
+		Group group = new Group();
+		group.setId(COHORT_UUID);
+		
+		groupService.update(COHORT_UUID, group);
+	}
+	
+	@Test
+	public void shouldUpdateGroup() {
+		Cohort cohort = new Cohort();
+		cohort.setUuid(COHORT_UUID);
+		cohort.setVoided(false);
+		
+		Group group = new Group();
+		group.setId(COHORT_UUID);
+		group.setActive(false);
+		
+		when(dao.get(COHORT_UUID)).thenReturn(cohort);
+		when(translator.toFhirResource(cohort)).thenReturn(group);
+		when(translator.toOpenmrsType(cohort, group)).thenReturn(cohort);
+		when(dao.createOrUpdate(cohort)).thenReturn(cohort);
+		
+		Group result = groupService.update(COHORT_UUID, group);
+		assertThat(result, notNullValue());
+		assertThat(result.getActive(), is(false));
+	}
+	
+	@Test
+	public void shouldDeleteGroup() {
+		Group group = new Group();
+		group.setId(COHORT_UUID);
+		
+		when(dao.delete(COHORT_UUID)).thenReturn(cohort);
+		when(translator.toFhirResource(cohort)).thenReturn(group);
+		
+		Group result = groupService.delete(COHORT_UUID);
+		assertThat(result, notNullValue());
+		assertThat(result.getId(), equalTo(COHORT_UUID));
+		assertThat(result.getActive(), is(false));
+	}
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirPatientServiceImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirPatientServiceImplTest.java
index f5bd2fa12..f813c8caf 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirPatientServiceImplTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/impl/FhirPatientServiceImplTest.java
@@ -21,12 +21,14 @@
 import static org.hamcrest.Matchers.not;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anySet;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
@@ -173,6 +175,31 @@ public void getPatientByUuid_shouldRetrievePatientByUuid() {
 		assertThat(result.getId(), equalTo(PATIENT_UUID));
 	}
 	
+	@Test
+	public void getById_shouldReturnPatientById() {
+		when(dao.getPatientById(1)).thenReturn(patient);
+		when(patientTranslator.toFhirResource(patient)).thenReturn(fhirPatient);
+		
+		org.hl7.fhir.r4.model.Patient result = patientService.getById(1);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getId(), notNullValue());
+		assertThat(result.getId(), equalTo(PATIENT_UUID));
+	}
+	
+	@Test
+	public void getByIds_shouldRetrievePatientsByIds() {
+		when(dao.getPatientsByIds(anySet())).thenReturn(Arrays.asList(patient, patient, patient));
+		when(patientTranslator.toFhirResource(patient)).thenReturn(fhirPatient);
+		
+		List<org.hl7.fhir.r4.model.Patient> patientList = patientService
+		        .getPatientsByIds(new HashSet<>(Arrays.asList(1, 2, 3)));
+		
+		assertThat(patientList, notNullValue());
+		assertThat(patientList, not(empty()));
+		assertThat(patientList, hasSize(3));
+	}
+	
 	@Test
 	public void searchForPatients_shouldSearchForPatientsByName() {
 		List<Patient> patients = new ArrayList<>();
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImplTest.java
new file mode 100644
index 000000000..b69ac361d
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupMemberTranslatorImplTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.hl7.fhir.r4.model.Group;
+import org.hl7.fhir.r4.model.Reference;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.Cohort;
+import org.openmrs.Patient;
+import org.openmrs.module.fhir2.api.dao.FhirPatientDao;
+import org.openmrs.module.fhir2.api.translators.PatientReferenceTranslator;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GroupMemberTranslatorImplTest {
+	
+	private static final String COHORT_UUID = "787e12bd-314e-4cc4-9b4d-1cdff9be9545";
+	
+	private static final String COHORT_NAME = "Patient with VL > 2";
+	
+	@Mock
+	private PatientReferenceTranslator patientReferenceTranslator;
+	
+	@Mock
+	private FhirPatientDao patientDao;
+	
+	private GroupMemberTranslatorImpl groupMemberTranslator;
+	
+	private Cohort cohort;
+	
+	private Group.GroupMemberComponent groupMemberComponent;
+	
+	@Before
+	public void setup() {
+		groupMemberTranslator = new GroupMemberTranslatorImpl();
+		groupMemberTranslator.setPatientDao(patientDao);
+		groupMemberTranslator.setPatientReferenceTranslator(patientReferenceTranslator);
+	}
+	
+	@Before
+	public void init() {
+		cohort = new Cohort();
+		cohort.setUuid(COHORT_UUID);
+		cohort.setName(COHORT_NAME);
+		
+		groupMemberComponent = new Group.GroupMemberComponent();
+		groupMemberComponent.setId(COHORT_UUID);
+	}
+	
+	@Test
+	public void shouldTranslateCohortMemberToFHIRType() {
+		Reference patientReference = mock(Reference.class);
+		Patient patient = mock(Patient.class);
+		when(patientReferenceTranslator.toFhirResource(patient)).thenReturn(patientReference);
+		when(patientDao.getPatientById(1)).thenReturn(patient);
+		
+		Group.GroupMemberComponent component = groupMemberTranslator.toFhirResource(1);
+		assertThat(component, notNullValue());
+		assertThat(component.getEntity(), notNullValue());
+		assertThat(component.hasEntity(), is(true));
+	}
+	
+	@Test
+	public void shouldTranslateGroupMemberComponentToOpenMRSType() {
+		Reference patientReference = mock(Reference.class);
+		Patient patient = mock(Patient.class);
+		when(patient.getPatientId()).thenReturn(1);
+		when(patientReferenceTranslator.toOpenmrsType(patientReference)).thenReturn(patient);
+		
+		Group.GroupMemberComponent component = new Group.GroupMemberComponent();
+		component.setEntity(patientReference);
+		
+		Integer patientId = groupMemberTranslator.toOpenmrsType(component);
+		assertThat(patientId, notNullValue());
+		assertThat(patientId, is(1));
+	}
+	
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImplTest.java
new file mode 100644
index 000000000..fff5fd4dd
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/GroupTranslatorImplTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.fhir2.api.translators.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import org.hl7.fhir.r4.model.Group;
+import org.hl7.fhir.r4.model.Reference;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.Cohort;
+import org.openmrs.module.fhir2.api.translators.GroupMemberTranslator;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GroupTranslatorImplTest {
+	
+	private static final String COHORT_UUID = "787e12bd-314e-4cc4-9b4d-1cdff9be9545";
+	
+	private static final String COHORT_NAME = "Patient with VL > 2";
+	
+	@Mock
+	private GroupMemberTranslator<Integer> groupMemberTranslator;
+	
+	private GroupTranslatorImpl groupTranslator;
+	
+	@Before
+	public void setup() {
+		groupTranslator = new GroupTranslatorImpl();
+		groupTranslator.setGroupMemberTranslator(groupMemberTranslator);
+	}
+	
+	@Test
+	public void shouldTranslateUuidToIdFHIRType() {
+		Cohort cohort = mock(Cohort.class);
+		when(cohort.getUuid()).thenReturn(COHORT_UUID);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.getId(), is(COHORT_UUID));
+	}
+	
+	@Test
+	public void shouldTranslateNameToNameFHIRType() {
+		Cohort cohort = mock(Cohort.class);
+		when(cohort.getName()).thenReturn(COHORT_NAME);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.getName(), is(COHORT_NAME));
+	}
+	
+	@Test
+	public void shouldTranslateNameOpenMRSTypeToNameFHIRType() {
+		Group group = mock(Group.class);
+		when(group.hasName()).thenReturn(true);
+		when(group.getName()).thenReturn("Mr. Moon's patient list");
+		
+		Cohort cohort = groupTranslator.toOpenmrsType(group);
+		assertThat(cohort, notNullValue());
+		assertThat(cohort.getName(), notNullValue());
+		assertThat(cohort.getName(), is("Mr. Moon's patient list"));
+	}
+	
+	@Test
+	public void shouldReturnUpdatedNameOpenMRSType() {
+		Cohort cohort = new Cohort();
+		cohort.setName("Moon's patient list");
+		
+		Group group = mock(Group.class);
+		when(group.hasName()).thenReturn(true);
+		when(group.getName()).thenReturn("Mr. Moon's patient list");
+		
+		Cohort updateCohort = groupTranslator.toOpenmrsType(cohort, group);
+		assertThat(updateCohort, notNullValue());
+		assertThat(updateCohort.getName(), notNullValue());
+		assertThat(updateCohort.getName(), is("Mr. Moon's patient list"));
+	}
+	
+	@Test
+	public void shouldTranslateIsVoidedToIsActiveFHIRType() {
+		Cohort cohort = mock(Cohort.class);
+		when(cohort.getVoided()).thenReturn(false);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.getActive(), is(true));
+	}
+	
+	@Test
+	public void shouldTranslateActiveFHIRTypeToIsVoidedOpenMRSType() {
+		Group group = mock(Group.class);
+		when(group.hasActive()).thenReturn(true);
+		when(group.getActive()).thenReturn(true);
+		
+		Cohort cohort = groupTranslator.toOpenmrsType(group);
+		assertThat(cohort, notNullValue());
+		assertThat(cohort.getVoided(), is(false));
+	}
+	
+	@Test
+	public void shouldUpdateIsVoidedOpenMRSType() {
+		Cohort cohort = new Cohort();
+		cohort.setVoided(false);
+		
+		Group group = mock(Group.class);
+		when(group.hasActive()).thenReturn(true);
+		when(group.getActive()).thenReturn(false);
+		
+		Cohort updateCohort = groupTranslator.toOpenmrsType(cohort, group);
+		assertThat(updateCohort, notNullValue());
+		assertThat(updateCohort.getVoided(), is(true));
+	}
+	
+	@Test
+	public void shouldTranslateGroupTypeToAlwaysPerson() {
+		Cohort cohort = mock(Cohort.class);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.getType(), is(Group.GroupType.PERSON));
+	}
+	
+	@Test
+	public void shouldTranslateCohortMembersToFHIRGroupMembers() {
+		Cohort cohort = mock(Cohort.class);
+		Reference patientReference = mock(Reference.class);
+		Group.GroupMemberComponent groupMemberComponent = mock(Group.GroupMemberComponent.class);
+		when(cohort.getMemberIds()).thenReturn(new HashSet<>(Arrays.asList(1, 2, 3)));
+		when(groupMemberTranslator.toFhirResource(anyInt())).thenReturn(groupMemberComponent);
+		when(groupMemberComponent.hasEntity()).thenReturn(true);
+		when(groupMemberComponent.getEntity()).thenReturn(patientReference);
+		
+		Group group = groupTranslator.toFhirResource(cohort);
+		assertThat(group, notNullValue());
+		assertThat(group.hasMember(), is(true));
+		assertThat(group.getMemberFirstRep().hasEntity(), is(true));
+		assertThat(group.getMemberFirstRep().getEntity(), is(patientReference));
+	}
+	
+	@Test
+	public void shouldTranslateFHIRGroupMembersToOpenMRSCohortMembers() {
+		Group group = mock(Group.class);
+		Group.GroupMemberComponent groupMemberComponent = mock(Group.GroupMemberComponent.class);
+		
+		when(group.hasMember()).thenReturn(true);
+		when(group.getMember()).thenReturn(Arrays.asList(groupMemberComponent, groupMemberComponent));
+		when(groupMemberTranslator.toOpenmrsType(groupMemberComponent)).thenReturn(1);
+		
+		Cohort cohort = groupTranslator.toOpenmrsType(group);
+		assertThat(cohort, notNullValue());
+		assertThat(cohort.getMemberIds().isEmpty(), is(false));
+		assertThat(cohort.getMemberIds(), hasSize(1));
+		assertThat(cohort.getMemberIds().iterator().next(), is(1));
+	}
+	
+	@Test
+	public void shouldUpdateMemberList() {
+		Cohort cohort = new Cohort();
+		cohort.setVoided(false);
+		cohort.setMemberIds(new HashSet<>(Arrays.asList(1, 2, 3)));
+		
+		Group group = mock(Group.class);
+		when(group.hasMember()).thenReturn(true);
+		
+		Group.GroupMemberComponent component1 = mock(Group.GroupMemberComponent.class);
+		Group.GroupMemberComponent component2 = mock(Group.GroupMemberComponent.class);
+		Group.GroupMemberComponent component3 = mock(Group.GroupMemberComponent.class);
+		Group.GroupMemberComponent component4 = mock(Group.GroupMemberComponent.class);
+		
+		when(groupMemberTranslator.toOpenmrsType(component1)).thenReturn(1);
+		when(groupMemberTranslator.toOpenmrsType(component2)).thenReturn(2);
+		when(groupMemberTranslator.toOpenmrsType(component3)).thenReturn(3);
+		when(groupMemberTranslator.toOpenmrsType(component4)).thenReturn(4);
+		when(group.getMember()).thenReturn(Arrays.asList(component1, component2, component3, component4));
+		
+		Cohort updateCohort = groupTranslator.toOpenmrsType(cohort, group);
+		assertThat(updateCohort, notNullValue());
+		assertThat(updateCohort.getMemberIds(), notNullValue());
+		assertThat(updateCohort.getMemberIds(), hasSize(4));
+	}
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProviderTest.java b/api/src/test/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProviderTest.java
new file mode 100644
index 000000000..bad3e4a44
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProviderTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.fhir2.providers.r3;
+
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import org.hl7.fhir.convertors.conv30_40.Group30_40;
+import org.hl7.fhir.dstu3.model.Group;
+import org.hl7.fhir.dstu3.model.IdType;
+import org.hl7.fhir.dstu3.model.OperationOutcome;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.module.fhir2.api.FhirGroupService;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GroupFhirResourceProviderTest {
+	
+	private static final String COHORT_UUID = "ce8bfad7-c87e-4af0-80cd-c2015c7dff93";
+	
+	private static final String BAD_COHORT_UUID = "51f069dc-e204-40f4-90d6-080385bed91f";
+	
+	@Mock
+	private FhirGroupService fhirGroupService;
+	
+	private GroupFhirResourceProvider resourceProvider;
+	
+	private org.hl7.fhir.r4.model.Group group;
+	
+	@Before
+	public void setup() {
+		resourceProvider = new GroupFhirResourceProvider();
+		resourceProvider.setGroupService(fhirGroupService);
+		
+		group = new org.hl7.fhir.r4.model.Group();
+		group.setId(COHORT_UUID);
+	}
+	
+	@Test
+	public void getResourceType_shouldReturnResourceType() {
+		assertThat(resourceProvider.getResourceType(), equalTo(Group.class));
+		assertThat(resourceProvider.getResourceType().getName(), equalTo(Group.class.getName()));
+	}
+	
+	@Test
+	public void getGroupByUuid_shouldReturnMatchingGroup() {
+		when(fhirGroupService.get(COHORT_UUID)).thenReturn(group);
+		
+		IdType id = new IdType();
+		id.setValue(COHORT_UUID);
+		Group group = resourceProvider.getGroupByUuid(id);
+		assertThat(group, notNullValue());
+		assertThat(group.getId(), notNullValue());
+		assertThat(group.getId(), equalTo(COHORT_UUID));
+	}
+	
+	@Test(expected = ResourceNotFoundException.class)
+	public void getGroupByUuid_shouldThrowResourceNotFoundException() {
+		IdType id = new IdType();
+		id.setValue(BAD_COHORT_UUID);
+		Group group = resourceProvider.getGroupByUuid(id);
+		assertThat(group, nullValue());
+	}
+	
+	@Test
+	public void shouldCreateNewGroup() {
+		when(fhirGroupService.create(any(org.hl7.fhir.r4.model.Group.class))).thenReturn(group);
+		
+		MethodOutcome result = resourceProvider.createGroup(Group30_40.convertGroup(group));
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getCreated(), is(true));
+		assertThat(result.getResource(), notNullValue());
+		assertThat(result.getResource().getIdElement().getIdPart(), equalTo(group.getId()));
+		assertThat(result.getResource().getStructureFhirVersionEnum(), equalTo(FhirVersionEnum.DSTU3));
+	}
+	
+	@Test
+	public void shouldUpdateExistingGroup() {
+		org.hl7.fhir.r4.model.Group.GroupMemberComponent groupMemberComponent = mock(
+		    org.hl7.fhir.r4.model.Group.GroupMemberComponent.class);
+		
+		group.setActual(false);
+		group.addMember(groupMemberComponent);
+		
+		when(fhirGroupService.update(eq(COHORT_UUID), any(org.hl7.fhir.r4.model.Group.class))).thenReturn(group);
+		
+		MethodOutcome result = resourceProvider.updateGroup(new IdType().setValue(COHORT_UUID),
+		    Group30_40.convertGroup(group));
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getResource(), notNullValue());
+		assertThat(result.getResource().getIdElement().getIdPart(), equalTo(group.getId()));
+		assertThat(result.getResource().getStructureFhirVersionEnum(), equalTo(FhirVersionEnum.DSTU3));
+	}
+	
+	@Test(expected = InvalidRequestException.class)
+	public void updateGroupShouldThrowInvalidRequestForUuidMismatch() {
+		when(fhirGroupService.update(eq(BAD_COHORT_UUID), any(org.hl7.fhir.r4.model.Group.class)))
+		        .thenThrow(InvalidRequestException.class);
+		
+		resourceProvider.updateGroup(new IdType().setValue(BAD_COHORT_UUID), Group30_40.convertGroup(group));
+	}
+	
+	@Test(expected = InvalidRequestException.class)
+	public void ShouldThrowInvalidRequestForMissingIdInGroupToUpdate() {
+		org.hl7.fhir.r4.model.Group noIdGroup = new org.hl7.fhir.r4.model.Group();
+		
+		when(fhirGroupService.update(eq(COHORT_UUID), any(org.hl7.fhir.r4.model.Group.class)))
+		        .thenThrow(InvalidRequestException.class);
+		
+		resourceProvider.updateGroup(new IdType().setValue(COHORT_UUID), Group30_40.convertGroup(noIdGroup));
+	}
+	
+	@Test(expected = MethodNotAllowedException.class)
+	public void shouldThrowMethodNotAllowedIfGroupToUpdateDoesNotExist() {
+		org.hl7.fhir.r4.model.Group wrongGroup = new org.hl7.fhir.r4.model.Group();
+		wrongGroup.setId(BAD_COHORT_UUID);
+		
+		when(fhirGroupService.update(eq(BAD_COHORT_UUID), any(org.hl7.fhir.r4.model.Group.class)))
+		        .thenThrow(MethodNotAllowedException.class);
+		
+		resourceProvider.updateGroup(new IdType().setValue(BAD_COHORT_UUID), Group30_40.convertGroup(wrongGroup));
+	}
+	
+	@Test
+	public void shouldDeleteRequestedGroup() {
+		when(fhirGroupService.delete(COHORT_UUID)).thenReturn(group);
+		
+		OperationOutcome result = resourceProvider.deleteGroup(new IdType().setValue(COHORT_UUID));
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getIssue(), notNullValue());
+		assertThat(result.getIssueFirstRep().getSeverity(), equalTo(OperationOutcome.IssueSeverity.INFORMATION));
+		assertThat(result.getIssueFirstRep().getDetails().getCodingFirstRep().getCode(), equalTo("MSG_DELETED"));
+	}
+	
+	@Test(expected = ResourceNotFoundException.class)
+	public void shouldThrowResourceNotFoundExceptionWhenIdRefersToNonExistentGroup() {
+		when(fhirGroupService.delete(BAD_COHORT_UUID)).thenReturn(null);
+		
+		resourceProvider.deleteGroup(new IdType().setValue(BAD_COHORT_UUID));
+	}
+}
diff --git a/api/src/test/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProviderTest.java b/api/src/test/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProviderTest.java
new file mode 100644
index 000000000..e1a1dca97
--- /dev/null
+++ b/api/src/test/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProviderTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.fhir2.providers.r4;
+
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import org.hl7.fhir.r4.model.Group;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.OperationOutcome;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.openmrs.module.fhir2.api.FhirGroupService;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GroupFhirResourceProviderTest {
+	
+	private static final String COHORT_UUID = "ce8bfad7-c87e-4af0-80cd-c2015c7dff93";
+	
+	private static final String BAD_COHORT_UUID = "51f069dc-e204-40f4-90d6-080385bed91f";
+	
+	@Mock
+	private FhirGroupService fhirGroupService;
+	
+	GroupFhirResourceProvider resourceProvider;
+	
+	Group group;
+	
+	@Before
+	public void setup() {
+		resourceProvider = new GroupFhirResourceProvider();
+		resourceProvider.setGroupService(fhirGroupService);
+		
+		group = new Group();
+		group.setId(COHORT_UUID);
+	}
+	
+	@Test
+	public void getResourceType_shouldReturnResourceType() {
+		assertThat(resourceProvider.getResourceType(), equalTo(Group.class));
+		assertThat(resourceProvider.getResourceType().getName(), equalTo(Group.class.getName()));
+	}
+	
+	@Test
+	public void getGroupByUuid_shouldReturnMatchingGroup() {
+		when(fhirGroupService.get(COHORT_UUID)).thenReturn(group);
+		
+		IdType id = new IdType();
+		id.setValue(COHORT_UUID);
+		Group group = resourceProvider.getGroupByUuid(id);
+		assertThat(group, notNullValue());
+		assertThat(group.getId(), notNullValue());
+		assertThat(group.getId(), equalTo(COHORT_UUID));
+	}
+	
+	@Test(expected = ResourceNotFoundException.class)
+	public void getGroupByUuid_shouldThrowResourceNotFoundException() {
+		IdType id = new IdType();
+		id.setValue(BAD_COHORT_UUID);
+		Group group = resourceProvider.getGroupByUuid(id);
+		assertThat(group, nullValue());
+	}
+	
+	@Test
+	public void shouldCreateNewGroup() {
+		when(fhirGroupService.create(any(org.hl7.fhir.r4.model.Group.class))).thenReturn(group);
+		
+		MethodOutcome result = resourceProvider.createGroup(group);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getCreated(), is(true));
+		assertThat(result.getResource(), notNullValue());
+		assertThat(result.getResource().getIdElement().getIdPart(), equalTo(group.getId()));
+		assertThat(result.getResource().getStructureFhirVersionEnum(), equalTo(FhirVersionEnum.R4));
+	}
+	
+	@Test
+	public void shouldUpdateExistingGroup() {
+		Group.GroupMemberComponent groupMemberComponent = mock(Group.GroupMemberComponent.class);
+		
+		group.setActual(false);
+		group.addMember(groupMemberComponent);
+		
+		when(fhirGroupService.update(eq(COHORT_UUID), any(org.hl7.fhir.r4.model.Group.class))).thenReturn(group);
+		
+		MethodOutcome result = resourceProvider.updateGroup(new IdType().setValue(COHORT_UUID), group);
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getResource(), notNullValue());
+		assertThat(result.getResource().getIdElement().getIdPart(), equalTo(group.getId()));
+		assertThat(result.getResource().getStructureFhirVersionEnum(), equalTo(FhirVersionEnum.R4));
+	}
+	
+	@Test(expected = InvalidRequestException.class)
+	public void updateGroupShouldThrowInvalidRequestForUuidMismatch() {
+		when(fhirGroupService.update(eq(BAD_COHORT_UUID), any(org.hl7.fhir.r4.model.Group.class)))
+		        .thenThrow(InvalidRequestException.class);
+		
+		resourceProvider.updateGroup(new IdType().setValue(BAD_COHORT_UUID), group);
+	}
+	
+	@Test(expected = InvalidRequestException.class)
+	public void ShouldThrowInvalidRequestForMissingIdInGroupToUpdate() {
+		org.hl7.fhir.r4.model.Group noIdGroup = new org.hl7.fhir.r4.model.Group();
+		
+		when(fhirGroupService.update(eq(COHORT_UUID), any(org.hl7.fhir.r4.model.Group.class)))
+		        .thenThrow(InvalidRequestException.class);
+		
+		resourceProvider.updateGroup(new IdType().setValue(COHORT_UUID), noIdGroup);
+	}
+	
+	@Test(expected = MethodNotAllowedException.class)
+	public void shouldThrowMethodNotAllowedIfGroupToUpdateDoesNotExist() {
+		org.hl7.fhir.r4.model.Group wrongGroup = new org.hl7.fhir.r4.model.Group();
+		wrongGroup.setId(BAD_COHORT_UUID);
+		
+		when(fhirGroupService.update(eq(BAD_COHORT_UUID), any(org.hl7.fhir.r4.model.Group.class)))
+		        .thenThrow(MethodNotAllowedException.class);
+		
+		resourceProvider.updateGroup(new IdType().setValue(BAD_COHORT_UUID), wrongGroup);
+	}
+	
+	@Test
+	public void shouldDeleteRequestedGroup() {
+		when(fhirGroupService.delete(COHORT_UUID)).thenReturn(group);
+		
+		OperationOutcome result = resourceProvider.deleteGroup(new IdType().setValue(COHORT_UUID));
+		
+		assertThat(result, notNullValue());
+		assertThat(result.getIssue(), notNullValue());
+		assertThat(result.getIssueFirstRep().getSeverity(), equalTo(OperationOutcome.IssueSeverity.INFORMATION));
+		assertThat(result.getIssueFirstRep().getDetails().getCodingFirstRep().getCode(), equalTo("MSG_DELETED"));
+	}
+	
+	@Test(expected = ResourceNotFoundException.class)
+	public void shouldThrowResourceNotFoundExceptionWhenIdRefersToNonExistentGroup() {
+		when(fhirGroupService.delete(BAD_COHORT_UUID)).thenReturn(null);
+		
+		resourceProvider.deleteGroup(new IdType().setValue(BAD_COHORT_UUID));
+	}
+}
diff --git a/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProviderIntegrationTest.java b/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProviderIntegrationTest.java
new file mode 100644
index 000000000..f777fd39a
--- /dev/null
+++ b/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r3/GroupFhirResourceProviderIntegrationTest.java
@@ -0,0 +1,390 @@
+/*
+ * 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.fhir2.providers.r3;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.hl7.fhir.dstu3.model.Group;
+import org.hl7.fhir.dstu3.model.OperationOutcome;
+import org.junit.Before;
+import org.junit.Test;
+import org.openmrs.module.fhir2.BaseFhirIntegrationTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+@Slf4j
+public class GroupFhirResourceProviderIntegrationTest extends BaseFhirR3IntegrationTest<GroupFhirResourceProvider, Group> {
+	
+	private static final String COHORT_UUID = "1d64befb-3b2e-48e5-85f5-353d43e23e46";
+	
+	private static final String BAD_COHORT_UUID = "5c9d032b-6092-4052-93d2-a04202b98462";
+	
+	private static final String COHORT_DATA_XML = "org/openmrs/module/fhir2/api/dao/impl/FhirCohortDaoImplTest_initial_data.xml";
+	
+	private static final String PATIENT_DATA_XML = "org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImplTest_initial_data.xml";
+	
+	private static final String JSON_CREATE_GROUP_DOCUMENT = "org/openmrs/module/fhir2/providers/GroupWebTest_create.json";
+	
+	private static final String XML_CREATE_GROUP_DOCUMENT = "org/openmrs/module/fhir2/providers/GroupWebTest_create.xml";
+	
+	private static final String JSON_UPDATE_GROUP_DOCUMENT = "org/openmrs/module/fhir2/providers/GroupWebTest_update.json";
+	
+	private static final String XML_UPDATE_GROUP_DOCUMENT = "org/openmrs/module/fhir2/providers/GroupWebTest_update.xml";
+	
+	@Autowired
+	@Getter(AccessLevel.PUBLIC)
+	private GroupFhirResourceProvider resourceProvider;
+	
+	@Before
+	@Override
+	public void setup() throws Exception {
+		super.setup();
+		executeDataSet(PATIENT_DATA_XML);
+		executeDataSet(COHORT_DATA_XML);
+	}
+	
+	@Test
+	public void shouldReturnExistingGroupAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(BaseFhirIntegrationTest.FhirMediaTypes.JSON)
+		        .go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(BaseFhirIntegrationTest.FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		
+		assertThat(group, notNullValue());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(group, validResource());
+	}
+	
+	@Test
+	public void shouldThrow404ForNonExistingGroupAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Group/" + BAD_COHORT_UUID)
+		        .accept(BaseFhirIntegrationTest.FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(BaseFhirIntegrationTest.FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+	}
+	
+	@Test
+	public void shouldReturnExistingGroupAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(BaseFhirIntegrationTest.FhirMediaTypes.XML)
+		        .go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(BaseFhirIntegrationTest.FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		
+		assertThat(group, notNullValue());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(group, validResource());
+	}
+	
+	@Test
+	public void shouldThrow404ForNonExistingGroupAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Group/" + BAD_COHORT_UUID)
+		        .accept(BaseFhirIntegrationTest.FhirMediaTypes.XML).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(BaseFhirIntegrationTest.FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+	}
+	
+	@Test
+	public void shouldCreateNewGroupAsJson() throws Exception {
+		// read JSON record
+		String jsonGroup;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(JSON_CREATE_GROUP_DOCUMENT)) {
+			Objects.requireNonNull(is);
+			jsonGroup = IOUtils.toString(is, StandardCharsets.UTF_8);
+		}
+		
+		// create group
+		MockHttpServletResponse response = post("/Group").accept(FhirMediaTypes.JSON).jsonContent(jsonGroup).go();
+		
+		// verify created correctly
+		assertThat(response, isCreated());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		assertThat(group, notNullValue());
+		assertThat(group.getActive(), is(true));
+		assertThat(group.hasMember(), is(true));
+		
+		// try to get new group
+		response = get(group.getId()).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		
+		Group newGroup = readResponse(response);
+		
+		assertThat(newGroup.getId(), equalTo(group.getId()));
+		assertThat(newGroup.getActive(), equalTo(true));
+		
+	}
+	
+	@Test
+	public void shouldCreateNewGroupAsXML() throws Exception {
+		// read JSON record
+		String xmlGroup;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(XML_CREATE_GROUP_DOCUMENT)) {
+			Objects.requireNonNull(is);
+			xmlGroup = IOUtils.toString(is, StandardCharsets.UTF_8);
+		}
+		
+		// create group
+		MockHttpServletResponse response = post("/Group").accept(FhirMediaTypes.XML).xmlContext(xmlGroup).go();
+		
+		// verify created correctly
+		assertThat(response, isCreated());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		assertThat(group, notNullValue());
+		assertThat(group.getActive(), is(true));
+		assertThat(group.hasMember(), is(true));
+		
+		// try to get new group
+		response = get(group.getId()).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		
+		Group newGroup = readResponse(response);
+		
+		assertThat(newGroup.getId(), equalTo(group.getId()));
+		assertThat(newGroup.getActive(), equalTo(true));
+	}
+	
+	@Test
+	public void shouldUpdateExistingGroupAsJson() throws Exception {
+		//Before update
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		
+		assertThat(group, notNullValue());
+		assertThat(group, validResource());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		
+		// Get existing group with updated name
+		String jsonGroup;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(JSON_UPDATE_GROUP_DOCUMENT)) {
+			Objects.requireNonNull(is);
+			jsonGroup = IOUtils.toString(is, StandardCharsets.UTF_8);
+		}
+		
+		//Update
+		response = put("/Group/" + COHORT_UUID).jsonContent(jsonGroup).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		// read updated record
+		group = readResponse(response);
+		
+		assertThat(group, notNullValue());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(group.getActive(), is(true));
+		assertThat(group, validResource());
+		
+		// Double-check via get
+		response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		Group updatedGroup = readResponse(response);
+		
+		assertThat(updatedGroup, validResource());
+		assertThat(updatedGroup, notNullValue());
+		assertThat(updatedGroup.getActive(), is(true));
+	}
+	
+	@Test
+	public void shouldUpdateExistingGroupAsXML() throws Exception {
+		//Before update
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		
+		assertThat(group, notNullValue());
+		assertThat(group, validResource());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		
+		// Get existing group with updated name
+		String xmlGroup;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(XML_UPDATE_GROUP_DOCUMENT)) {
+			Objects.requireNonNull(is);
+			xmlGroup = IOUtils.toString(is, StandardCharsets.UTF_8);
+		}
+		
+		//Update
+		response = put("/Group/" + COHORT_UUID).xmlContext(xmlGroup).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		// read updated record
+		group = readResponse(response);
+		
+		assertThat(group, notNullValue());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(group.getActive(), is(true));
+		assertThat(group, validResource());
+		
+		// Double-check via get
+		response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.XML).go();
+		
+		Group updatedGroup = readResponse(response);
+		
+		assertThat(updatedGroup, validResource());
+		assertThat(updatedGroup, notNullValue());
+		assertThat(updatedGroup.getActive(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnBadRequestWhenDocumentIdDoesNotMatchGroupIdAsXML() throws Exception {
+		// get the existing record
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.XML).go();
+		Group group = readResponse(response);
+		
+		// update the existing record
+		group.setId(BAD_COHORT_UUID);
+		
+		// send the update to the server
+		response = put("/Group/" + COHORT_UUID).xmlContext(toXML(group)).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isBadRequest());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenUpdatingNonExistentGroupAsXML() throws Exception {
+		// get the existing record
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.XML).go();
+		Group group = readResponse(response);
+		
+		// update the existing record
+		group.setId(BAD_COHORT_UUID);
+		
+		// send the update to the server
+		response = put("/Group/" + BAD_COHORT_UUID).xmlContext(toXML(group)).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnBadRequestWhenDocumentIdDoesNotMatchGroupIdAsJSON() throws Exception {
+		// get the existing record
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		Group group = readResponse(response);
+		
+		// update the existing record
+		group.setId(BAD_COHORT_UUID);
+		
+		// send the update to the server
+		response = put("/Group/" + COHORT_UUID).jsonContent(toJson(group)).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isBadRequest());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenUpdatingNonExistentGroupAsJSON() throws Exception {
+		// get the existing record
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		Group group = readResponse(response);
+		
+		// update the existing record
+		group.setId(BAD_COHORT_UUID);
+		
+		// send the update to the server
+		response = put("/Group/" + BAD_COHORT_UUID).jsonContent(toJson(group)).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldDeleteExistingGroup() throws Exception {
+		MockHttpServletResponse response = delete("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		
+		response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, statusEquals(HttpStatus.GONE));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenDeletingNonExistentGroup() throws Exception {
+		MockHttpServletResponse response = delete("/Group/" + BAD_COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+}
diff --git a/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProviderIntegrationTest.java b/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProviderIntegrationTest.java
new file mode 100644
index 000000000..50a9e9809
--- /dev/null
+++ b/integration-tests/src/test/java/org/openmrs/module/fhir2/providers/r4/GroupFhirResourceProviderIntegrationTest.java
@@ -0,0 +1,404 @@
+/*
+ * 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.fhir2.providers.r4;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.hl7.fhir.r4.model.Extension;
+import org.hl7.fhir.r4.model.Group;
+import org.hl7.fhir.r4.model.OperationOutcome;
+import org.junit.Before;
+import org.junit.Test;
+import org.openmrs.module.fhir2.BaseFhirIntegrationTest;
+import org.openmrs.module.fhir2.FhirConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+@Slf4j
+public class GroupFhirResourceProviderIntegrationTest extends BaseFhirR4IntegrationTest<org.openmrs.module.fhir2.providers.r4.GroupFhirResourceProvider, Group> {
+	
+	private static final String COHORT_UUID = "1d64befb-3b2e-48e5-85f5-353d43e23e46";
+	
+	private static final String BAD_COHORT_UUID = "5c9d032b-6092-4052-93d2-a04202b98462";
+	
+	private static final String COHORT_DATA_XML = "org/openmrs/module/fhir2/api/dao/impl/FhirCohortDaoImplTest_initial_data.xml";
+	
+	private static final String PATIENT_DATA_XML = "org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImplTest_initial_data.xml";
+	
+	private static final String JSON_CREATE_GROUP_DOCUMENT = "org/openmrs/module/fhir2/providers/GroupWebTest_create.json";
+	
+	private static final String XML_CREATE_GROUP_DOCUMENT = "org/openmrs/module/fhir2/providers/GroupWebTest_create.xml";
+	
+	private static final String JSON_UPDATE_GROUP_DOCUMENT = "org/openmrs/module/fhir2/providers/GroupWebTest_update.json";
+	
+	private static final String XML_UPDATE_GROUP_DOCUMENT = "org/openmrs/module/fhir2/providers/GroupWebTest_update.xml";
+	
+	@Autowired
+	@Getter(AccessLevel.PUBLIC)
+	private org.openmrs.module.fhir2.providers.r4.GroupFhirResourceProvider resourceProvider;
+	
+	@Before
+	@Override
+	public void setup() throws Exception {
+		super.setup();
+		executeDataSet(PATIENT_DATA_XML);
+		executeDataSet(COHORT_DATA_XML);
+	}
+	
+	@Test
+	public void shouldReturnExistingGroupAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(BaseFhirIntegrationTest.FhirMediaTypes.JSON)
+		        .go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(BaseFhirIntegrationTest.FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		
+		assertThat(group, notNullValue());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(group, validResource());
+	}
+	
+	@Test
+	public void shouldThrow404ForNonExistingGroupAsJson() throws Exception {
+		MockHttpServletResponse response = get("/Group/" + BAD_COHORT_UUID)
+		        .accept(BaseFhirIntegrationTest.FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(BaseFhirIntegrationTest.FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+	}
+	
+	@Test
+	public void shouldReturnExistingGroupAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(BaseFhirIntegrationTest.FhirMediaTypes.XML)
+		        .go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(BaseFhirIntegrationTest.FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		
+		assertThat(group, notNullValue());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(group, validResource());
+	}
+	
+	@Test
+	public void shouldThrow404ForNonExistingGroupAsXML() throws Exception {
+		MockHttpServletResponse response = get("/Group/" + BAD_COHORT_UUID)
+		        .accept(BaseFhirIntegrationTest.FhirMediaTypes.XML).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(BaseFhirIntegrationTest.FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+	}
+	
+	@Test
+	public void shouldCreateNewGroupAsJson() throws Exception {
+		// read JSON record
+		String jsonGroup;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(JSON_CREATE_GROUP_DOCUMENT)) {
+			Objects.requireNonNull(is);
+			jsonGroup = IOUtils.toString(is, StandardCharsets.UTF_8);
+		}
+		
+		// create group
+		MockHttpServletResponse response = post("/Group").accept(FhirMediaTypes.JSON).jsonContent(jsonGroup).go();
+		
+		// verify created correctly
+		assertThat(response, isCreated());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		assertThat(group, notNullValue());
+		assertThat(group.getActive(), is(true));
+		assertThat(group.hasMember(), is(true));
+		
+		// try to get new group
+		response = get(group.getId()).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		
+		Group newGroup = readResponse(response);
+		
+		assertThat(newGroup.getId(), equalTo(group.getId()));
+		assertThat(newGroup.getActive(), equalTo(true));
+		
+	}
+	
+	@Test
+	public void shouldCreateNewGroupAsXML() throws Exception {
+		// read JSON record
+		String xmlGroup;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(XML_CREATE_GROUP_DOCUMENT)) {
+			Objects.requireNonNull(is);
+			xmlGroup = IOUtils.toString(is, StandardCharsets.UTF_8);
+		}
+		
+		// create group
+		MockHttpServletResponse response = post("/Group").accept(FhirMediaTypes.XML).xmlContext(xmlGroup).go();
+		
+		// verify created correctly
+		assertThat(response, isCreated());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		assertThat(group, notNullValue());
+		assertThat(group.getActive(), is(true));
+		assertThat(group.hasMember(), is(true));
+		
+		// try to get new group
+		response = get(group.getId()).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		
+		Group newGroup = readResponse(response);
+		
+		assertThat(newGroup.getId(), equalTo(group.getId()));
+		assertThat(newGroup.getActive(), equalTo(true));
+	}
+	
+	@Test
+	public void shouldUpdateExistingGroupAsJson() throws Exception {
+		//Before update
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		Extension descExtension = group.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_GROUP_DESCRIPTION);
+		
+		assertThat(group, notNullValue());
+		assertThat(group, validResource());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(descExtension.getValue().toString(), equalTo("Covid19 patients"));
+		
+		// Get existing group with updated name
+		String jsonGroup;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(JSON_UPDATE_GROUP_DOCUMENT)) {
+			Objects.requireNonNull(is);
+			jsonGroup = IOUtils.toString(is, StandardCharsets.UTF_8);
+		}
+		
+		//Update
+		response = put("/Group/" + COHORT_UUID).jsonContent(jsonGroup).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		// read updated record
+		group = readResponse(response);
+		descExtension = group.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_GROUP_DESCRIPTION);
+		
+		assertThat(group, notNullValue());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(group.getActive(), is(true));
+		assertThat(descExtension.getValue().toString(), equalTo("Patients with at least one encounter"));
+		assertThat(group, validResource());
+		
+		// Double-check via get
+		response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		Group updatedGroup = readResponse(response);
+		descExtension = updatedGroup.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_GROUP_DESCRIPTION);
+		
+		assertThat(updatedGroup, validResource());
+		assertThat(updatedGroup, notNullValue());
+		assertThat(updatedGroup.getActive(), is(true));
+		assertThat(descExtension.getValue().toString(), equalTo("Patients with at least one encounter"));
+	}
+	
+	@Test
+	public void shouldUpdateExistingGroupAsXML() throws Exception {
+		//Before update
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		Group group = readResponse(response);
+		Extension descExtension = group.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_GROUP_DESCRIPTION);
+		
+		assertThat(group, notNullValue());
+		assertThat(group, validResource());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(descExtension.getValue().toString(), equalTo("Covid19 patients"));
+		
+		// Get existing group with updated name
+		String xmlGroup;
+		try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(XML_UPDATE_GROUP_DOCUMENT)) {
+			Objects.requireNonNull(is);
+			xmlGroup = IOUtils.toString(is, StandardCharsets.UTF_8);
+		}
+		
+		//Update
+		response = put("/Group/" + COHORT_UUID).xmlContext(xmlGroup).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isOk());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		// read updated record
+		group = readResponse(response);
+		descExtension = group.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_GROUP_DESCRIPTION);
+		
+		assertThat(group, notNullValue());
+		assertThat(group.getIdElement().getIdPart(), equalTo(COHORT_UUID));
+		assertThat(group.getActive(), is(true));
+		assertThat(descExtension.getValue().toString(), equalTo("Patients with at least one encounter"));
+		assertThat(group, validResource());
+		
+		// Double-check via get
+		response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.XML).go();
+		
+		Group updatedGroup = readResponse(response);
+		descExtension = updatedGroup.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_GROUP_DESCRIPTION);
+		
+		assertThat(updatedGroup, validResource());
+		assertThat(updatedGroup, notNullValue());
+		assertThat(updatedGroup.getActive(), is(true));
+		assertThat(descExtension.getValue().toString(), equalTo("Patients with at least one encounter"));
+	}
+	
+	@Test
+	public void shouldReturnBadRequestWhenDocumentIdDoesNotMatchGroupIdAsXML() throws Exception {
+		// get the existing record
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.XML).go();
+		Group group = readResponse(response);
+		
+		// update the existing record
+		group.setId(BAD_COHORT_UUID);
+		
+		// send the update to the server
+		response = put("/Group/" + COHORT_UUID).xmlContext(toXML(group)).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isBadRequest());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenUpdatingNonExistentGroupAsXML() throws Exception {
+		// get the existing record
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.XML).go();
+		Group group = readResponse(response);
+		
+		// update the existing record
+		group.setId(BAD_COHORT_UUID);
+		
+		// send the update to the server
+		response = put("/Group/" + BAD_COHORT_UUID).xmlContext(toXML(group)).accept(FhirMediaTypes.XML).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.XML.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnBadRequestWhenDocumentIdDoesNotMatchGroupIdAsJSON() throws Exception {
+		// get the existing record
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		Group group = readResponse(response);
+		
+		// update the existing record
+		group.setId(BAD_COHORT_UUID);
+		
+		// send the update to the server
+		response = put("/Group/" + COHORT_UUID).jsonContent(toJson(group)).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isBadRequest());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenUpdatingNonExistentGroupAsJSON() throws Exception {
+		// get the existing record
+		MockHttpServletResponse response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		Group group = readResponse(response);
+		
+		// update the existing record
+		group.setId(BAD_COHORT_UUID);
+		
+		// send the update to the server
+		response = put("/Group/" + BAD_COHORT_UUID).jsonContent(toJson(group)).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+	
+	@Test
+	public void shouldDeleteExistingGroup() throws Exception {
+		MockHttpServletResponse response = delete("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isOk());
+		
+		response = get("/Group/" + COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, statusEquals(HttpStatus.GONE));
+	}
+	
+	@Test
+	public void shouldReturnNotFoundWhenDeletingNonExistentGroup() throws Exception {
+		MockHttpServletResponse response = delete("/Group/" + BAD_COHORT_UUID).accept(FhirMediaTypes.JSON).go();
+		
+		assertThat(response, isNotFound());
+		assertThat(response.getContentType(), is(FhirMediaTypes.JSON.toString()));
+		assertThat(response.getContentAsString(), notNullValue());
+		
+		OperationOutcome operationOutcome = readOperationOutcome(response);
+		
+		assertThat(operationOutcome, notNullValue());
+		assertThat(operationOutcome.hasIssue(), is(true));
+	}
+}
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirCohortDaoImplTest_initial_data.xml b/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirCohortDaoImplTest_initial_data.xml
new file mode 100644
index 000000000..4527f4c00
--- /dev/null
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/api/dao/impl/FhirCohortDaoImplTest_initial_data.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+    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.
+-->
+<dataset>
+    <cohort cohort_id="1" name="John's patientList" description="cohort voided" creator="1" date_created="2005-01-01 00:00:00.0" voided="true" voided_by="1" date_voided="2005-01-01 00:00:00.0" void_reason="This is voided" uuid="985ff1a2-c2ef-49fd-836f-8a1d936d9ef9"/>
+    <cohort cohort_id="2" name="Covid19 patients" description="Covid19 patients" creator="1" date_created="2005-01-01 00:00:00.0" voided="false" uuid="1d64befb-3b2e-48e5-85f5-353d43e23e46"/>
+</dataset>
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_create.json b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_create.json
new file mode 100644
index 000000000..af17e553b
--- /dev/null
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_create.json
@@ -0,0 +1,27 @@
+{
+  "resourceType": "Group",
+  "extension": [
+    {
+      "url": "http://fhir.openmrs.org/ext/group/description",
+      "valueString": "Patients with at least one encounter"
+    }
+  ],
+  "active": true,
+  "type": "person",
+  "actual": true,
+  "name": "Test group",
+  "quantity": 1,
+  "member": [
+    {
+      "id": "d275f94d-a093-42c8-a4b1-24f90f7c0fb9",
+      "entity": {
+        "reference": "Patient/61b38324-e2fd-4feb-95b7-9e9a2a4400df",
+        "display": "John Doe (OpenMRS ID: 1234-4)"
+      },
+      "period": {
+        "start": "2019-10-16T00:00:28+03:00"
+      },
+      "inactive": false
+    }
+  ]
+}
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_create.xml b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_create.xml
new file mode 100644
index 000000000..34b805827
--- /dev/null
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_create.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+    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.
+-->
+<Group xmlns="http://hl7.org/fhir">
+    <extension url="http://fhir.openmrs.org/ext/group/description">
+        <valueString value="Patients with at least one encounter"/>
+    </extension>
+    <active value="true"/>
+    <type value="person"/>
+    <actual value="true"/>
+    <name value="Test group"/>
+    <quantity value="1"/>
+    <member>
+        <entity>
+            <reference value="Patient/61b38324-e2fd-4feb-95b7-9e9a2a4400df" />
+        </entity>
+        <period>
+            <start value="2019-10-16T00:00:28+03:00"/>
+        </period>
+        <inactive value="false" />
+    </member>
+</Group>
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_update.json b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_update.json
new file mode 100644
index 000000000..b692046ee
--- /dev/null
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_update.json
@@ -0,0 +1,28 @@
+{
+  "resourceType": "Group",
+  "id": "1d64befb-3b2e-48e5-85f5-353d43e23e46",
+  "extension": [
+    {
+      "url": "http://fhir.openmrs.org/ext/group/description",
+      "valueString": "Patients with at least one encounter"
+    }
+  ],
+  "active": true,
+  "type": "person",
+  "actual": true,
+  "name": "Test group",
+  "quantity": 1,
+  "member": [
+    {
+      "id": "d275f94d-a093-42c8-a4b1-24f90f7c0fb9",
+      "entity": {
+        "reference": "Patient/61b38324-e2fd-4feb-95b7-9e9a2a4400df",
+        "display": "John Doe (OpenMRS ID: 1234-4)"
+      },
+      "period": {
+        "start": "2019-10-16T00:00:28+03:00"
+      },
+      "inactive": false
+    }
+  ]
+}
diff --git a/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_update.xml b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_update.xml
new file mode 100644
index 000000000..12b7e2c90
--- /dev/null
+++ b/test-data/src/test/resources/org/openmrs/module/fhir2/providers/GroupWebTest_update.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+    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.
+-->
+<Group xmlns="http://hl7.org/fhir">
+    <id value="1d64befb-3b2e-48e5-85f5-353d43e23e46"/>
+    <extension url="http://fhir.openmrs.org/ext/group/description">
+        <valueString value="Patients with at least one encounter"/>
+    </extension>
+    <active value="true"/>
+    <type value="person"/>
+    <actual value="true"/>
+    <name value="Test group"/>
+    <quantity value="1"/>
+    <member>
+        <entity>
+            <reference value="Patient/61b38324-e2fd-4feb-95b7-9e9a2a4400df" />
+        </entity>
+        <period>
+            <start value="2019-10-16T00:00:28+03:00"/>
+        </period>
+        <inactive value="false" />
+    </member>
+</Group>

From 6227bee029284b776df57aa8f88380d781d3197c Mon Sep 17 00:00:00 2001
From: Ian <52504170+ibacher@users.noreply.github.com>
Date: Fri, 5 Feb 2021 17:57:46 +0000
Subject: [PATCH 13/18] Update to HAPI 5.2.1 (#323)

---
 api/pom.xml | 4 ++++
 pom.xml     | 8 +++++++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/api/pom.xml b/api/pom.xml
index 0f54b0d6b..9c91e63d7 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -36,6 +36,10 @@
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-client</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>org.fhir</groupId>
+			<artifactId>ucum</artifactId>
+		</dependency>
 		<dependency>
 			<groupId>org.projectlombok</groupId>
 			<artifactId>lombok</artifactId>
diff --git a/pom.xml b/pom.xml
index 70d247924..1c99d4905 100644
--- a/pom.xml
+++ b/pom.xml
@@ -273,6 +273,11 @@
                 <version>${hapifhirVersion}</version>
                 <scope>test</scope>
             </dependency>
+            <dependency>
+                <groupId>org.fhir</groupId>
+                <artifactId>ucum</artifactId>
+                <version>${ucumVersion}</version>
+            </dependency>
             <dependency>
                 <groupId>org.projectlombok</groupId>
                 <artifactId>lombok</artifactId>
@@ -724,7 +729,8 @@
         <lombokVersion>1.18.16</lombokVersion>
         <openmrsPlatformVersion>2.0.5</openmrsPlatformVersion>
         <openmrsPlatformToolsVersion>2.0.5</openmrsPlatformToolsVersion>
-        <hapifhirVersion>5.0.0</hapifhirVersion>
+        <hapifhirVersion>5.2.1</hapifhirVersion>
+        <ucumVersion>1.0.3</ucumVersion>
     </properties>
 
     <profiles>

From c1519ea6049abae0278eb052db897620e2444db3 Mon Sep 17 00:00:00 2001
From: Ian <ian_bacher@brown.edu>
Date: Mon, 8 Feb 2021 09:08:48 -0500
Subject: [PATCH 14/18] Return empty list when requesting results after last

Previously, this code always returned the last item
---
 .../api/search/SearchQueryBundleProvider.java |  8 +++--
 .../search/TwoSearchQueryBundleProvider.java  |  9 ++++--
 .../search/SearchQueryBundleProviderTest.java | 29 +++++++++++++++++--
 3 files changed, 39 insertions(+), 7 deletions(-)

diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/search/SearchQueryBundleProvider.java b/api/src/main/java/org/openmrs/module/fhir2/api/search/SearchQueryBundleProvider.java
index a605d9d52..ddb8b8e5f 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/search/SearchQueryBundleProvider.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/search/SearchQueryBundleProvider.java
@@ -91,10 +91,14 @@ public List<IBaseResource> getResources(int fromIndex, int toIndex) {
 			firstResult = fromIndex;
 		}
 		
+		Integer size = size();
+		if (size != null && firstResult > size) {
+			return Collections.emptyList();
+		}
+		
 		// NPE-safe unboxing
 		int lastResult = Integer.MAX_VALUE;
-		Integer lastResultHolder = size();
-		lastResult = lastResultHolder == null ? lastResult : lastResultHolder;
+		lastResult = size == null ? lastResult : size;
 		
 		if (toIndex - firstResult > 0) {
 			lastResult = Math.min(lastResult, toIndex);
diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/search/TwoSearchQueryBundleProvider.java b/api/src/main/java/org/openmrs/module/fhir2/api/search/TwoSearchQueryBundleProvider.java
index a957d0b59..f40a8767d 100644
--- a/api/src/main/java/org/openmrs/module/fhir2/api/search/TwoSearchQueryBundleProvider.java
+++ b/api/src/main/java/org/openmrs/module/fhir2/api/search/TwoSearchQueryBundleProvider.java
@@ -13,6 +13,7 @@
 import javax.annotation.Nullable;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Optional;
@@ -69,10 +70,14 @@ public List<IBaseResource> getResources(int fromIndex, int toIndex) {
 			firstResult = fromIndex;
 		}
 		
+		Integer size = size();
+		if (size != null && firstResult > size) {
+			return Collections.emptyList();
+		}
+		
 		// NPE-safe unboxing
 		int lastResult = Integer.MAX_VALUE;
-		Integer lastResultHolder = size();
-		lastResult = lastResultHolder == null ? lastResult : lastResultHolder;
+		lastResult = size == null ? lastResult : size;
 		
 		if (toIndex - firstResult > 0) {
 			lastResult = Math.min(lastResult, toIndex);
diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/search/SearchQueryBundleProviderTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/search/SearchQueryBundleProviderTest.java
index 270afaddc..46def8dc2 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/search/SearchQueryBundleProviderTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/search/SearchQueryBundleProviderTest.java
@@ -10,15 +10,22 @@
 package org.openmrs.module.fhir2.api.search;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
+import java.util.List;
 
 import org.exparity.hamcrest.date.DateMatchers;
+import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.instance.model.api.IPrimitiveType;
 import org.hl7.fhir.r4.model.Observation;
 import org.junit.Before;
@@ -31,6 +38,7 @@
 import org.openmrs.module.fhir2.api.dao.FhirObservationDao;
 import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
 import org.openmrs.module.fhir2.api.translators.ObservationTranslator;
+import org.openmrs.module.fhir2.api.util.FhirUtils;
 
 @RunWith(MockitoJUnitRunner.class)
 public class SearchQueryBundleProviderTest {
@@ -51,8 +59,7 @@ public class SearchQueryBundleProviderTest {
 	
 	@Before
 	public void setup() {
-		SearchParameterMap theParams = new SearchParameterMap();
-		searchQueryBundleProvider = new SearchQueryBundleProvider<>(theParams, observationDao, translator,
+		searchQueryBundleProvider = new SearchQueryBundleProvider<>(new SearchParameterMap(), observationDao, translator,
 		        globalPropertyService, searchQueryInclude);
 	}
 	
@@ -72,7 +79,23 @@ public void shouldGetDatePublished() {
 	}
 	
 	@Test
-	public void shouldReturnRandomUuid() {
+	public void shouldReturnEmptyListWhenNoResults() {
+		when(observationDao.getSearchResultUuids(any())).thenReturn(Collections.emptyList());
+		List<IBaseResource> resources = searchQueryBundleProvider.getResources(0, 10);
+		assertThat(resources, empty());
+	}
+	
+	@Test
+	public void shouldReturnEmptyListWhenRequestingTooManyResults() {
+		when(observationDao.getSearchResultUuids(any())).thenReturn(Arrays.asList(FhirUtils.newUuid(), FhirUtils.newUuid()));
+		List<IBaseResource> resources = searchQueryBundleProvider.getResources(3, 13);
+		assertThat(resources, empty());
+	}
+	
+	@Test
+	public void shouldReturnDifferentUuid() {
 		assertThat(searchQueryBundleProvider.getUuid(), notNullValue());
+		assertThat(searchQueryBundleProvider.getUuid(), not(equalTo(new SearchQueryBundleProvider<>(new SearchParameterMap(),
+		        observationDao, translator, globalPropertyService, searchQueryInclude).getUuid())));
 	}
 }

From 8b859f9b55d588489d2216f6cabe92e4ea5bd61e Mon Sep 17 00:00:00 2001
From: Ian <ian_bacher@brown.edu>
Date: Mon, 8 Feb 2021 09:15:56 -0500
Subject: [PATCH 15/18] Bug fix for upgrade to HAPI 5.2.1

---
 pom.xml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/pom.xml b/pom.xml
index 1c99d4905..698eac8d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -212,6 +212,12 @@
                 <groupId>ca.uhn.hapi.fhir</groupId>
                 <artifactId>hapi-fhir-server</artifactId>
                 <version>${hapifhirVersion}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.springframework</groupId>
+                        <artifactId>spring-messaging</artifactId>
+                    </exclusion>
+                </exclusions>
             </dependency>
             <dependency>
                 <groupId>ca.uhn.hapi.fhir</groupId>

From ef7a47490f4827cf6733600e45cdb9ac2a89685e Mon Sep 17 00:00:00 2001
From: Ian <ian_bacher@brown.edu>
Date: Tue, 9 Feb 2021 08:24:53 -0500
Subject: [PATCH 16/18] Revert "Mark FHIR2 aware of some basic configuration
 modules"

This reverts commit 3a9194218d120f6c44d8709186e05109eb948e5b.
---
 omod/src/main/resources/config.xml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/omod/src/main/resources/config.xml b/omod/src/main/resources/config.xml
index 5136336fd..077dacbc1 100644
--- a/omod/src/main/resources/config.xml
+++ b/omod/src/main/resources/config.xml
@@ -47,8 +47,6 @@
 
 	<aware_of_modules>
 		<aware_of_module>org.openmrs.module.legacyui</aware_of_module>
-		<aware_of_module>org.openmrs.module.referencemetadata</aware_of_module>
-		<aware_of_module>org.openmrs.module.initializer</aware_of_module>
 	</aware_of_modules>
 
 	<servlet>

From 6ca1460973d07208a0bf213fa3b7320ac499934d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 10 Feb 2021 13:21:42 +0000
Subject: [PATCH 17/18] Bump jackson-databind from 2.9.0 to 2.9.10.7 in
 /integration-tests-2.2 (#318)

Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.9.0 to 2.9.10.7.
- [Release notes](https://github.com/FasterXML/jackson/releases)
- [Commits](https://github.com/FasterXML/jackson/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 integration-tests-2.2/pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/integration-tests-2.2/pom.xml b/integration-tests-2.2/pom.xml
index 06d12ae08..508a3f265 100644
--- a/integration-tests-2.2/pom.xml
+++ b/integration-tests-2.2/pom.xml
@@ -76,7 +76,7 @@
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <version>2.9.0</version>
+            <version>2.9.10.7</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>

From cb69c3e9557b74fe69eb86be2f49b07657ac9c27 Mon Sep 17 00:00:00 2001
From: Ankit kumar <49350053+theanandankit@users.noreply.github.com>
Date: Wed, 10 Feb 2021 18:53:26 +0530
Subject: [PATCH 18/18] FM2-327: Improve the Tests for
 FhirRelatedPersonDaoImplTest (#315)

Co-authored-by: Ian <52504170+ibacher@users.noreply.github.com>
---
 .../api/dao/impl/FhirRelatedPersonDaoImplTest.java | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirRelatedPersonDaoImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirRelatedPersonDaoImplTest.java
index 80d24915a..2970736fd 100644
--- a/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirRelatedPersonDaoImplTest.java
+++ b/api/src/test/java/org/openmrs/module/fhir2/api/dao/impl/FhirRelatedPersonDaoImplTest.java
@@ -31,6 +31,10 @@ public class FhirRelatedPersonDaoImplTest extends BaseModuleContextSensitiveTest
 	
 	private static final String BAD_RELATIONSHIP_UUID = "d4c91630-8563-481b-8efa-48e10c139w6e";
 	
+	private static final String PERSON_A_UUID = "61b38324-e2fd-4feb-95b7-9e9a2a4400df";
+	
+	private static final String PERSON_B_UUID = "5c521595-4e12-46b0-8248-b8f2d3697766";
+	
 	private static final String RELATIONSHIP_DATA_XML = "org/openmrs/module/fhir2/api/dao/impl/FhirRelatedPersonDaoImplTest_intial_data.xml";
 	
 	@Autowired
@@ -60,4 +64,14 @@ public void getRelationshipWithWrongUuid_shouldReturnNull() {
 		assertThat(relationship, nullValue());
 	}
 	
+	@Test
+	public void getRelationshipWithUuid_shouldReturnPersonAAndPersonB() {
+		Relationship relationship = relatedPersonDao.get(RELATIONSHIP_UUID);
+		assertThat(relationship, notNullValue());
+		assertThat(relationship.getPersonA(), notNullValue());
+		assertThat(relationship.getPersonB(), notNullValue());
+		assertThat(relationship.getPersonA().getUuid(), equalTo(PERSON_A_UUID));
+		assertThat(relationship.getPersonB().getUuid(), equalTo(PERSON_B_UUID));
+	}
+	
 }