Adding reminder for transaction form generation (#5332)
[openemr.git] / src / Services / PatientService.php
blobc66fede842773f7a4fe17f2c40b4c0754cf121a2
1 <?php
3 /**
4 * Patient Service
6 * @package OpenEMR
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";
39 /**
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;
48 /**
49 * Key of translated suffix values that can be in a patient's name.
50 * @var array|null
52 private $patientSuffixKeys = null;
54 /**
55 * Default constructor.
57 public function __construct($base_table = null)
59 parent::__construct($base_table ?? self::TABLE_NAME);
60 $this->patientValidator = new PatientValidator();
63 /**
64 * TODO: This should go in the ChartTrackerService and doesn't have to be static.
66 * @param $pid unique patient id
67 * @return recordset
69 public static function getChartTrackerInformationActivity($pid)
71 $sql = "SELECT ct.ct_when,
72 ct.ct_userid,
73 ct.ct_location,
74 u.username,
75 u.fname,
76 u.mname,
77 u.lname
78 FROM chart_tracker AS ct
79 LEFT OUTER JOIN users AS u ON u.id = ct.ct_userid
80 WHERE ct.ct_pid = ?
81 ORDER BY ct.ct_when DESC";
82 return sqlStatement($sql, array($pid));
85 /**
86 * TODO: This should go in the ChartTrackerService and doesn't have to be static.
88 * @return recordset
90 public static function getChartTrackerInformation()
92 $sql = "SELECT ct.ct_when,
93 u.username,
94 u.fname AS ufname,
95 u.mname AS umname,
96 u.lname AS ulname,
97 p.pubpid,
98 p.fname,
99 p.mname,
100 p.lname
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
106 ORDER BY p.pubpid";
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
122 * @param $data
123 * @return false|int
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(
149 $sql,
150 $query['bind']
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
160 if ($results) {
161 return $data;
162 } else {
163 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
172 * payload.
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'])
189 } else {
190 $processingResult->addInternalError("error processing SQL Insert");
193 return $processingResult;
197 * Do a database update using the pid from the input
198 * array
200 * Return the data that was updated into the database,
201 * or false if there was an error with the update
203 * @param array $data
204 * @return mixed
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']);
228 if (
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']);
236 if ($sqlResult) {
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);
241 return $data;
242 } else {
243 return false;
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
253 * payload.
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']);
283 if (!$sqlResult) {
284 $processingResult->addErrorMessage("error processing SQL Update");
285 } else {
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']);
302 return $record;
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
315 * payload.
317 public function getAll($search = array(), $isAndCondition = true, $puuidBind = null)
319 $querySearch = [];
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)
344 $sql = "SELECT
345 patient_data.*
346 ,patient_history_type_key
347 ,previous_name_first
348 ,previous_name_prefix
349 ,previous_name_first
350 ,previous_name_middle
351 ,previous_name_last
352 ,previous_name_suffix
353 ,previous_name_enddate
354 FROM patient_data
355 LEFT JOIN (
356 SELECT
357 pid AS patient_history_pid
358 ,history_type_key AS patient_history_type_key
359 ,previous_name_prefix
360 ,previous_name_first
361 ,previous_name_middle
362 ,previous_name_last
363 ,previous_name_suffix
364 ,previous_name_enddate
365 ,`date` AS previous_creation_date
366 ,uuid AS patient_history_uuid
367 FROM patient_history
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;
396 } else {
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
421 * payload.
423 public function getOne($puuidString)
425 $processingResult = new ProcessingResult();
427 $isValid = $this->patientValidator->isExistingUuid($puuidString);
429 if (!$isValid) {
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
443 * @param $pid
445 public function findByPid($pid)
447 $table = PatientService::TABLE_NAME;
448 $patientRow = self::selectHelper("SELECT * FROM `$table`", [
449 'where' => 'WHERE pid = ?',
450 'limit' => 1,
451 'data' => [$pid]
454 return $patientRow;
458 * @return number
460 public function getPatientPictureDocumentId($pid)
462 $sql = "SELECT doc.id AS id
463 FROM documents doc
464 JOIN categories_to_documents cate_to_doc
465 ON doc.id = cate_to_doc.document_id
466 JOIN categories cate
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)
498 $sql = "SELECT pid,
500 previous_name_prefix,
501 previous_name_first,
502 previous_name_middle,
503 previous_name_last,
504 previous_name_suffix,
505 previous_name_enddate
506 FROM patient_history
507 WHERE pid = ? AND history_type_key = ?";
508 $results = QueryUtils::sqlStatementThrowException($sql, array($pid, 'name_history'));
509 $rows = [];
510 while ($row = sqlFetchArray($results)) {
511 $row['formatted_name'] = $this->formatPreviousName($row);
512 $rows[] = $row;
515 return $rows;
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)
526 $sql = "SELECT pid,
528 previous_name_prefix,
529 previous_name_first,
530 previous_name_middle,
531 previous_name_last,
532 previous_name_suffix,
533 previous_name_enddate
534 FROM patient_history
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);
539 return $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)
552 $insertData = [
553 'pid' => $pid,
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
563 pid = ? AND
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)) {
575 return false;
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)
587 if (
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'] : "");
601 return text($name);
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'];
617 } else {
618 return $this->getPatientAge($dobYMD, $asOfYMD);
620 } else {
621 return $this->getPatientAge($dobYMD, $asOfYMD);
625 // Returns Age
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)) {
633 return '';
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) {
643 $nowDay = date("d");
644 $nowMonth = date("m");
645 $nowYear = date("Y");
646 } else {
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
659 if ($dayDiff < 0) {
660 $ageInMonths -= 1;
663 if ($ageInMonths > 24) {
664 $age = $yearDiff;
665 if (($monthDiff == 0) && ($dayDiff < 0)) {
666 $age -= 1;
667 } elseif ($monthDiff < 0) {
668 $age -= 1;
670 } else {
671 $age = "$ageInMonths " . xl('month');
674 return $age;
680 * @param type $dob
681 * @param type $date
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)
689 if ($date == null) {
690 $daynow = date("d");
691 $monthnow = date("m");
692 $yearnow = date("Y");
693 $datenow = $yearnow . $monthnow . $daynow;
694 } else {
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
717 $nd = 29;
718 } else { // otherwise, it is not a leap year.
719 $nd = 28;
721 } else { // other months have 31 days
722 $nd = 31;
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
731 } else {
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;
741 } else {
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();
769 $suffix = null;
770 foreach ($suffixes as $s) {
771 if (stripos($patientRecord['lname'], $s) !== false) {
772 $suffix = $s;
773 $result['lname'] = trim(str_replace($s, '', $patientRecord['lname']));
774 break;
777 return $suffix;
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;