diff --git a/API_README.md b/API_README.md index 876359e9a7e..4c349cd4d77 100644 --- a/API_README.md +++ b/API_README.md @@ -80,6 +80,7 @@ Finally, APIs which are integrated with the new `handleProcessingResult` method - [FHIR MedicationStatement API](FHIR_README.md#get-fhirmedicationstatement) - [FHIR Medication API](FHIR_README.md#get-fhirmedication) - [FHIR MedicationStatement API](FHIR_README.md#get-fhirmedicationstatement) + - [FHIR Location API](FHIR_README.md#get-fhirlocation) - [Portal FHIR API Endpoints](FHIR_README.md#portalfhir-endpoints) - [Patient API](FHIR_README.md#get-portalfhirpatient) - [Dev notes](API_README.md#dev-notes) diff --git a/FHIR_README.md b/FHIR_README.md index c2d5e6f94d6..1d182640750 100644 --- a/FHIR_README.md +++ b/FHIR_README.md @@ -46,6 +46,7 @@ Database Result -> Service Component -> FHIR Service Component -> Parse OpenEMR - [FHIR MedicationStatement API](FHIR_README.md#get-fhirmedicationstatement) - [FHIR Medication API](FHIR_README.md#get-fhirmedication) - [FHIR MedicationStatement API](FHIR_README.md#get-fhirmedicationstatement) + - [FHIR Location API](FHIR_README.md#get-fhirlocation) - [Portal FHIR API Endpoints](FHIR_README.md#portalfhir-endpoints) - [Patient API](FHIR_README.md#get-portalfhirpatient) - [Todos](FHIR_README.md#project-management) @@ -485,6 +486,21 @@ Request: ```sh curl -X GET 'http://localhost:8300/apis/fhir/Medication/9109890a-6756-44c1-a82d-bdfac91c7424' ``` +#### GET /fhir/Location + +Request: + +```sh +curl -X GET 'http://localhost:8300/apis/fhir/Location' +``` + +#### GET /fhir/Location/:uuid + +Request: + +```sh +curl -X GET 'http://localhost:8300/apis/fhir/Location/90f3d0e9-2a19-453b-84bd-1fa2b533f96c' +``` ### /portalfhir/ Endpoints diff --git a/_rest_routes.inc.php b/_rest_routes.inc.php index 24bfd2af0bb..0a9b9412777 100644 --- a/_rest_routes.inc.php +++ b/_rest_routes.inc.php @@ -400,6 +400,7 @@ use OpenEMR\RestControllers\FHIR\FhirEncounterRestController; use OpenEMR\RestControllers\FHIR\FhirObservationRestController; use OpenEMR\RestControllers\FHIR\FhirImmunizationRestController; +use OpenEMR\RestControllers\FHIR\FhirLocationRestController; use OpenEMR\RestControllers\FHIR\FhirMedicationRestController; use OpenEMR\RestControllers\FHIR\FhirMedicationStatementRestController; use OpenEMR\RestControllers\FHIR\FhirOrganizationRestController; @@ -549,6 +550,14 @@ "GET /fhir/Medication/:id" => function ($id) { RestConfig::authorization_check("patients", "med"); return (new FhirMedicationRestController(null))->getOne($id); + }, + "GET /fhir/Location" => function () { + RestConfig::authorization_check("patients", "med"); + return (new FhirLocationRestController())->getAll($_GET); + }, + "GET /fhir/Location/:uuid" => function ($uuid) { + RestConfig::authorization_check("patients", "med"); + return (new FhirLocationRestController())->getOne($uuid); } ); diff --git a/src/RestControllers/FHIR/FhirLocationRestController.php b/src/RestControllers/FHIR/FhirLocationRestController.php new file mode 100644 index 00000000000..bcb188674e3 --- /dev/null +++ b/src/RestControllers/FHIR/FhirLocationRestController.php @@ -0,0 +1,64 @@ + + * @copyright Copyright (c) 2020 Yash Bothra + * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 + */ + +namespace OpenEMR\RestControllers\FHIR; + +use OpenEMR\Services\FHIR\FhirLocationService; +use OpenEMR\Services\FHIR\FhirResourcesService; +use OpenEMR\RestControllers\RestControllerHelper; +use OpenEMR\FHIR\R4\FHIRResource\FHIRBundle\FHIRBundleEntry; + +class FhirLocationRestController +{ + private $fhirLocationService; + private $fhirService; + + public function __construct() + { + $this->fhirLocationService = new FhirLocationService(); + $this->fhirService = new FhirResourcesService(); + } + + /** + * Queries for a single FHIR location resource by FHIR id + * @param $fhirId The FHIR location resource id (uuid) + * @returns 200 if the operation completes successfully + */ + public function getOne($fhirId) + { + $processingResult = $this->fhirLocationService->getOne($fhirId); + return RestControllerHelper::handleProcessingResult($processingResult, 200); + } + + /** + * Queries for FHIR location resources using various search parameters. + * Search parameters include: + * - patient (puuid) + * @return FHIR bundle with query results, if found + */ + public function getAll($searchParams) + { + $processingResult = $this->fhirLocationService->getAll($searchParams); + $bundleEntries = array(); + foreach ($processingResult->getData() as $index => $searchResult) { + $bundleEntry = [ + 'fullUrl' => \RestConfig::$REST_FULL_URL . '/' . $searchResult->getId(), + 'resource' => $searchResult + ]; + $fhirBundleEntry = new FHIRBundleEntry($bundleEntry); + array_push($bundleEntries, $fhirBundleEntry); + } + $bundleSearchResult = $this->fhirService->createBundle('Location', $bundleEntries, false); + $searchResponseBody = RestControllerHelper::responseHandler($bundleSearchResult, null, 200); + return $searchResponseBody; + } +} diff --git a/src/Services/FHIR/FhirLocationService.php b/src/Services/FHIR/FhirLocationService.php new file mode 100644 index 00000000000..2940499cfbe --- /dev/null +++ b/src/Services/FHIR/FhirLocationService.php @@ -0,0 +1,152 @@ + + * @copyright Copyright (c) 2020 Yash Bothra + * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 + */ + +namespace OpenEMR\Services\FHIR; + +use OpenEMR\FHIR\R4\FHIRDomainResource\FHIRLocation; +use OpenEMR\FHIR\R4\FHIRElement\FHIRContactPoint; +use OpenEMR\FHIR\R4\FHIRElement\FHIRId; +use OpenEMR\Services\LocationService; + +class FhirLocationService extends FhirServiceBase +{ + /** + * @var LocationService + */ + private $locationService; + + public function __construct() + { + parent::__construct(); + $this->locationService = new LocationService(); + } + + /** + * Returns an array mapping FHIR Location Resource search parameters to OpenEMR Location search parameters + * @return array The search parameters + */ + protected function loadSearchParameters() + { + return []; + } + + /** + * Parses an OpenEMR location record, returning the equivalent FHIR Location Resource + * + * @param array $dataRecord The source OpenEMR data record + * @param boolean $encode Indicates if the returned resource is encoded into a string. Defaults to false. + * @return FHIRLocation + */ + public function parseOpenEMRRecord($dataRecord = array(), $encode = false) + { + $locationResource = new FHIRLocation(); + + $meta = array('versionId' => '1', 'lastUpdated' => gmdate('c')); + $locationResource->setMeta($meta); + + $id = new FHIRId(); + $id->setValue($dataRecord['uuid']); + $locationResource->setId($id); + + $locationResource->setStatus("active"); + + $locationResource->setAddress(array( + 'line' => [$dataRecord['street']], + 'city' => $dataRecord['city'], + 'state' => $dataRecord['state'], + 'postalCode' => $dataRecord['postal_code'], + )); + + if (!empty($dataRecord['name'] && $dataRecord['name'] != "`s Home")) { + $locationResource->setName($dataRecord['name']); + } + + if (!empty($dataRecord['phone'])) { + $phone = new FHIRContactPoint(); + $phone->setSystem('phone'); + $phone->setValue($dataRecord['phone']); + $locationResource->addTelecom($phone); + } + + if (!empty($dataRecord['fax'])) { + $fax = new FHIRContactPoint(); + $fax->setSystem('fax'); + $fax->setValue($dataRecord['fax']); + $locationResource->addTelecom($fax); + } + + if (!empty($dataRecord['website'])) { + $url = new FHIRContactPoint(); + $url->setSystem('website'); + $url->setValue($dataRecord['website']); + $locationResource->addTelecom($url); + } + + if (!empty($dataRecord['email'])) { + $email = new FHIRContactPoint(); + $email->setSystem('email'); + $email->setValue($dataRecord['email']); + $locationResource->addTelecom($email); + } + + if ($encode) { + return json_encode($locationResource); + } else { + return $locationResource; + } + } + + /** + * Performs a FHIR Location Resource lookup by FHIR Resource ID + * + * @param $fhirResourceId //The OpenEMR record's FHIR Location Resource ID. + */ + public function getOne($fhirResourceId) + { + $processingResult = $this->locationService->getOne($fhirResourceId); + if (!$processingResult->hasErrors()) { + if (count($processingResult->getData()) > 0) { + $openEmrRecord = $processingResult->getData()[0]; + $fhirRecord = $this->parseOpenEMRRecord($openEmrRecord); + $processingResult->setData([]); + $processingResult->addData($fhirRecord); + } + } + return $processingResult; + } + + /** + * Searches for OpenEMR records using OpenEMR search parameters + * + * @param array openEMRSearchParameters OpenEMR search fields + * @return ProcessingResult + */ + public function searchForOpenEMRRecords($openEMRSearchParameters) + { + return $this->locationService->getAll($openEMRSearchParameters, false); + } + + public function parseFhirResource($fhirResource = array()) + { + // TODO: If Required in Future + } + + public function insertOpenEMRRecord($openEmrRecord) + { + // TODO: If Required in Future + } + + public function updateOpenEMRRecord($fhirResourceId, $updatedOpenEMRRecord) + { + // TODO: If Required in Future + } +} diff --git a/src/Services/LocationService.php b/src/Services/LocationService.php new file mode 100644 index 00000000000..f30d8847e0b --- /dev/null +++ b/src/Services/LocationService.php @@ -0,0 +1,187 @@ + + * @copyright Copyright (c) 2020 Yash Bothra + * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 + */ + +namespace OpenEMR\Services; + +use OpenEMR\Common\Uuid\UuidRegistry; +use OpenEMR\Common\Uuid\UuidMapping; +use OpenEMR\Validators\BaseValidator; +use OpenEMR\Validators\ProcessingResult; + +class LocationService extends BaseService +{ + private const PATIENT_TABLE = "patient_data"; + private const PRACTITIONER_TABLE = "users"; + private const FACILITY_TABLE = "facility"; + private $uuidMapping; + + /** + * Default constructor. + */ + public function __construct() + { + $this->uuidMapping = new UuidMapping(); + (new UuidRegistry(['table_name' => self::PATIENT_TABLE]))->createMissingUuids(); + $this->uuidMapping->createMissingResourceUuids("Location", self::PATIENT_TABLE); + (new UuidRegistry(['table_name' => self::PRACTITIONER_TABLE]))->createMissingUuids(); + $this->uuidMapping->createMissingResourceUuids("Location", self::PRACTITIONER_TABLE); + (new UuidRegistry(['table_name' => self::FACILITY_TABLE]))->createMissingUuids(); + $this->uuidMapping->createMissingResourceUuids("Location", self::FACILITY_TABLE); + } + + /** + * Returns a list of locations matching optional search criteria. + * Search criteria is conveyed by array where key = field/column name, value = field value. + * If no search criteria is provided, all records are returned. + * + * @param $search search array parameters + * @param $isAndCondition specifies if AND condition is used for multiple criteria. Defaults to true. + * @return ProcessingResult which contains validation messages, internal error messages, and the data + * payload. + */ + public function getAll($search = array(), $isAndCondition = true) + { + $sqlBindArray = array(); + + $sql = 'SELECT location.*, uuid_mapping.uuid FROM + (SELECT + uuid as target_uuid, + CONCAT(fname,"\'s Home") as name, + street, + city, + postal_code, + state, + country_code, + phone_cell as phone, + null as fax, + null as website, + email from patient_data + UNION SELECT + uuid as target_uuid, + name, + street, + city, + postal_code, + state, + country_code, + phone, + fax, + website, + email from facility + UNION SELECT + uuid as target_uuid, + CONCAT(fname,"\'s Home") as name, + street, + city, + zip as postal_code, + state, + null as country_code, + phone, + fax, + url as website, + email from users) as location + LEFT JOIN uuid_mapping ON uuid_mapping.target_uuid=location.target_uuid'; + + if (!empty($search)) { + $sql .= ' WHERE '; + $whereClauses = array(); + foreach ($search as $fieldName => $fieldValue) { + array_push($whereClauses, $fieldName . ' = ?'); + array_push($sqlBindArray, $fieldValue); + } + $sqlCondition = ($isAndCondition == true) ? 'AND' : 'OR'; + $sql .= implode(' ' . $sqlCondition . ' ', $whereClauses); + } + + $statementResults = sqlStatement($sql, $sqlBindArray); + + $processingResult = new ProcessingResult(); + while ($row = sqlFetchArray($statementResults)) { + $row['uuid'] = UuidRegistry::uuidToString($row['uuid']); + $processingResult->addData($row); + } + + return $processingResult; + } + + /** + * Returns a single location record by id. + * @param $uuid - The location uuid identifier in string format. + * @return ProcessingResult which contains validation messages, internal error messages, and the data + * payload. + */ + public function getOne($uuid) + { + $processingResult = new ProcessingResult(); + + $isValid = BaseValidator::validateId( + "uuid", + "uuid_mapping", + $uuid, + true + ); + + if ($isValid !== true) { + $validationMessages = [ + 'uuid' => ["invalid or nonexisting value" => " value " . $uuid] + ]; + $processingResult->setValidationMessages($validationMessages); + return $processingResult; + } + + $sql = 'SELECT location.*, uuid_mapping.uuid FROM + (SELECT + uuid as target_uuid, + CONCAT(fname,"\'s Home") as name, + street, + city, + postal_code, + state, + country_code, + phone_cell as phone, + null as fax, + null as website, + email from patient_data + UNION SELECT + uuid as target_uuid, + name, + street, + city, + postal_code, + state, + country_code, + phone, + fax, + website, + email from facility + UNION SELECT + uuid as target_uuid, + CONCAT(fname,"\'s Home") as name, + street, + city, + zip as postal_code, + state, + null as country_code, + phone, + fax, + url as website, + email from users) as location + LEFT JOIN uuid_mapping ON uuid_mapping.target_uuid=location.target_uuid + WHERE uuid_mapping.uuid=?'; + + $uuidBinary = UuidRegistry::uuidToBytes($uuid); + $sqlResult = sqlQuery($sql, [$uuidBinary]); + $sqlResult['uuid'] = UuidRegistry::uuidToString($sqlResult['uuid']); + $processingResult->addData($sqlResult); + return $processingResult; + } +}