7 * @link http://www.open-emr.org
8 * @author Victor Kofia <victor.kofia@gmail.com>
9 * @author Brady Miller <brady.g.miller@gmail.com>
10 * @author Jerry Padgett <sjpadgett@gmail.com>
11 * @copyright Copyright (c) 2017 Victor Kofia <victor.kofia@gmail.com>
12 * @copyright Copyright (c) 2018 Brady Miller <brady.g.miller@gmail.com>
13 * @copyright Copyright (c) 2020 Jerry Padgett <sjpadgett@gmail.com>
14 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
17 namespace OpenEMR\Services
;
19 use OpenEMR\Common\Uuid\UuidRegistry
;
20 use OpenEMR\Events\Patient\BeforePatientCreatedEvent
;
21 use OpenEMR\Events\Patient\BeforePatientUpdatedEvent
;
22 use OpenEMR\Events\Patient\PatientCreatedEvent
;
23 use OpenEMR\Events\Patient\PatientUpdatedEvent
;
24 use OpenEMR\Services\Search\ISearchField
;
25 use OpenEMR\Services\Search\TokenSearchField
;
26 use OpenEMR\Services\Search\SearchModifier
;
27 use OpenEMR\Services\Search\StringSearchField
;
28 use OpenEMR\Services\Search\TokenSearchValue
;
29 use OpenEMR\Validators\PatientValidator
;
30 use OpenEMR\Validators\ProcessingResult
;
32 class PatientService
extends BaseService
34 const TABLE_NAME
= 'patient_data';
37 * In the case where a patient doesn't have a picture uploaded,
38 * this value will be returned so that the document controller
39 * can return an empty response.
41 private $patient_picture_fallback_id = -1;
43 private $patientValidator;
46 * Default constructor.
48 public function __construct()
50 parent
::__construct(self
::TABLE_NAME
);
51 $this->patientValidator
= new PatientValidator();
55 * TODO: This should go in the ChartTrackerService and doesn't have to be static.
57 * @param $pid unique patient id
60 public static function getChartTrackerInformationActivity($pid)
62 $sql = "SELECT ct.ct_when,
69 FROM chart_tracker AS ct
70 LEFT OUTER JOIN users AS u ON u.id = ct.ct_userid
72 ORDER BY ct.ct_when DESC";
73 return sqlStatement($sql, array($pid));
77 * TODO: This should go in the ChartTrackerService and doesn't have to be static.
81 public static function getChartTrackerInformation()
83 $sql = "SELECT ct.ct_when,
92 FROM chart_tracker AS ct
93 JOIN cttemp ON cttemp.ct_pid = ct.ct_pid AND cttemp.ct_when = ct.ct_when
94 LEFT OUTER JOIN users AS u ON u.id = ct.ct_userid
95 LEFT OUTER JOIN patient_data AS p ON p.pid = ct.ct_pid
96 WHERE ct.ct_userid != 0
98 return sqlStatement($sql);
101 public function getFreshPid()
103 $pid = sqlQuery("SELECT MAX(pid)+1 AS pid FROM patient_data");
104 return $pid['pid'] === null ?
1 : intval($pid['pid']);
108 * Insert a patient record into the database
110 * returns the newly-created patient data array, or false in the case of
111 * an error with the sql insert
116 public function databaseInsert($data)
118 $freshPid = $this->getFreshPid();
119 $data['pid'] = $freshPid;
120 $data['uuid'] = (new UuidRegistry(['table_name' => 'patient_data']))->createUuid();
122 // The 'date' is the updated-date, and 'regdate' is the created-date
123 // so set both to the current datetime.
124 $data['date'] = date("Y-m-d H:i:s");
125 $data['regdate'] = date("Y-m-d H:i:s");
126 if (empty($data['pubpid'])) {
127 $data['pubpid'] = $freshPid;
130 // Before a patient is inserted, fire the "before patient created" event so listeners can do extra processing
131 $beforePatientCreatedEvent = new BeforePatientCreatedEvent($data);
132 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(BeforePatientCreatedEvent
::EVENT_HANDLE
, $beforePatientCreatedEvent, 10);
133 $data = $beforePatientCreatedEvent->getPatientData();
135 $query = $this->buildInsertColumns($data);
136 $sql = " INSERT INTO patient_data SET ";
137 $sql .= $query['set'];
139 $results = sqlInsert(
144 // Tell subscribers that a new patient has been created
145 $patientCreatedEvent = new PatientCreatedEvent($data);
146 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(PatientCreatedEvent
::EVENT_HANDLE
, $patientCreatedEvent, 10);
148 // If we have a result-set from our insert, return the PID,
149 // otherwise return false
158 * Inserts a new patient record.
160 * @param $data The patient fields (array) to insert.
161 * @return ProcessingResult which contains validation messages, internal error messages, and the data
164 public function insert($data)
166 $processingResult = $this->patientValidator
->validate($data, PatientValidator
::DATABASE_INSERT_CONTEXT
);
168 if (!$processingResult->isValid()) {
169 return $processingResult;
172 $data = $this->databaseInsert($data);
174 if (false !== $data['pid']) {
175 $processingResult->addData(array(
176 'pid' => $data['pid'],
177 'uuid' => UuidRegistry
::uuidToString($data['uuid'])
180 $processingResult->addInternalError("error processing SQL Insert");
183 return $processingResult;
187 * Do a database update using the pid from the input
190 * Return the data that was updated into the database,
191 * or false if there was an error with the update
196 public function databaseUpdate($data)
198 // Get the data before update to send to the event listener
199 $dataBeforeUpdate = $this->findByPid($data['pid']);
201 // The `date` column is treated as an updated_date
202 $data['date'] = date("Y-m-d H:i:s");
203 $table = PatientService
::TABLE_NAME
;
205 // Fire the "before patient updated" event so listeners can do extra processing before data is updated
206 $beforePatientUpdatedEvent = new BeforePatientUpdatedEvent($data);
207 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(BeforePatientUpdatedEvent
::EVENT_HANDLE
, $beforePatientUpdatedEvent, 10);
208 $data = $beforePatientUpdatedEvent->getPatientData();
210 $query = $this->buildUpdateColumns($data);
211 $sql = " UPDATE $table SET ";
212 $sql .= $query['set'];
213 $sql .= " WHERE `pid` = ?";
215 array_push($query['bind'], $data['pid']);
216 $sqlResult = sqlStatement($sql, $query['bind']);
219 // Tell subscribers that a new patient has been updated
220 $patientUpdatedEvent = new PatientUpdatedEvent($dataBeforeUpdate, $data);
221 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(PatientUpdatedEvent
::EVENT_HANDLE
, $patientUpdatedEvent, 10);
230 * Updates an existing patient record.
232 * @param $puuidString - The patient uuid identifier in string format used for update.
233 * @param $data - The updated patient data fields
234 * @return ProcessingResult which contains validation messages, internal error messages, and the data
237 public function update($puuidString, $data)
239 $data["uuid"] = $puuidString;
240 $processingResult = $this->patientValidator
->validate($data, PatientValidator
::DATABASE_UPDATE_CONTEXT
);
241 if (!$processingResult->isValid()) {
242 return $processingResult;
245 // Get the data before update to send to the event listener
246 $dataBeforeUpdate = $this->getOne($puuidString);
248 // The `date` column is treated as an updated_date
249 $data['date'] = date("Y-m-d H:i:s");
251 // Fire the "before patient updated" event so listeners can do extra processing before data is updated
252 $beforePatientUpdatedEvent = new BeforePatientUpdatedEvent($data);
253 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(BeforePatientUpdatedEvent
::EVENT_HANDLE
, $beforePatientUpdatedEvent, 10);
254 $data = $beforePatientUpdatedEvent->getPatientData();
256 $query = $this->buildUpdateColumns($data);
257 $sql = " UPDATE patient_data SET ";
258 $sql .= $query['set'];
259 $sql .= " WHERE `uuid` = ?";
261 $puuidBinary = UuidRegistry
::uuidToBytes($puuidString);
262 array_push($query['bind'], $puuidBinary);
263 $sqlResult = sqlStatement($sql, $query['bind']);
266 $processingResult->addErrorMessage("error processing SQL Update");
268 $processingResult = $this->getOne($puuidString);
269 // Tell subscribers that a new patient has been updated
270 // We have to do this here and in the databaseUpdate() because this lookup is
271 // by uuid where the databseUpdate updates by pid.
272 $patientUpdatedEvent = new PatientUpdatedEvent($dataBeforeUpdate, $processingResult->getData());
273 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(PatientUpdatedEvent
::EVENT_HANDLE
, $patientUpdatedEvent, 10);
275 return $processingResult;
278 protected function createResultRecordFromDatabaseResult($record)
280 if (!empty($record['uuid'])) {
281 $record['uuid'] = UuidRegistry
::uuidToString($record['uuid']);
289 * Returns a list of patients matching optional search criteria.
290 * Search criteria is conveyed by array where key = field/column name, value = field value.
291 * If no search criteria is provided, all records are returned.
293 * @param $search search array parameters
294 * @param $isAndCondition specifies if AND condition is used for multiple criteria. Defaults to true.
295 * @param $puuidBind - Optional variable to only allow visibility of the patient with this puuid.
296 * @return ProcessingResult which contains validation messages, internal error messages, and the data
299 public function getAll($search = array(), $isAndCondition = true, $puuidBind = null)
302 if (!empty($search)) {
303 if (isset($puuidBind)) {
304 $querySearch['uuid'] = new TokenSearchField('uuid', $puuidBind);
305 } else if (isset($search['uuid'])) {
306 $querySearch['uuid'] = new TokenSearchField('uuid', $search['uuid']);
308 $wildcardFields = array('fname', 'mname', 'lname', 'street', 'city', 'state','postal_code','title');
309 foreach ($wildcardFields as $field) {
310 if (isset($search[$field])) {
311 $querySearch[$field] = new StringSearchField($field, $search[$field], SearchModifier
::CONTAINS
, $isAndCondition);
315 return $this->search($querySearch, $isAndCondition);
319 * Returns a single patient record by patient id.
320 * @param $puuidString - The patient uuid identifier in string format.
321 * @return ProcessingResult which contains validation messages, internal error messages, and the data
324 public function getOne($puuidString)
326 $processingResult = new ProcessingResult();
328 $isValid = $this->patientValidator
->isExistingUuid($puuidString);
331 $validationMessages = [
332 'uuid' => ["invalid or nonexisting value" => " value " . $puuidString]
334 $processingResult->setValidationMessages($validationMessages);
335 return $processingResult;
354 contact_relationship,
369 $puuidBinary = UuidRegistry
::uuidToBytes($puuidString);
370 $sqlResult = sqlQuery($sql, [$puuidBinary]);
372 $sqlResult['uuid'] = UuidRegistry
::uuidToString($sqlResult['uuid']);
373 $processingResult->addData($sqlResult);
374 return $processingResult;
378 * Given a pid, find the patient record
382 public function findByPid($pid)
384 $table = PatientService
::TABLE_NAME
;
385 $patientRow = self
::selectHelper("SELECT * FROM `$table`", [
386 'where' => 'WHERE pid = ?',
397 public function getPatientPictureDocumentId($pid)
399 $sql = "SELECT doc.id AS id
401 JOIN categories_to_documents cate_to_doc
402 ON doc.id = cate_to_doc.document_id
404 ON cate.id = cate_to_doc.category_id
405 WHERE cate.name LIKE ? and doc.foreign_id = ?";
407 $result = sqlQuery($sql, array($GLOBALS['patient_photo_category_name'], $pid));
409 if (empty($result) ||
empty($result['id'])) {
410 return $this->patient_picture_fallback_id
;
413 return $result['id'];
417 * Fetch UUID for the patient id
419 * @param string $id - ID of Patient
420 * @return false if nothing found otherwise return UUID
422 public function getUuid($pid)
424 return self
::getUuidById($pid, self
::TABLE_NAME
, 'pid');