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\Database\QueryUtils
;
20 use OpenEMR\Common\Uuid\UuidRegistry
;
21 use OpenEMR\Events\Patient\BeforePatientCreatedEvent
;
22 use OpenEMR\Events\Patient\BeforePatientUpdatedEvent
;
23 use OpenEMR\Events\Patient\PatientCreatedEvent
;
24 use OpenEMR\Events\Patient\PatientUpdatedEvent
;
25 use OpenEMR\Services\Search\FhirSearchWhereClauseBuilder
;
26 use OpenEMR\Services\Search\ISearchField
;
27 use OpenEMR\Services\Search\TokenSearchField
;
28 use OpenEMR\Services\Search\SearchModifier
;
29 use OpenEMR\Services\Search\StringSearchField
;
30 use OpenEMR\Services\Search\TokenSearchValue
;
31 use OpenEMR\Validators\PatientValidator
;
32 use OpenEMR\Validators\ProcessingResult
;
34 class PatientService
extends BaseService
36 public const TABLE_NAME
= 'patient_data';
37 private const PATIENT_HISTORY_TABLE
= "patient_history";
40 * In the case where a patient doesn't have a picture uploaded,
41 * this value will be returned so that the document controller
42 * can return an empty response.
44 private $patient_picture_fallback_id = -1;
46 private $patientValidator;
49 * Key of translated suffix values that can be in a patient's name.
52 private $patientSuffixKeys = null;
55 * Default constructor.
57 public function __construct($base_table = null)
59 parent
::__construct($base_table ?? self
::TABLE_NAME
);
60 $this->patientValidator
= new PatientValidator();
64 * TODO: This should go in the ChartTrackerService and doesn't have to be static.
66 * @param $pid unique patient id
69 public static function getChartTrackerInformationActivity($pid)
71 $sql = "SELECT ct.ct_when,
78 FROM chart_tracker AS ct
79 LEFT OUTER JOIN users AS u ON u.id = ct.ct_userid
81 ORDER BY ct.ct_when DESC";
82 return sqlStatement($sql, array($pid));
86 * TODO: This should go in the ChartTrackerService and doesn't have to be static.
90 public static function getChartTrackerInformation()
92 $sql = "SELECT ct.ct_when,
101 FROM chart_tracker AS ct
102 JOIN cttemp ON cttemp.ct_pid = ct.ct_pid AND cttemp.ct_when = ct.ct_when
103 LEFT OUTER JOIN users AS u ON u.id = ct.ct_userid
104 LEFT OUTER JOIN patient_data AS p ON p.pid = ct.ct_pid
105 WHERE ct.ct_userid != 0
107 return sqlStatement($sql);
110 public function getFreshPid()
112 $pid = sqlQuery("SELECT MAX(pid)+1 AS pid FROM patient_data");
113 return $pid['pid'] === null ?
1 : intval($pid['pid']);
117 * Insert a patient record into the database
119 * returns the newly-created patient data array, or false in the case of
120 * an error with the sql insert
125 public function databaseInsert($data)
127 $freshPid = $this->getFreshPid();
128 $data['pid'] = $freshPid;
129 $data['uuid'] = (new UuidRegistry(['table_name' => 'patient_data']))->createUuid();
131 // The 'date' is the updated-date, and 'regdate' is the created-date
132 // so set both to the current datetime.
133 $data['date'] = date("Y-m-d H:i:s");
134 $data['regdate'] = date("Y-m-d H:i:s");
135 if (empty($data['pubpid'])) {
136 $data['pubpid'] = $freshPid;
139 // Before a patient is inserted, fire the "before patient created" event so listeners can do extra processing
140 $beforePatientCreatedEvent = new BeforePatientCreatedEvent($data);
141 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(BeforePatientCreatedEvent
::EVENT_HANDLE
, $beforePatientCreatedEvent, 10);
142 $data = $beforePatientCreatedEvent->getPatientData();
144 $query = $this->buildInsertColumns($data);
145 $sql = " INSERT INTO patient_data SET ";
146 $sql .= $query['set'];
148 $results = sqlInsert(
152 $data['id'] = $results;
154 // Tell subscribers that a new patient has been created
155 $patientCreatedEvent = new PatientCreatedEvent($data);
156 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(PatientCreatedEvent
::EVENT_HANDLE
, $patientCreatedEvent, 10);
158 // If we have a result-set from our insert, return the PID,
159 // otherwise return false
168 * Inserts a new patient record.
170 * @param $data The patient fields (array) to insert.
171 * @return ProcessingResult which contains validation messages, internal error messages, and the data
174 public function insert($data)
176 $processingResult = $this->patientValidator
->validate($data, PatientValidator
::DATABASE_INSERT_CONTEXT
);
178 if (!$processingResult->isValid()) {
179 return $processingResult;
182 $data = $this->databaseInsert($data);
184 if (false !== $data['pid']) {
185 $processingResult->addData(array(
186 'pid' => $data['pid'],
187 'uuid' => UuidRegistry
::uuidToString($data['uuid'])
190 $processingResult->addInternalError("error processing SQL Insert");
193 return $processingResult;
197 * Do a database update using the pid from the input
200 * Return the data that was updated into the database,
201 * or false if there was an error with the update
206 public function databaseUpdate($data)
208 // Get the data before update to send to the event listener
209 $dataBeforeUpdate = $this->findByPid($data['pid']);
211 // The `date` column is treated as an updated_date
212 $data['date'] = date("Y-m-d H:i:s");
213 $table = PatientService
::TABLE_NAME
;
215 // Fire the "before patient updated" event so listeners can do extra processing before data is updated
216 $beforePatientUpdatedEvent = new BeforePatientUpdatedEvent($data);
217 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(BeforePatientUpdatedEvent
::EVENT_HANDLE
, $beforePatientUpdatedEvent, 10);
218 $data = $beforePatientUpdatedEvent->getPatientData();
220 $query = $this->buildUpdateColumns($data);
221 $sql = " UPDATE $table SET ";
222 $sql .= $query['set'];
223 $sql .= " WHERE `pid` = ?";
225 array_push($query['bind'], $data['pid']);
226 $sqlResult = sqlStatement($sql, $query['bind']);
229 $dataBeforeUpdate['care_team_provider'] != ($data['care_team_provider'] ??
'')
230 ||
($dataBeforeUpdate['care_team_facility'] ??
'') != ($data['care_team_facility'] ??
'')
232 // need to save off our care team
233 $this->saveCareTeamHistory($data, $dataBeforeUpdate['care_team_provider'], $dataBeforeUpdate['care_team_facility']);
237 // Tell subscribers that a new patient has been updated
238 $patientUpdatedEvent = new PatientUpdatedEvent($dataBeforeUpdate, $data);
239 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(PatientUpdatedEvent
::EVENT_HANDLE
, $patientUpdatedEvent, 10);
248 * Updates an existing patient record.
250 * @param $puuidString - The patient uuid identifier in string format used for update.
251 * @param $data - The updated patient data fields
252 * @return ProcessingResult which contains validation messages, internal error messages, and the data
255 public function update($puuidString, $data)
257 $data["uuid"] = $puuidString;
258 $processingResult = $this->patientValidator
->validate($data, PatientValidator
::DATABASE_UPDATE_CONTEXT
);
259 if (!$processingResult->isValid()) {
260 return $processingResult;
263 // Get the data before update to send to the event listener
264 $dataBeforeUpdate = $this->getOne($puuidString);
266 // The `date` column is treated as an updated_date
267 $data['date'] = date("Y-m-d H:i:s");
269 // Fire the "before patient updated" event so listeners can do extra processing before data is updated
270 $beforePatientUpdatedEvent = new BeforePatientUpdatedEvent($data);
271 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(BeforePatientUpdatedEvent
::EVENT_HANDLE
, $beforePatientUpdatedEvent, 10);
272 $data = $beforePatientUpdatedEvent->getPatientData();
274 $query = $this->buildUpdateColumns($data);
275 $sql = " UPDATE patient_data SET ";
276 $sql .= $query['set'];
277 $sql .= " WHERE `uuid` = ?";
279 $puuidBinary = UuidRegistry
::uuidToBytes($puuidString);
280 array_push($query['bind'], $puuidBinary);
281 $sqlResult = sqlStatement($sql, $query['bind']);
284 $processingResult->addErrorMessage("error processing SQL Update");
286 $processingResult = $this->getOne($puuidString);
287 // Tell subscribers that a new patient has been updated
288 // We have to do this here and in the databaseUpdate() because this lookup is
289 // by uuid where the databseUpdate updates by pid.
290 $patientUpdatedEvent = new PatientUpdatedEvent($dataBeforeUpdate, $processingResult->getData());
291 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(PatientUpdatedEvent
::EVENT_HANDLE
, $patientUpdatedEvent, 10);
293 return $processingResult;
296 protected function createResultRecordFromDatabaseResult($record)
298 if (!empty($record['uuid'])) {
299 $record['uuid'] = UuidRegistry
::uuidToString($record['uuid']);
307 * Returns a list of patients matching optional search criteria.
308 * Search criteria is conveyed by array where key = field/column name, value = field value.
309 * If no search criteria is provided, all records are returned.
311 * @param $search search array parameters
312 * @param $isAndCondition specifies if AND condition is used for multiple criteria. Defaults to true.
313 * @param $puuidBind - Optional variable to only allow visibility of the patient with this puuid.
314 * @return ProcessingResult which contains validation messages, internal error messages, and the data
317 public function getAll($search = array(), $isAndCondition = true, $puuidBind = null)
320 if (!empty($search)) {
321 if (isset($puuidBind)) {
322 $querySearch['uuid'] = new TokenSearchField('uuid', $puuidBind);
323 } else if (isset($search['uuid'])) {
324 $querySearch['uuid'] = new TokenSearchField('uuid', $search['uuid']);
326 $wildcardFields = array('fname', 'mname', 'lname', 'street', 'city', 'state','postal_code','title');
327 foreach ($wildcardFields as $field) {
328 if (isset($search[$field])) {
329 $querySearch[$field] = new StringSearchField($field, $search[$field], SearchModifier
::CONTAINS
, $isAndCondition);
332 // for backwards compatability, we will make sure we do exact matches on the keys using string comparisons if no object is used
333 foreach ($search as $field => $key) {
334 if (!isset($querySearch[$field]) && !($key instanceof ISearchField
)) {
335 $querySearch[$field] = new StringSearchField($field, $search[$field], SearchModifier
::EXACT
, $isAndCondition);
339 return $this->search($querySearch, $isAndCondition);
342 public function search($search, $isAndCondition = true)
346 ,patient_history_type_key
348 ,previous_name_prefix
350 ,previous_name_middle
352 ,previous_name_suffix
353 ,previous_name_enddate
357 pid AS patient_history_pid
358 ,history_type_key AS patient_history_type_key
359 ,previous_name_prefix
361 ,previous_name_middle
363 ,previous_name_suffix
364 ,previous_name_enddate
365 ,`date` AS previous_creation_date
366 ,uuid AS patient_history_uuid
368 ) patient_history ON patient_data.pid = patient_history.patient_history_pid";
369 $whereClause = FhirSearchWhereClauseBuilder
::build($search, $isAndCondition);
371 $sql .= $whereClause->getFragment();
372 $sqlBindArray = $whereClause->getBoundValues();
373 $statementResults = QueryUtils
::sqlStatementThrowException($sql, $sqlBindArray);
375 $processingResult = $this->hydrateSearchResultsFromQueryResource($statementResults);
376 return $processingResult;
379 private function hydrateSearchResultsFromQueryResource($queryResource)
381 $processingResult = new ProcessingResult();
382 $patientsByUuid = [];
383 $patientFields = array_combine($this->getFields(), $this->getFields());
384 $previousNameColumns = ['previous_name_prefix', 'previous_name_first', 'previous_name_middle'
385 , 'previous_name_last', 'previous_name_suffix', 'previous_name_enddate'];
386 $previousNamesFields = array_combine($previousNameColumns, $previousNameColumns);
387 $patientOrderedList = [];
388 while ($row = sqlFetchArray($queryResource)) {
389 $record = $this->createResultRecordFromDatabaseResult($row);
390 $patientUuid = $record['uuid'];
391 if (!isset($patientsByUuid[$patientUuid])) {
392 $patient = array_intersect_key($record, $patientFields);
393 $patient['suffix'] = $this->parseSuffixForPatientRecord($patient);
394 $patient['previous_names'] = [];
395 $patientOrderedList[] = $patientUuid;
397 $patient = $patientsByUuid[$patientUuid];
399 if (!empty($record['patient_history_type_key'])) {
400 if ($record['patient_history_type_key'] == 'name_history') {
401 $previousName = array_intersect_key($record, $previousNamesFields);
402 $previousName['formatted_name'] = $this->formatPreviousName($previousName);
403 $patient['previous_names'][] = $previousName;
407 // now let's grab our history
408 $patientsByUuid[$patientUuid] = $patient;
410 foreach ($patientOrderedList as $uuid) {
411 $patient = $patientsByUuid[$uuid];
412 $processingResult->addData($patient);
414 return $processingResult;
418 * Returns a single patient record by patient id.
419 * @param $puuidString - The patient uuid identifier in string format.
420 * @return ProcessingResult which contains validation messages, internal error messages, and the data
423 public function getOne($puuidString)
425 $processingResult = new ProcessingResult();
427 $isValid = $this->patientValidator
->isExistingUuid($puuidString);
430 $validationMessages = [
431 'uuid' => ["invalid or nonexisting value" => " value " . $puuidString]
433 $processingResult->setValidationMessages($validationMessages);
434 return $processingResult;
437 return $this->search(['uuid' => new TokenSearchField('uuid', [$puuidString], true)]);
441 * Given a pid, find the patient record
445 public function findByPid($pid)
447 $table = PatientService
::TABLE_NAME
;
448 $patientRow = self
::selectHelper("SELECT * FROM `$table`", [
449 'where' => 'WHERE pid = ?',
460 public function getPatientPictureDocumentId($pid)
462 $sql = "SELECT doc.id AS id
464 JOIN categories_to_documents cate_to_doc
465 ON doc.id = cate_to_doc.document_id
467 ON cate.id = cate_to_doc.category_id
468 WHERE cate.name LIKE ? and doc.foreign_id = ?";
470 $result = sqlQuery($sql, array($GLOBALS['patient_photo_category_name'], $pid));
472 if (empty($result) ||
empty($result['id'])) {
473 return $this->patient_picture_fallback_id
;
476 return $result['id'];
480 * Fetch UUID for the patient id
482 * @param string $id - ID of Patient
483 * @return false if nothing found otherwise return UUID
485 public function getUuid($pid)
487 return self
::getUuidById($pid, self
::TABLE_NAME
, 'pid');
490 private function saveCareTeamHistory($patientData, $oldProviders, $oldFacilities)
492 $careTeamService = new CareTeamService();
493 $careTeamService->createCareTeamHistory($patientData['pid'], $oldProviders, $oldFacilities);
496 public function getPatientNameHistory($pid)
500 previous_name_prefix,
502 previous_name_middle,
504 previous_name_suffix,
505 previous_name_enddate
507 WHERE pid = ? AND history_type_key = ?";
508 $results = QueryUtils
::sqlStatementThrowException($sql, array($pid, 'name_history'));
510 while ($row = sqlFetchArray($results)) {
511 $row['formatted_name'] = $this->formatPreviousName($row);
518 public function deletePatientNameHistoryById($id)
520 $sql = "DELETE FROM patient_history WHERE id = ?";
521 return sqlQuery($sql, array($id));
524 public function getPatientNameHistoryById($pid, $id)
528 previous_name_prefix,
530 previous_name_middle,
532 previous_name_suffix,
533 previous_name_enddate
535 WHERE pid = ? AND id = ? AND history_type_key = ?";
536 $result = sqlQuery($sql, array($pid, $id, 'name_history'));
537 $result['formatted_name'] = $this->formatPreviousName($result);
543 * Create a previous patient name history
544 * Updates not allowed for this history feature.
546 * @param string $pid patient internal id
547 * @param array $record array values to insert
548 * @return int | false new id or false if name already exist
550 public function createPatientNameHistory($pid, $record)
554 'history_type_key' => 'name_history',
555 'previous_name_prefix' => $record['previous_name_prefix'],
556 'previous_name_first' => $record['previous_name_first'],
557 'previous_name_middle' => $record['previous_name_middle'],
558 'previous_name_last' => $record['previous_name_last'],
559 'previous_name_suffix' => $record['previous_name_suffix'],
560 'previous_name_enddate' => $record['previous_name_enddate']
562 $sql = "SELECT pid FROM " . self
::PATIENT_HISTORY_TABLE
. " WHERE
564 history_type_key = ? AND
565 previous_name_prefix = ? AND
566 previous_name_first = ? AND
567 previous_name_middle = ? AND
568 previous_name_last = ? AND
569 previous_name_suffix = ? AND
570 previous_name_enddate = ?
572 $go_flag = QueryUtils
::fetchSingleValue($sql, 'pid', $insertData);
573 // return false which calling routine should understand as existing name record
574 if (!empty($go_flag)) {
577 // finish up the insert
578 $insertData['uuid'] = UuidRegistry
::getRegistryForTable(self
::PATIENT_HISTORY_TABLE
)->createUuid();
579 $insert = $this->buildInsertColumns($insertData);
580 $sql = "INSERT INTO " . self
::PATIENT_HISTORY_TABLE
. " SET " . $insert['set'];
582 return QueryUtils
::sqlInsert($sql, $insert['bind']);
585 public function formatPreviousName($item)
588 $item['previous_name_enddate'] === '0000-00-00'
589 ||
$item['previous_name_enddate'] === '00/00/0000'
591 $item['previous_name_enddate'] = '';
593 $item['previous_name_enddate'] = oeFormatShortDate($item['previous_name_enddate']);
594 $name = ($item['previous_name_prefix'] ?
$item['previous_name_prefix'] . " " : "") .
595 $item['previous_name_first'] .
596 ($item['previous_name_middle'] ?
" " . $item['previous_name_middle'] . " " : " ") .
597 $item['previous_name_last'] .
598 ($item['previous_name_suffix'] ?
" " . $item['previous_name_suffix'] : "") .
599 ($item['previous_name_enddate'] ?
" " . $item['previous_name_enddate'] : "");
605 * Returns a string to be used to display a patient's age
607 * @param type $dobYMD
608 * @param type $asOfYMD
609 * @return string suitable for displaying patient's age based on preferences
611 public function getPatientAgeDisplay($dobYMD, $asOfYMD = null)
613 if ($GLOBALS['age_display_format'] == '1') {
614 $ageYMD = $this->getPatientAgeYMD($dobYMD, $asOfYMD);
615 if (isset($GLOBALS['age_display_limit']) && $ageYMD['age'] <= $GLOBALS['age_display_limit']) {
616 return $ageYMD['ageinYMD'];
618 return $this->getPatientAge($dobYMD, $asOfYMD);
621 return $this->getPatientAge($dobYMD, $asOfYMD);
626 // in months if < 2 years old
627 // in years if > 2 years old
628 // given YYYYMMDD from MySQL DATE_FORMAT(DOB,'%Y%m%d')
629 // (optional) nowYMD is a date in YYYYMMDD format
630 public function getPatientAge($dobYMD, $nowYMD = null)
632 if (empty($dobYMD)) {
635 // strip any dashes from the DOB
636 $dobYMD = preg_replace("/-/", "", $dobYMD);
637 $dobDay = substr($dobYMD, 6, 2);
638 $dobMonth = substr($dobYMD, 4, 2);
639 $dobYear = (int) substr($dobYMD, 0, 4);
641 // set the 'now' date values
642 if ($nowYMD == null) {
644 $nowMonth = date("m");
645 $nowYear = date("Y");
647 $nowDay = substr($nowYMD, 6, 2);
648 $nowMonth = substr($nowYMD, 4, 2);
649 $nowYear = substr($nowYMD, 0, 4);
652 $dayDiff = $nowDay - $dobDay;
653 $monthDiff = $nowMonth - $dobMonth;
654 $yearDiff = $nowYear - $dobYear;
656 $ageInMonths = (($nowYear * 12) +
$nowMonth) - (($dobYear * 12) +
$dobMonth);
658 // We want the age in FULL months, so if the current date is less than the numerical day of birth, subtract a month
663 if ($ageInMonths > 24) {
665 if (($monthDiff == 0) && ($dayDiff < 0)) {
667 } elseif ($monthDiff < 0) {
671 $age = "$ageInMonths " . xl('month');
682 * @return array containing
683 * age - decimal age in years
684 * age_in_months - decimal age in months
685 * ageinYMD - formatted string #y #m #d
687 public function getPatientAgeYMD($dob, $date = null)
691 $monthnow = date("m");
692 $yearnow = date("Y");
693 $datenow = $yearnow . $monthnow . $daynow;
695 $datenow = preg_replace("/-/", "", $date);
696 $yearnow = substr($datenow, 0, 4);
697 $monthnow = substr($datenow, 4, 2);
698 $daynow = substr($datenow, 6, 2);
699 $datenow = $yearnow . $monthnow . $daynow;
702 $dob = preg_replace("/-/", "", $dob);
703 $dobyear = substr($dob, 0, 4);
704 $dobmonth = substr($dob, 4, 2);
705 $dobday = substr($dob, 6, 2);
706 $dob = $dobyear . $dobmonth . $dobday;
708 //to compensate for 30, 31, 28, 29 days/month
709 $mo = $monthnow; //to avoid confusion with later calculation
711 if ($mo == 05 or $mo == 07 or $mo == 10 or $mo == 12) { // determined by monthnow-1
712 $nd = 30; // nd = number of days in a month, if monthnow is 5, 7, 9, 12 then
713 } elseif ($mo == 03) { // look at April, June, September, November for calculation. These months only have 30 days.
714 // for march, look to the month of February for calculation, check for leap year
715 $check_leap_Y = $yearnow / 4; // To check if this is a leap year.
716 if (is_int($check_leap_Y)) { // If it true then this is the leap year
718 } else { // otherwise, it is not a leap year.
721 } else { // other months have 31 days
725 $bdthisyear = $yearnow . $dobmonth . $dobday; //Date current year's birthday falls on
726 if ($datenow < $bdthisyear) { // if patient hasn't had birthday yet this year
727 $age_year = $yearnow - $dobyear - 1;
728 if ($daynow < $dobday) {
729 $months_since_birthday = 12 - $dobmonth +
$monthnow - 1;
730 $days_since_dobday = $nd - $dobday +
$daynow; //did not take into account for month with 31 days
732 $months_since_birthday = 12 - $dobmonth +
$monthnow;
733 $days_since_dobday = $daynow - $dobday;
735 } else // if patient has had birthday this calandar year
737 $age_year = (int) $yearnow - (int) $dobyear;
738 if ($daynow < $dobday) {
739 $months_since_birthday = $monthnow - $dobmonth - 1;
740 $days_since_dobday = $nd - $dobday +
$daynow;
742 $months_since_birthday = $monthnow - $dobmonth;
743 $days_since_dobday = $daynow - $dobday;
747 $day_as_month_decimal = $days_since_dobday / 30;
748 $months_since_birthday_float = $months_since_birthday +
$day_as_month_decimal;
749 $month_as_year_decimal = $months_since_birthday_float / 12;
750 $age_float = $age_year +
$month_as_year_decimal;
752 $age_in_months = $age_year * 12 +
$months_since_birthday_float;
753 $age_in_months = round($age_in_months, 2); //round the months to xx.xx 2 floating points
754 $age = round($age_float, 2);
756 // round the years to 2 floating points
757 $ageinYMD = $age_year . "y " . $months_since_birthday . "m " . $days_since_dobday . "d";
758 return compact('age', 'age_in_months', 'ageinYMD');
761 private function parseSuffixForPatientRecord($patientRecord)
763 // if we have a suffix populated (that wasn't entered into last name) let's use that.
764 if (!empty($patientRecord['suffix'])) {
765 return $patientRecord['suffix'];
767 // parse suffix from last name. saves messing with LBF
768 $suffixes = $this->getPatientSuffixKeys();
770 foreach ($suffixes as $s) {
771 if (stripos($patientRecord['lname'], $s) !== false) {
773 $result['lname'] = trim(str_replace($s, '', $patientRecord['lname']));
780 private function getPatientSuffixKeys()
782 if (!isset($this->patientSuffixKeys
)) {
783 $this->patientSuffixKeys
= array(xl('Jr.'), ' ' . xl('Jr'), xl('Sr.'), ' ' . xl('Sr'), xl('II{{patient suffix}}'), xl('III{{patient suffix}}'), xl('IV{{patient suffix}}'));
785 return $this->patientSuffixKeys
;