fix: ccda zip import and php warnings and deprecations (#7416)
[openemr.git] / library / patient.inc.php
blobc519ea174f46f300879ae34770024cbe5af33211
1 <?php
3 /**
4 * patient.inc.php includes functions for manipulating patient information.
6 * @package OpenEMR
7 * @link http://www.open-emr.org
8 * @author Brady Miller <brady.g.miller@gmail.com>
9 * @author Sherwin Gaddis <sherwingaddis@gmail.com>
10 * @author Stephen Waite <stephen.waite@cmsvt.com>
11 * @author Rod Roark <rod@sunsetsystems.com>
12 * @copyright Copyright (c) 2018-2019 Brady Miller <brady.g.miller@gmail.com>
13 * @copyright Copyright (c) 2019 Sherwin Gaddis <sherwingaddis@gmail.com>
14 * @copyright Copyright (c) 2018-2021 Stephen Waite <stephen.waite@cmsvt.com>
15 * @copyright Copyright (c) 2021-2022 Rod Roark <rod@sunsetsystems.com>
16 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
19 use OpenEMR\Common\Uuid\UuidRegistry;
20 use OpenEMR\Services\FacilityService;
21 use OpenEMR\Services\PatientService;
22 use OpenEMR\Services\SocialHistoryService;
23 use OpenEMR\Billing\InsurancePolicyTypes;
24 use OpenEMR\Services\InsuranceCompanyService;
26 require_once(dirname(__FILE__) . "/dupscore.inc.php");
28 global $facilityService;
29 $facilityService = new FacilityService();
31 // These are for sports team use:
32 $PLAYER_FITNESSES = array(
33 xl('Full Play'),
34 xl('Full Training'),
35 xl('Restricted Training'),
36 xl('Injured Out'),
37 xl('Rehabilitation'),
38 xl('Illness'),
39 xl('International Duty')
41 $PLAYER_FITCOLORS = array('#6677ff', '#00cc00', '#ffff00', '#ff3333', '#ff8800', '#ffeecc', '#ffccaa');
43 // Hard-coding this array because its values and meanings are fixed by the 837p
44 // standard and we don't want people messing with them.
45 global $policy_types;
46 $policy_types = InsurancePolicyTypes::getTranslatedPolicyTypes();
48 /**
49 * Get a patient's demographic data.
51 * @param int $pid The PID of the patient
52 * @param string $given an optional subsection of the patient's demographic
53 * data to retrieve.
54 * @return array The requested subsection of a patient's demographic data.
55 * If no subsection was given, returns everything, with the
56 * date of birth as the last field.
58 // To prevent sql injection on this function, if a variable is used for $given parameter, then
59 // it needs to be escaped via whitelisting prior to using this function.
60 function getPatientData($pid, $given = "*, DATE_FORMAT(DOB,'%m/%d/%Y') as DOB_TS")
62 $sql = "select $given from patient_data where pid=? order by date DESC limit 0,1";
63 return sqlQuery($sql, array($pid));
66 function getInsuranceProvider($ins_id)
69 $sql = "select name from insurance_companies where id=?";
70 $row = sqlQuery($sql, array($ins_id));
71 return $row['name'] ?? '';
74 function getInsuranceProviders()
76 $returnval = array();
78 if (true) {
79 $sql = "select name, id from insurance_companies where inactive != 1 order by name, id";
80 $rez = sqlStatement($sql);
81 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
82 $returnval[$row['id']] = $row['name'];
84 } else { // Please leave this here. I have a user who wants to see zip codes and PO
85 // box numbers listed along with the insurance company names, as many companies
86 // have different billing addresses for different plans. -- Rod Roark
87 $sql = "select insurance_companies.name, insurance_companies.id, " .
88 "addresses.zip, addresses.line1 " .
89 "from insurance_companies, addresses " .
90 "where addresses.foreign_id = insurance_companies.id " .
91 "order by insurance_companies.name, addresses.zip";
93 $rez = sqlStatement($sql);
95 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
96 preg_match("/\d+/", $row['line1'], $matches);
97 $returnval[$row['id']] = $row['name'] . " (" . $row['zip'] .
98 "," . $matches[0] . ")";
102 return $returnval;
105 function getInsuranceProvidersExtra()
107 $returnval = array();
108 // add a global and if for where to allow inactive inscompanies
110 $sql = "SELECT insurance_companies.name, insurance_companies.id, insurance_companies.cms_id,
111 addresses.line1, addresses.line2, addresses.city, addresses.state, addresses.zip
112 FROM insurance_companies, addresses
113 WHERE addresses.foreign_id = insurance_companies.id
114 AND insurance_companies.inactive != 1
115 ORDER BY insurance_companies.name, addresses.zip";
117 $rez = sqlStatement($sql);
119 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
120 $displayName = InsuranceCompanyService::getDisplayNameForInsuranceRecord($row);
121 $returnval[$row['id']] = $displayName;
124 return $returnval;
127 // ----------------------------------------------------------------------------
128 // Get one facility row. If the ID is not specified, then get either the
129 // "main" (billing) facility, or the default facility of the currently
130 // logged-in user. This was created to support genFacilityTitle() but
131 // may find additional uses.
133 function getFacility($facid = 0)
135 global $facilityService;
137 $facility = null;
139 if ($facid > 0) {
140 return $facilityService->getById($facid);
143 if ($GLOBALS['login_into_facility']) {
144 //facility is saved in sessions
145 $facility = $facilityService->getById($_SESSION['facilityId']);
146 } else {
147 if ($facid == 0) {
148 $facility = $facilityService->getPrimaryBillingLocation();
149 } else {
150 $facility = $facilityService->getFacilityForUser($_SESSION['authUserID']);
154 return $facility;
157 // Generate a report title including report name and facility name, address
158 // and phone.
160 function genFacilityTitle($repname = '', $facid = 0, $logo = "")
162 $s = '';
163 $s .= "<table class='ftitletable' width='100%'>\n";
164 $s .= " <tr>\n";
165 if (empty($logo)) {
166 $s .= " <td align='left' class='ftitlecell1'>" . text($repname) . "</td>\n";
167 } else {
168 $s .= " <td align='left' class='ftitlecell1'><img class='h-auto' style='max-height:8%;' src='" . attr($logo) . "' /></td>\n";
169 $s .= " <td align='left' class='ftitlecellm'><h2>" . text($repname) . "</h2></td>\n";
171 $s .= " <td align='right' class='ftitlecell2'>\n";
172 $r = getFacility($facid);
173 if (!empty($r)) {
174 $s .= "<b>" . text($r['name'] ?? '') . "</b>\n";
175 if (!empty($r['street'])) {
176 $s .= "<br />" . text($r['street']) . "\n";
179 if (!empty($r['city']) || !empty($r['state']) || !empty($r['postal_code'])) {
180 $s .= "<br />";
181 if ($r['city']) {
182 $s .= text($r['city']);
185 if ($r['state']) {
186 if ($r['city']) {
187 $s .= ", \n";
190 $s .= text($r['state']);
193 if ($r['postal_code']) {
194 $s .= " " . text($r['postal_code']);
197 $s .= "\n";
200 if (!empty($r['country_code'])) {
201 $s .= "<br />" . text($r['country_code']) . "\n";
204 if (preg_match('/[1-9]/', ($r['phone'] ?? ''))) {
205 $s .= "<br />" . text($r['phone']) . "\n";
209 $s .= " </td>\n";
210 $s .= " </tr>\n";
211 $s .= "</table>\n";
212 return $s;
216 GET FACILITIES
218 returns all facilities or just the id for the first one
219 (FACILITY FILTERING (lemonsoftware))
221 @param string - if 'first' return first facility ordered by id
222 @return array | int for 'first' case
224 function getFacilities($first = '')
226 global $facilityService;
228 $fres = $facilityService->getAllFacility();
230 if ($first == 'first') {
231 return $fres[0]['id'];
232 } else {
233 return $fres;
237 //(CHEMED) facility filter
238 function getProviderInfo($providerID = "%", $providers_only = true, $facility = '')
240 $param1 = "";
241 if ($providers_only === 'any') {
242 $param1 = " AND authorized = 1 AND active = 1 ";
243 } elseif ($providers_only) {
244 $param1 = " AND authorized = 1 AND calendar = 1 ";
247 //--------------------------------
248 //(CHEMED) facility filter
249 $param2 = "";
250 if ($facility) {
251 if ($GLOBALS['restrict_user_facility']) {
252 $param2 = " AND (facility_id = '" . add_escape_custom($facility) . "' OR '" . add_escape_custom($facility) . "' IN (select facility_id from users_facility where tablename = 'users' and table_id = id))";
253 } else {
254 $param2 = " AND facility_id = '" . add_escape_custom($facility) . "' ";
258 //--------------------------------
260 $command = "=";
261 if ($providerID == "%") {
262 $command = "like";
265 // removing active from query since is checked above with $providers_only argument
266 $query = "select distinct id, username, lname, fname, mname, authorized, info, facility, suffix, valedictory " .
267 "from users where username != '' and id $command '" .
268 add_escape_custom($providerID) . "' " . $param1 . $param2;
269 // sort by last name -- JRM June 2008
270 $query .= " ORDER BY lname, fname ";
271 $rez = sqlStatement($query);
272 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
273 $returnval[$iter] = $row;
276 //if only one result returned take the key/value pairs in array [0] and merge them down into
277 // the base array so that $resultval[0]['key'] is also accessible from $resultval['key']
279 if ($iter == 1) {
280 $akeys = array_keys($returnval[0]);
281 foreach ($akeys as $key) {
282 $returnval[0][$key] = $returnval[0][$key];
286 return ($returnval ?? null);
289 function getProviderName($providerID, $provider_only = 'any')
291 $pi = getProviderInfo($providerID, $provider_only);
292 if (!empty($pi[0]["lname"]) && (strlen($pi[0]["lname"]) > 0)) {
293 if (!empty($pi[0]["mname"]) && (strlen($pi[0]["mname"]) > 0)) {
294 $pi[0]["fname"] .= " " . $pi[0]["mname"];
297 if (!empty($pi[0]["suffix"]) && (strlen($pi[0]["suffix"]) > 0)) {
298 $pi[0]["lname"] .= ", " . $pi[0]["suffix"];
301 if (!empty($pi[0]["valedictory"]) && (strlen($pi[0]["valedictory"]) > 0)) {
302 $pi[0]["lname"] .= ", " . $pi[0]["valedictory"];
305 return $pi[0]['fname'] . " " . $pi[0]['lname'];
308 return "";
311 function getProviderId($providerName)
313 $query = "select id from users where username = ?";
314 $rez = sqlStatement($query, array($providerName));
315 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
316 $returnval[$iter] = $row;
319 return $returnval;
322 // To prevent sql injection on this function, if a variable is used for $given parameter, then
323 // it needs to be escaped via whitelisting prior to using this function; see lines 2020-2121 of
324 // library/clinical_rules.php script for example of this.
325 function getHistoryData($pid, $given = "*", $dateStart = '', $dateEnd = '')
327 $where = '';
328 if ($given == 'tobacco') {
329 $where = 'tobacco is not null and';
332 if ($dateStart && $dateEnd) {
333 $res = sqlQuery("select $given from history_data where $where pid = ? and date >= ? and date <= ? order by date DESC, id DESC limit 0,1", array($pid,$dateStart,$dateEnd));
334 } elseif ($dateStart && !$dateEnd) {
335 $res = sqlQuery("select $given from history_data where $where pid = ? and date >= ? order by date DESC, id DESC limit 0,1", array($pid,$dateStart));
336 } elseif (!$dateStart && $dateEnd) {
337 $res = sqlQuery("select $given from history_data where $where pid = ? and date <= ? order by date DESC, id DESC limit 0,1", array($pid,$dateEnd));
338 } else {
339 $res = sqlQuery("select $given from history_data where $where pid = ? order by date DESC, id DESC limit 0,1", array($pid));
342 return $res;
345 // function getInsuranceData($pid, $type = "primary", $given = "insd.*, DATE_FORMAT(subscriber_DOB,'%m/%d/%Y') as subscriber_DOB, ic.name as provider_name")
346 // To prevent sql injection on this function, if a variable is used for $given parameter, then
347 // it needs to be escaped via whitelisting prior to using this function.
348 function getInsuranceData($pid, $type = "primary", $given = "insd.*, ic.name as provider_name")
350 $sql = "select $given from insurance_data as insd " .
351 "left join insurance_companies as ic on ic.id = insd.provider " .
352 "where pid = ? and type = ? order by date DESC limit 1";
353 return sqlQuery($sql, array($pid, $type));
356 function getInsuranceDataNew($pid, $type = "primary", $given = "insd.*, ic.name as provider_name")
358 $sql = "select $given from insurance_data as insd " .
359 "left join insurance_companies as ic on ic.id = insd.provider " .
360 "where pid = ? and type = ? order by date DESC";
361 $sql_res = sqlStatement($sql, array($pid, $type));
362 while ($row = sqlFetchArray($sql_res)) {
363 $insarr[] = $row;
366 return $insarr;
369 // To prevent sql injection on this function, if a variable is used for $given parameter, then
370 // it needs to be escaped via whitelisting prior to using this function.
371 function getInsuranceDataByDate(
372 $pid,
373 $date,
374 $type,
375 $given = "insd.*, DATE_FORMAT(subscriber_DOB,'%m/%d/%Y') as subscriber_DOB, ic.name as provider_name"
378 This must take the date in the following manner: YYYY-MM-DD.
379 This function recalls the insurance value that was most recently entered from the
380 given date and before the insurance end date. It will call up most recent records up to and on the date given,
381 but not records entered after the given date.
383 $sql = "select $given from insurance_data as insd " .
384 "left join insurance_companies as ic on ic.id = provider " .
385 "where pid = ? and (date_format(date,'%Y-%m-%d') <= ? OR date IS NULL) and " .
386 "(date_format(date_end,'%Y-%m-%d') >= ? OR date_end IS NULL) and " .
387 "type = ? order by date DESC limit 1";
388 return sqlQuery($sql, array($pid, $date, $date, $type));
391 function get_unallocated_patient_balance($pid)
393 $unallocated = 0.0;
394 $query = "SELECT a.session_id, a.pay_total, a.global_amount " .
395 "FROM ar_session AS a " .
396 "WHERE a.patient_id = ? AND " .
397 "a.adjustment_code = 'pre_payment' AND a.closed = 0";
398 $res = sqlStatement($query, array($pid));
399 while ($row = sqlFetchArray($res)) {
400 $total_amt = $row['pay_total'] - $row['global_amount'];
401 $rs = sqlQuery("SELECT sum(pay_amount) AS total_pay_amt FROM ar_activity WHERE session_id = ? AND pid = ? AND deleted IS NULL", array($row['session_id'], $pid));
402 $pay_amount = $rs['total_pay_amt'];
403 $unallocated += ($total_amt - $pay_amount);
405 return sprintf('%01.2f', $unallocated);
408 function getInsuranceNameByDate(
409 $pid,
410 $date,
411 $type,
412 $given = "ic.name as provider_name"
414 // this must take the date in the following manner: YYYY-MM-DD
415 // this function recalls the insurance value that was most recently enterred from the
416 // given date. it will call up most recent records up to and on the date given,
417 // but not records enterred after the given date
418 $sql = "select $given from insurance_data as insd " .
419 "left join insurance_companies as ic on ic.id = provider " .
420 "where pid = ? and (date_format(date,'%Y-%m-%d') <= ? OR date IS NULL) and " .
421 "(date_format(date_end,'%Y-%m-%d') >= ? OR date_end IS NULL) and " .
422 "type = ? order by date DESC limit 1";
424 $row = sqlQuery($sql, array($pid, $date, $date, $type));
425 return $row['provider_name'];
428 // To prevent sql injection on this function, if a variable is used for $given parameter, then
429 // it needs to be escaped via whitelisting prior to using this function.
430 function getEmployerData($pid, $given = "*")
432 $sql = "select $given from employer_data where pid = ? order by date DESC limit 0,1";
433 return sqlQuery($sql, array($pid));
436 // Generate a consistent header and footer, used for printed patient reports
437 function genPatientHeaderFooter($pid, $DOS = null)
439 $patient_dob = getPatientData($pid, "DATE_FORMAT(DOB,'%m/%d/%Y') as DOB_TS");
440 $patient_name = getPatientName($pid);
442 // Header
443 $s = '<htmlpageheader name="PageHeader1"><div style="text-align: right; font-weight: bold;">';
444 $s .= text($patient_name) . '&emsp;DOB: ' . text($patient_dob['DOB_TS']);
445 if ($DOS) {
446 $s .= '&emsp;DOS: ' . text($DOS);
448 $s .= '</div></htmlpageheader>';
450 // Footer
451 $s .= '<htmlpagefooter name="PageFooter1"><div style="text-align: right; font-weight: bold;">';
452 $s .= '<div style="float: right; width:33%; text-align: left;">' . oeFormatDateTime(date("Y-m-d H:i:s")) . '</div>';
453 $s .= '<div style="float: right; width:33%; text-align: center;">{PAGENO}/{nbpg}</div>';
454 $s .= '<div style="float: right; width:33%; text-align: right;">' . text($patient_name) . '</div>';
455 $s .= '</div></htmlpagefooter>';
457 // Set the header and footer in the current document
458 $s .= '<sethtmlpageheader name="PageHeader1" page="ALL" value="ON" show-this-page="1" />';
459 $s .= '<sethtmlpagefooter name="PageFooter1" page="ALL" value="ON" />';
461 return $s;
464 function _set_patient_inc_count($limit, $count, $where, $whereBindArray = array())
466 // When the limit is exceeded, find out what the unlimited count would be.
467 $GLOBALS['PATIENT_INC_COUNT'] = $count;
468 // if ($limit != "all" && $GLOBALS['PATIENT_INC_COUNT'] >= $limit) {
469 if ($limit != "all") {
470 $tmp = sqlQuery("SELECT count(*) AS count FROM patient_data WHERE $where", $whereBindArray);
471 $GLOBALS['PATIENT_INC_COUNT'] = $tmp['count'];
476 * Allow the last name to be followed by a comma and some part of a first name(can
477 * also place middle name after the first name with a space separating them)
478 * Allows comma alone followed by some part of a first name(can also place middle name
479 * after the first name with a space separating them).
480 * Allows comma alone preceded by some part of a last name.
481 * If no comma or space, then will search both last name and first name.
482 * If the first letter of either name is capital, searches for name starting
483 * with given substring (the expected behavior). If it is lower case, it
484 * searches for the substring anywhere in the name. This applies to either
485 * last name, first name, and middle name.
486 * Also allows first name followed by middle and/or last name when separated by spaces.
487 * @param string $term
488 * @param string $given
489 * @param string $orderby
490 * @param string $limit
491 * @param string $start
492 * @return array
494 // To prevent sql injection on this function, if a variable is used for $given OR $orderby parameter, then
495 // it needs to be escaped via whitelisting prior to using this function.
496 function getPatientLnames($term = "%", $given = "pid, id, lname, fname, mname, providerID, DATE_FORMAT(DOB,'%m/%d/%Y') as DOB_TS", $orderby = "lname ASC, fname ASC", $limit = "all", $start = "0")
498 $names = getPatientNameSplit($term);
500 foreach ($names as $key => $val) {
501 if (!empty($val)) {
502 if ((strlen($val) > 1) && ($names[$key][0] != strtoupper($names[$key][0]))) {
503 $names[$key] = '%' . $val . '%';
504 } else {
505 $names[$key] = $val . '%';
510 // Debugging section below
511 //if(array_key_exists('first',$names)) {
512 // error_log("first name search term :".$names['first']);
514 //if(array_key_exists('middle',$names)) {
515 // error_log("middle name search term :".$names['middle']);
517 //if(array_key_exists('last',$names)) {
518 // error_log("last name search term :".$names['last']);
520 // Debugging section above
522 $sqlBindArray = array();
523 if (array_key_exists('last', $names) && $names['last'] == '') {
524 // Do not search last name
525 $where = "fname LIKE ? ";
526 array_push($sqlBindArray, $names['first']);
527 if ($names['middle'] != '') {
528 $where .= "AND mname LIKE ? ";
529 array_push($sqlBindArray, $names['middle']);
531 } elseif (array_key_exists('first', $names) && $names['first'] == '') {
532 // Do not search first name or middle name
533 $where = "lname LIKE ? ";
534 array_push($sqlBindArray, $names['last']);
535 } elseif (empty($names['first']) && !empty($names['last'])) {
536 // Search both first name and last name with same term
537 $names['first'] = $names['last'];
538 $where = "lname LIKE ? OR fname LIKE ? ";
539 array_push($sqlBindArray, $names['last'], $names['first']);
540 } elseif ($names['middle'] != '') {
541 $where = "lname LIKE ? AND fname LIKE ? AND mname LIKE ? ";
542 array_push($sqlBindArray, $names['last'], $names['first'], $names['middle']);
543 } else {
544 $where = "lname LIKE ? AND fname LIKE ? ";
545 array_push($sqlBindArray, $names['last'], $names['first']);
548 if (!empty($GLOBALS['pt_restrict_field'])) {
549 if ($_SESSION["authUser"] != 'admin' || $GLOBALS['pt_restrict_admin']) {
550 $where .= " AND ( patient_data." . add_escape_custom($GLOBALS['pt_restrict_field']) .
551 " = ( SELECT facility_id FROM users WHERE username = ?) OR patient_data." .
552 add_escape_custom($GLOBALS['pt_restrict_field']) . " = '' ) ";
553 array_push($sqlBindArray, $_SESSION["authUser"]);
557 $sql = "SELECT $given FROM patient_data WHERE $where ORDER BY $orderby";
558 if ($limit != "all") {
559 $sql .= " limit " . escape_limit($start) . ", " . escape_limit($limit);
562 $rez = sqlStatement($sql, $sqlBindArray);
564 $returnval = array();
565 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
566 $returnval[$iter] = $row;
569 if (is_countable($returnval)) {
570 _set_patient_inc_count($limit, count($returnval), $where, $sqlBindArray);
573 return $returnval;
576 * Accept a string used by a search function expected to find a patient name,
577 * then split up the string if a comma or space exists. Return an array having
578 * from 1 to 3 elements, named first, middle, and last.
579 * See above getPatientLnames() function for details on how the splitting occurs.
580 * @param string $term
581 * @return array
583 function getPatientNameSplit($term)
585 $term = trim($term);
586 if (strpos($term, ',') !== false) {
587 $names = explode(',', $term);
588 $n['last'] = $names[0];
589 if (strpos(trim($names[1]), ' ') !== false) {
590 list($n['first'], $n['middle']) = explode(' ', trim($names[1]));
591 } else {
592 $n['first'] = $names[1];
594 } elseif (strpos($term, ' ') !== false) {
595 $names = explode(' ', $term);
596 if (count($names) == 1) {
597 $n['last'] = $names[0];
598 } elseif (count($names) == 3) {
599 $n['first'] = $names[0];
600 $n['middle'] = $names[1];
601 $n['last'] = $names[2];
602 } else {
603 // This will handle first and last name or first followed by
604 // multiple names only using just the last of the names in the list.
605 $n['first'] = $names[0];
606 $n['last'] = end($names);
608 } else {
609 $n['last'] = $term;
610 if (empty($n['last'])) {
611 $n['last'] = '%';
615 // Trim whitespace off the names before returning
616 foreach ($n as $key => $val) {
617 $n[$key] = trim($val);
620 return $n; // associative array containing names
623 // To prevent sql injection on this function, if a variable is used for $given OR $orderby parameter, then
624 // it needs to be escaped via whitelisting prior to using this function.
625 function getPatientId($pid = "%", $given = "pid, id, lname, fname, mname, providerID, DATE_FORMAT(DOB,'%m/%d/%Y') as DOB_TS", $orderby = "lname ASC, fname ASC", $limit = "all", $start = "0")
628 $sqlBindArray = array();
629 $where = "pubpid LIKE ? ";
630 array_push($sqlBindArray, $pid . "%");
631 if (!empty($GLOBALS['pt_restrict_field']) && $GLOBALS['pt_restrict_by_id']) {
632 if ($_SESSION["authUser"] != 'admin' || $GLOBALS['pt_restrict_admin']) {
633 $where .= "AND ( patient_data." . add_escape_custom($GLOBALS['pt_restrict_field']) .
634 " = ( SELECT facility_id FROM users WHERE username = ?) OR patient_data." .
635 add_escape_custom($GLOBALS['pt_restrict_field']) . " = '' ) ";
636 array_push($sqlBindArray, $_SESSION["authUser"]);
640 $sql = "SELECT $given FROM patient_data WHERE $where ORDER BY $orderby";
641 if ($limit != "all") {
642 $sql .= " limit " . escape_limit($start) . ", " . escape_limit($limit);
645 $rez = sqlStatement($sql, $sqlBindArray);
646 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
647 $returnval[$iter] = $row;
650 if (is_countable($returnval)) {
651 _set_patient_inc_count($limit, count($returnval), $where, $sqlBindArray);
653 return $returnval;
656 // To prevent sql injection on this function, if a variable is used for $given OR $orderby parameter, then
657 // it needs to be escaped via whitelisting prior to using this function.
658 function getByPatientDemographics($searchTerm = "%", $given = "pid, id, lname, fname, mname, providerID, DATE_FORMAT(DOB,'%m/%d/%Y') as DOB_TS", $orderby = "lname ASC, fname ASC", $limit = "all", $start = "0")
660 $layoutCols = sqlStatement(
661 "SELECT field_id FROM layout_options WHERE form_id = 'DEM' AND field_id not like ? AND uor != 0",
662 array('em\_%')
665 $sqlBindArray = array();
666 $where = "";
667 for ($iter = 0; $row = sqlFetchArray($layoutCols); $iter++) {
668 if ($iter > 0) {
669 $where .= " or ";
672 $where .= " " . add_escape_custom($row["field_id"]) . " like ? ";
673 array_push($sqlBindArray, "%" . $searchTerm . "%");
676 $sql = "SELECT $given FROM patient_data WHERE $where ORDER BY $orderby";
677 if ($limit != "all") {
678 $sql .= " limit " . escape_limit($start) . ", " . escape_limit($limit);
681 $rez = sqlStatement($sql, $sqlBindArray);
682 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
683 $returnval[$iter] = $row;
686 if (is_countable($returnval)) {
687 _set_patient_inc_count($limit, count($returnval), $where, $sqlBindArray);
689 return $returnval;
692 // To prevent sql injection on this function, if a variable is used for $given OR $orderby parameter, then
693 // it needs to be escaped via whitelisting prior to using this function.
694 function getByPatientDemographicsFilter(
695 $searchFields,
696 $searchTerm = "%",
697 $given = "pid, id, lname, fname, mname, providerID, DATE_FORMAT(DOB,'%m/%d/%Y') as DOB_TS",
698 $orderby = "lname ASC, fname ASC",
699 $limit = "all",
700 $start = "0",
701 $search_service_code = ''
704 $layoutCols = explode('~', $searchFields);
705 $sqlBindArray = array();
706 $where = "";
707 $i = 0;
708 foreach ($layoutCols as $val) {
709 if (empty($val)) {
710 continue;
713 if ($i > 0) {
714 $where .= " or ";
717 if ($val == 'pid') {
718 $where .= " " . escape_sql_column_name($val, ['patient_data']) . " = ? ";
719 array_push($sqlBindArray, $searchTerm);
720 } else {
721 $where .= " " . escape_sql_column_name($val, ['patient_data']) . " like ? ";
722 array_push($sqlBindArray, $searchTerm . "%");
725 $i++;
728 // If no search terms, ensure valid syntax.
729 if ($i == 0) {
730 $where = "1 = 1";
733 // If a non-empty service code was given, then restrict to patients who
734 // have been provided that service. Since the code is used in a LIKE
735 // clause, % and _ wildcards are supported.
736 if ($search_service_code) {
737 $where = "( $where ) AND " .
738 "( SELECT COUNT(*) FROM billing AS b WHERE " .
739 "b.pid = patient_data.pid AND " .
740 "b.activity = 1 AND " .
741 "b.code_type != 'COPAY' AND " .
742 "b.code LIKE ? " .
743 ") > 0";
744 array_push($sqlBindArray, $search_service_code);
747 $sql = "SELECT $given FROM patient_data WHERE $where ORDER BY $orderby";
748 if ($limit != "all") {
749 $sql .= " limit " . escape_limit($start) . ", " . escape_limit($limit);
752 $rez = sqlStatement($sql, $sqlBindArray);
753 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
754 $returnval[$iter] = $row;
757 if (is_countable($returnval)) {
758 _set_patient_inc_count($limit, count($returnval), $where, $sqlBindArray);
760 return $returnval;
763 // return a collection of Patient PIDs
764 // new arg style by JRM March 2008
765 // orig function getPatientPID($pid = "%", $given = "pid, id, lname, fname, mname, providerID, DATE_FORMAT(DOB,'%m/%d/%Y') as DOB_TS", $orderby = "lname ASC, fname ASC", $limit="all", $start="0")
766 // To prevent sql injection on this function, if a variable is used for $given OR $orderby parameter, then
767 // it needs to be escaped via whitelisting prior to using this function.
768 function getPatientPID($args)
770 $pid = "%";
771 $given = "pid, id, lname, fname, mname, suffix, providerID, DATE_FORMAT(DOB,'%m/%d/%Y') as DOB_TS";
772 $orderby = "lname ASC, fname ASC";
773 $limit = "all";
774 $start = "0";
776 // alter default values if defined in the passed in args
777 if (isset($args['pid'])) {
778 $pid = $args['pid'];
781 if (isset($args['given'])) {
782 $given = $args['given'];
785 if (isset($args['orderby'])) {
786 $orderby = $args['orderby'];
789 if (isset($args['limit'])) {
790 $limit = $args['limit'];
793 if (isset($args['start'])) {
794 $start = $args['start'];
797 $command = "=";
798 if ($pid == -1) {
799 $pid = "%";
800 } elseif (empty($pid)) {
801 $pid = "NULL";
804 if (strstr($pid, "%")) {
805 $command = "like";
808 $sql = "select $given from patient_data where pid $command '" . add_escape_custom($pid) . "' order by $orderby";
809 if ($limit != "all") {
810 $sql .= " limit " . escape_limit($start) . ", " . escape_limit($limit);
813 $rez = sqlStatement($sql);
814 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
815 $returnval[$iter] = $row;
818 return $returnval;
821 /* return a patient's name in the format LAST [SUFFIX], FIRST [MIDDLE] */
822 function getPatientName($pid)
824 if (empty($pid)) {
825 return "";
828 $patientData = getPatientPID(array("pid" => $pid));
829 if (empty($patientData[0]['lname'])) {
830 return "";
833 $patientName = $patientData[0]['lname'];
834 $patientName .= $patientData[0]['suffix'] ? " " . $patientData[0]['suffix'] . ", " : ", ";
835 $patientName .= $patientData[0]['fname'];
836 $patientName .= empty($patientData[0]['mname']) ? "" : " " . $patientData[0]['mname'];
837 return $patientName;
841 * Get a patient's first name, middle name, last name and suffix if applicable.
843 * Returns a properly formatted, complete name when applicable. Example name
844 * would be "John B Doe Jr". No additional punctuation is added. Spaces are
845 * correctly omitted if the middle name of suffix does not apply.
847 * @var $pid int The Patient ID
848 * @returns string The Full Name
850 function getPatientFullNameAsString($pid): string
852 if (empty($pid)) {
853 return '';
855 $ptData = getPatientPID(["pid" => $pid]);
856 $pt = $ptData[0];
858 if (empty($pt['lname'])) {
859 return "";
862 $name = $pt['fname'];
864 if ($pt['mname']) {
865 $name .= " {$pt['mname']}";
868 $name .= " {$pt['lname']}";
870 if ($pt['suffix']) {
871 $name .= " {$pt['suffix']}";
874 return $name;
877 /* return a patient's name in the format FIRST LAST */
878 function getPatientNameFirstLast($pid)
880 if (empty($pid)) {
881 return "";
884 $patientData = getPatientPID(array("pid" => $pid));
885 if (empty($patientData[0]['lname'])) {
886 return "";
889 $patientName = $patientData[0]['fname'] . " " . $patientData[0]['lname'];
890 return $patientName;
893 /* find patient data by DOB */
894 // To prevent sql injection on this function, if a variable is used for $given OR $orderby parameter, then
895 // it needs to be escaped via whitelisting prior to using this function.
896 function getPatientDOB($DOB = "%", $given = "pid, id, lname, fname, mname", $orderby = "lname ASC, fname ASC", $limit = "all", $start = "0")
898 $sqlBindArray = array();
899 $where = "DOB like ? ";
900 array_push($sqlBindArray, $DOB . "%");
901 if (!empty($GLOBALS['pt_restrict_field'])) {
902 if ($_SESSION["authUser"] != 'admin' || $GLOBALS['pt_restrict_admin']) {
903 $where .= "AND ( patient_data." . add_escape_custom($GLOBALS['pt_restrict_field']) .
904 " = ( SELECT facility_id FROM users WHERE username = ?) OR patient_data." .
905 add_escape_custom($GLOBALS['pt_restrict_field']) . " = '' ) ";
906 array_push($sqlBindArray, $_SESSION["authUser"]);
910 $sql = "SELECT $given FROM patient_data WHERE $where ORDER BY $orderby";
912 if ($limit != "all") {
913 $sql .= " limit " . escape_limit($start) . ", " . escape_limit($limit);
916 $rez = sqlStatement($sql, $sqlBindArray);
917 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
918 $returnval[$iter] = $row;
921 if (is_countable($returnval)) {
922 _set_patient_inc_count($limit, count($returnval), $where, $sqlBindArray);
924 return $returnval;
927 /* find patient data by SSN */
928 // To prevent sql injection on this function, if a variable is used for $given OR $orderby parameter, then
929 // it needs to be escaped via whitelisting prior to using this function.
930 function getPatientSSN($ss = "%", $given = "pid, id, lname, fname, mname, providerID", $orderby = "lname ASC, fname ASC", $limit = "all", $start = "0")
932 $sqlBindArray = array();
933 $where = "ss LIKE ?";
934 array_push($sqlBindArray, $ss . "%");
935 $sql = "SELECT $given FROM patient_data WHERE $where ORDER BY $orderby";
936 if ($limit != "all") {
937 $sql .= " limit " . escape_limit($start) . ", " . escape_limit($limit);
940 $rez = sqlStatement($sql, $sqlBindArray);
941 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
942 $returnval[$iter] = $row;
945 if (is_countable($returnval)) {
946 _set_patient_inc_count($limit, count($returnval), $where, $sqlBindArray);
948 return $returnval;
951 //(CHEMED) Search by phone number
952 // To prevent sql injection on this function, if a variable is used for $given OR $orderby parameter, then
953 // it needs to be escaped via whitelisting prior to using this function.
954 function getPatientPhone($phone = "%", $given = "pid, id, lname, fname, mname, providerID", $orderby = "lname ASC, fname ASC", $limit = "all", $start = "0")
956 $phone = preg_replace("/[[:punct:]]/", "", $phone);
957 $sqlBindArray = array();
958 $where = "REPLACE(REPLACE(phone_home, '-', ''), ' ', '') REGEXP ?";
959 array_push($sqlBindArray, $phone);
960 $sql = "SELECT $given FROM patient_data WHERE $where ORDER BY $orderby";
961 if ($limit != "all") {
962 $sql .= " limit " . escape_limit($start) . ", " . escape_limit($limit);
965 $rez = sqlStatement($sql, $sqlBindArray);
966 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
967 $returnval[$iter] = $row;
970 if (is_countable($returnval)) {
971 _set_patient_inc_count($limit, count($returnval), $where, $sqlBindArray);
973 return $returnval;
976 //----------------------input functions
977 function newPatientData(
978 $db_id = "",
979 $title = "",
980 $fname = "",
981 $lname = "",
982 $mname = "",
983 $sex = "",
984 $DOB = "",
985 $street = "",
986 $postal_code = "",
987 $city = "",
988 $state = "",
989 $country_code = "",
990 $ss = "",
991 $occupation = "",
992 $phone_home = "",
993 $phone_biz = "",
994 $phone_contact = "",
995 $status = "",
996 $contact_relationship = "",
997 $referrer = "",
998 $referrerID = "",
999 $email = "",
1000 $language = "",
1001 $ethnoracial = "",
1002 $interpretter = "",
1003 $migrantseasonal = "",
1004 $family_size = "",
1005 $monthly_income = "",
1006 $homeless = "",
1007 $financial_review = "",
1008 $pubpid = "",
1009 $pid = "MAX(pid)+1",
1010 $providerID = "",
1011 $genericname1 = "",
1012 $genericval1 = "",
1013 $genericname2 = "",
1014 $genericval2 = "",
1015 $billing_note = "",
1016 $phone_cell = "",
1017 $hipaa_mail = "",
1018 $hipaa_voice = "",
1019 $squad = 0,
1020 $pharmacy_id = 0,
1021 $drivers_license = "",
1022 $hipaa_notice = "",
1023 $hipaa_message = "",
1024 $regdate = ""
1027 $fitness = 0;
1028 $referral_source = '';
1029 if ($pid) {
1030 $rez = sqlQuery("select id, fitness, referral_source from patient_data where pid = ?", array($pid));
1031 // Check for brain damage:
1032 if ($db_id != $rez['id']) {
1033 $errmsg = "Internal error: Attempt to change patient_data.id from '" .
1034 text($rez['id']) . "' to '" . text($db_id) . "' for pid '" . text($pid) . "'";
1035 die($errmsg);
1038 $fitness = $rez['fitness'];
1039 $referral_source = $rez['referral_source'];
1042 // Get the default price level.
1043 $lrow = sqlQuery("SELECT option_id FROM list_options WHERE " .
1044 "list_id = 'pricelevel' AND activity = 1 ORDER BY is_default DESC, seq ASC LIMIT 1");
1045 $pricelevel = empty($lrow['option_id']) ? '' : $lrow['option_id'];
1047 $query = ("replace into patient_data set
1048 id='" . add_escape_custom($db_id) . "',
1049 title='" . add_escape_custom($title) . "',
1050 fname='" . add_escape_custom($fname) . "',
1051 lname='" . add_escape_custom($lname) . "',
1052 mname='" . add_escape_custom($mname) . "',
1053 sex='" . add_escape_custom($sex) . "',
1054 DOB='" . add_escape_custom($DOB) . "',
1055 street='" . add_escape_custom($street) . "',
1056 postal_code='" . add_escape_custom($postal_code) . "',
1057 city='" . add_escape_custom($city) . "',
1058 state='" . add_escape_custom($state) . "',
1059 country_code='" . add_escape_custom($country_code) . "',
1060 drivers_license='" . add_escape_custom($drivers_license) . "',
1061 ss='" . add_escape_custom($ss) . "',
1062 occupation='" . add_escape_custom($occupation) . "',
1063 phone_home='" . add_escape_custom($phone_home) . "',
1064 phone_biz='" . add_escape_custom($phone_biz) . "',
1065 phone_contact='" . add_escape_custom($phone_contact) . "',
1066 status='" . add_escape_custom($status) . "',
1067 contact_relationship='" . add_escape_custom($contact_relationship) . "',
1068 referrer='" . add_escape_custom($referrer) . "',
1069 referrerID='" . add_escape_custom($referrerID) . "',
1070 email='" . add_escape_custom($email) . "',
1071 language='" . add_escape_custom($language) . "',
1072 ethnoracial='" . add_escape_custom($ethnoracial) . "',
1073 interpretter='" . add_escape_custom($interpretter) . "',
1074 migrantseasonal='" . add_escape_custom($migrantseasonal) . "',
1075 family_size='" . add_escape_custom($family_size) . "',
1076 monthly_income='" . add_escape_custom($monthly_income) . "',
1077 homeless='" . add_escape_custom($homeless) . "',
1078 financial_review='" . add_escape_custom($financial_review) . "',
1079 pubpid='" . add_escape_custom($pubpid) . "',
1080 pid= '" . add_escape_custom($pid) . "',
1081 providerID = '" . add_escape_custom($providerID) . "',
1082 genericname1 = '" . add_escape_custom($genericname1) . "',
1083 genericval1 = '" . add_escape_custom($genericval1) . "',
1084 genericname2 = '" . add_escape_custom($genericname2) . "',
1085 genericval2 = '" . add_escape_custom($genericval2) . "',
1086 billing_note= '" . add_escape_custom($billing_note) . "',
1087 phone_cell = '" . add_escape_custom($phone_cell) . "',
1088 pharmacy_id = '" . add_escape_custom($pharmacy_id) . "',
1089 hipaa_mail = '" . add_escape_custom($hipaa_mail) . "',
1090 hipaa_voice = '" . add_escape_custom($hipaa_voice) . "',
1091 hipaa_notice = '" . add_escape_custom($hipaa_notice) . "',
1092 hipaa_message = '" . add_escape_custom($hipaa_message) . "',
1093 squad = '" . add_escape_custom($squad) . "',
1094 fitness='" . add_escape_custom($fitness) . "',
1095 referral_source='" . add_escape_custom($referral_source) . "',
1096 regdate='" . add_escape_custom($regdate) . "',
1097 pricelevel='" . add_escape_custom($pricelevel) . "',
1098 date=NOW()");
1100 $id = sqlInsert($query);
1102 if (!$db_id) {
1103 // find the last inserted id for new patient case
1104 $db_id = $id;
1107 $foo = sqlQuery("select `pid`, `uuid` from `patient_data` where `id` = ? order by `date` limit 0,1", array($id));
1109 // set uuid if not set yet (if this was an insert and not an update)
1110 if (empty($foo['uuid'])) {
1111 $uuid = (new UuidRegistry(['table_name' => 'patient_data']))->createUuid();
1112 sqlStatementNoLog("UPDATE `patient_data` SET `uuid` = ? WHERE `id` = ?", [$uuid, $id]);
1115 return $foo['pid'];
1118 // Supported input date formats are:
1119 // mm/dd/yyyy
1120 // mm/dd/yy (assumes 20yy for yy < 10, else 19yy)
1121 // yyyy/mm/dd
1122 // also mm-dd-yyyy, etc. and mm.dd.yyyy, etc.
1124 function fixDate($date, $default = "0000-00-00")
1126 $fixed_date = $default;
1127 $date = trim($date);
1128 if (preg_match("'^[0-9]{1,4}[/.-][0-9]{1,2}[/.-][0-9]{1,4}$'", $date)) {
1129 $dmy = preg_split("'[/.-]'", $date);
1130 if ($dmy[0] > 99) {
1131 $fixed_date = sprintf("%04u-%02u-%02u", $dmy[0], $dmy[1], $dmy[2]);
1132 } else {
1133 if ($dmy[0] != 0 || $dmy[1] != 0 || $dmy[2] != 0) {
1134 if ($dmy[2] < 1000) {
1135 $dmy[2] += 1900;
1138 if ($dmy[2] < 1910) {
1139 $dmy[2] += 100;
1142 // Determine if MDY date format is used, preferring Date Display Format from
1143 // global settings if it's not YMD, otherwise guessing from country code.
1144 $using_mdy = empty($GLOBALS['date_display_format']) ?
1145 ($GLOBALS['phone_country_code'] == 1) : ($GLOBALS['date_display_format'] == 1);
1146 if ($using_mdy) {
1147 $fixed_date = sprintf("%04u-%02u-%02u", $dmy[2], $dmy[0], $dmy[1]);
1148 } else {
1149 $fixed_date = sprintf("%04u-%02u-%02u", $dmy[2], $dmy[1], $dmy[0]);
1154 return $fixed_date;
1157 function pdValueOrNull($key, $value)
1159 if (
1160 ($key == 'DOB' || $key == 'regdate' || $key == 'contrastart' ||
1161 substr($key, 0, 8) == 'userdate' || $key == 'deceased_date') &&
1162 (empty($value) || $value == '0000-00-00')
1164 return "NULL";
1165 } else {
1166 return "'" . add_escape_custom($value) . "'";
1171 * Create or update patient data from an array.
1173 * This is a wrapper function for the PatientService which is now the single point
1174 * of patient creation and update.
1176 * If successful, returns the pid of the patient
1178 * @param $pid
1179 * @param $new
1180 * @param false $create
1181 * @return mixed
1183 function updatePatientData($pid, $new, $create = false)
1185 // Create instance of patient service
1186 $patientService = new PatientService();
1187 if (
1188 $create === true ||
1189 $pid === null
1191 $result = $patientService->databaseInsert($new);
1192 updateDupScore($result['pid']);
1193 } else {
1194 $new['pid'] = $pid;
1195 $result = $patientService->databaseUpdate($new);
1198 // From the returned patient data array
1199 // retrieve the data and return the pid
1200 $pid = $result['pid'];
1202 return $pid;
1205 function newEmployerData(
1206 $pid,
1207 $name = "",
1208 $street = "",
1209 $postal_code = "",
1210 $city = "",
1211 $state = "",
1212 $country = ""
1215 return sqlInsert("insert into employer_data set
1216 name='" . add_escape_custom($name) . "',
1217 street='" . add_escape_custom($street) . "',
1218 postal_code='" . add_escape_custom($postal_code) . "',
1219 city='" . add_escape_custom($city) . "',
1220 state='" . add_escape_custom($state) . "',
1221 country='" . add_escape_custom($country) . "',
1222 pid='" . add_escape_custom($pid) . "',
1223 date=NOW()
1227 // Create or update employer data from an array.
1229 function updateEmployerData($pid, $new, $create = false)
1231 // used to hard code colnames array('name','street','city','state','postal_code','country');
1232 // but now adapted for layout based
1233 $colnames = array();
1234 foreach ($new as $key => $value) {
1235 $colnames[] = $key;
1238 if ($create) {
1239 $set = "pid = '" . add_escape_custom($pid) . "', date = NOW()";
1240 foreach ($colnames as $key) {
1241 $value = isset($new[$key]) ? $new[$key] : '';
1242 $set .= ", `$key` = '" . add_escape_custom($value) . "'";
1245 return sqlInsert("INSERT INTO employer_data SET $set");
1246 } else {
1247 $set = '';
1248 $old = getEmployerData($pid);
1249 $modified = false;
1250 foreach ($colnames as $key) {
1251 $value = empty($old[$key]) ? '' : $old[$key];
1252 if (isset($new[$key]) && strcmp($new[$key], $value) != 0) {
1253 $value = $new[$key];
1254 $modified = true;
1257 $set .= "`$key` = '" . add_escape_custom($value) . "', ";
1260 if ($modified) {
1261 $set .= "pid = '" . add_escape_custom($pid) . "', date = NOW()";
1262 return sqlInsert("INSERT INTO employer_data SET $set");
1265 return ($old['id'] ?? '');
1269 // This updates or adds the given insurance data info, while retaining any
1270 // previously added insurance_data rows that should be preserved.
1271 // This does not directly support the maintenance of non-current insurance.
1273 function newInsuranceData(
1274 $pid,
1275 $type = "",
1276 $provider = "",
1277 $policy_number = "",
1278 $group_number = "",
1279 $plan_name = "",
1280 $subscriber_lname = "",
1281 $subscriber_mname = "",
1282 $subscriber_fname = "",
1283 $subscriber_relationship = "",
1284 $subscriber_ss = "",
1285 $subscriber_DOB = null,
1286 $subscriber_street = "",
1287 $subscriber_postal_code = "",
1288 $subscriber_city = "",
1289 $subscriber_state = "",
1290 $subscriber_country = "",
1291 $subscriber_phone = "",
1292 $subscriber_employer = "",
1293 $subscriber_employer_street = "",
1294 $subscriber_employer_city = "",
1295 $subscriber_employer_postal_code = "",
1296 $subscriber_employer_state = "",
1297 $subscriber_employer_country = "",
1298 $copay = "",
1299 $subscriber_sex = "",
1300 $effective_date = null,
1301 $accept_assignment = "TRUE",
1302 $policy_type = "",
1303 $effective_date_end = null
1306 if (strlen($type) <= 0) {
1307 return false;
1310 if (is_null($accept_assignment)) {
1311 $accept_assignment = "TRUE";
1313 if (is_null($policy_type)) {
1314 $policy_type = "";
1317 // If empty dates were passed, then null.
1318 if (empty($effective_date)) {
1319 $effective_date = null;
1321 if (empty($subscriber_DOB)) {
1322 $subscriber_DOB = null;
1324 if (empty($effective_date_end)) {
1325 $effective_date_end = null;
1328 return sqlInsert(
1329 "INSERT INTO `insurance_data` SET `type` = ?,
1330 `provider` = ?,
1331 `policy_number` = ?,
1332 `group_number` = ?,
1333 `plan_name` = ?,
1334 `subscriber_lname` = ?,
1335 `subscriber_mname` = ?,
1336 `subscriber_fname` = ?,
1337 `subscriber_relationship` = ?,
1338 `subscriber_ss` = ?,
1339 `subscriber_DOB` = ?,
1340 `subscriber_street` = ?,
1341 `subscriber_postal_code` = ?,
1342 `subscriber_city` = ?,
1343 `subscriber_state` = ?,
1344 `subscriber_country` = ?,
1345 `subscriber_phone` = ?,
1346 `subscriber_employer` = ?,
1347 `subscriber_employer_city` = ?,
1348 `subscriber_employer_street` = ?,
1349 `subscriber_employer_postal_code` = ?,
1350 `subscriber_employer_state` = ?,
1351 `subscriber_employer_country` = ?,
1352 `copay` = ?,
1353 `subscriber_sex` = ?,
1354 `pid` = ?,
1355 `date` = ?,
1356 `accept_assignment` = ?,
1357 `policy_type` = ?,
1358 `date_end` = ?",
1360 $type,
1361 $provider,
1362 $policy_number,
1363 $group_number,
1364 $plan_name,
1365 $subscriber_lname,
1366 $subscriber_mname,
1367 $subscriber_fname,
1368 $subscriber_relationship,
1369 $subscriber_ss,
1370 $subscriber_DOB,
1371 $subscriber_street,
1372 $subscriber_postal_code,
1373 $subscriber_city,
1374 $subscriber_state,
1375 $subscriber_country,
1376 $subscriber_phone,
1377 $subscriber_employer,
1378 $subscriber_employer_city,
1379 $subscriber_employer_street,
1380 $subscriber_employer_postal_code,
1381 $subscriber_employer_state,
1382 $subscriber_employer_country,
1383 $copay,
1384 $subscriber_sex,
1385 $pid,
1386 $effective_date,
1387 $accept_assignment,
1388 $policy_type,
1389 $effective_date_end
1394 // This is used internally only.
1395 function updateInsuranceData($id, $new)
1397 $fields = sqlListFields("insurance_data");
1398 $use = array();
1400 foreach ($new as $key => $value) {
1401 if (in_array($key, $fields)) {
1402 $use[$key] = $value;
1406 $sqlBindArray = [];
1407 $sql = "UPDATE insurance_data SET ";
1408 foreach ($use as $key => $value) {
1409 $sql .= "`" . $key . "` = ?, ";
1410 array_push($sqlBindArray, $value);
1413 $sql = substr($sql, 0, -2) . " WHERE id = ?";
1414 array_push($sqlBindArray, $id);
1416 sqlStatement($sql, $sqlBindArray);
1419 function newHistoryData($pid, $new = false)
1421 $socialHistoryService = new SocialHistoryService();
1423 $insertionRecord = $new;
1424 if (!is_array(($insertionRecord))) {
1425 $insertionRecord = [
1426 'pid' => $pid
1429 $socialHistoryService->create($insertionRecord);
1432 function updateHistoryData($pid, $new)
1434 $socialHistoryService = new SocialHistoryService();
1435 return $socialHistoryService->updateHistoryDataForPatientPid($pid, $new);
1438 // Returns Age
1439 // in months if < 2 years old
1440 // in years if > 2 years old
1441 // given YYYYMMDD from MySQL DATE_FORMAT(DOB,'%Y%m%d')
1442 // (optional) nowYMD is a date in YYYYMMDD format
1443 function getPatientAge($dobYMD, $nowYMD = null)
1445 $patientService = new PatientService();
1446 return $patientService->getPatientAge($dobYMD, $nowYMD);
1450 * Wrapper to make sure the clinical rules dates formats corresponds to the
1451 * format expected by getPatientAgeYMD
1453 * @param string $dob date of birth
1454 * @param string $target date to calculate age on
1455 * @return array containing
1456 * age - decimal age in years
1457 * age_in_months - decimal age in months
1458 * ageinYMD - formatted string #y #m #d */
1459 function parseAgeInfo($dob, $target)
1461 // Prepare dob (expected in order Y M D, remove whatever delimiters might be there
1462 $dateDOB = preg_replace("/[-\s\/]/", "", $dob);
1464 // Prepare target (Y-M-D H:M:S)
1465 $dateTarget = preg_replace("/[-\s\/]/", "", $target);
1467 return getPatientAgeYMD($dateDOB, $dateTarget);
1472 * @param type $dob
1473 * @param type $date
1474 * @return array containing
1475 * age - decimal age in years
1476 * age_in_months - decimal age in months
1477 * ageinYMD - formatted string #y #m #d
1479 function getPatientAgeYMD($dob, $date = null)
1481 $service = new PatientService();
1482 return $service->getPatientAgeYMD($dob, $date);
1485 // Returns Age in days
1486 // in months if < 2 years old
1487 // in years if > 2 years old
1488 // given YYYYMMDD from MySQL DATE_FORMAT(DOB,'%Y%m%d')
1489 // (optional) nowYMD is a date in YYYYMMDD format
1490 function getPatientAgeInDays($dobYMD, $nowYMD = null)
1492 $age = -1;
1494 // strip any dashes from the DOB
1495 $dobYMD = preg_replace("/-/", "", $dobYMD);
1496 $dobDay = substr($dobYMD, 6, 2);
1497 $dobMonth = substr($dobYMD, 4, 2);
1498 $dobYear = substr($dobYMD, 0, 4);
1500 // set the 'now' date values
1501 if ($nowYMD == null) {
1502 $nowDay = date("d");
1503 $nowMonth = date("m");
1504 $nowYear = date("Y");
1505 } else {
1506 $nowDay = substr($nowYMD, 6, 2);
1507 $nowMonth = substr($nowYMD, 4, 2);
1508 $nowYear = substr($nowYMD, 0, 4);
1511 // do the date math
1512 $dobtime = strtotime($dobYear . "-" . $dobMonth . "-" . $dobDay);
1513 $nowtime = strtotime($nowYear . "-" . $nowMonth . "-" . $nowDay);
1514 $timediff = $nowtime - $dobtime;
1515 $age = $timediff / 86400; // 24 hours * 3600 seconds/hour = 86400 seconds
1517 return $age;
1520 * Returns a string to be used to display a patient's age
1522 * @param type $dobYMD
1523 * @param type $asOfYMD
1524 * @return string suitable for displaying patient's age based on preferences
1526 function getPatientAgeDisplay($dobYMD, $asOfYMD = null)
1528 $service = new PatientService();
1529 return $service->getPatientAgeDisplay($dobYMD, $asOfYMD);
1531 function dateToDB($date)
1533 $date = substr($date, 6, 4) . "-" . substr($date, 3, 2) . "-" . substr($date, 0, 2);
1534 return $date;
1538 * Get up to 3 insurances (primary, secondary, tertiary) that are effective
1539 * for the given patient on the given date.
1541 * @param int The PID of the patient.
1542 * @param string Date in yyyy-mm-dd format.
1543 * @return array Array of 0-3 insurance_data rows.
1545 function getEffectiveInsurances($patient_id, $encdate)
1547 $insarr = array();
1548 foreach (array('primary','secondary','tertiary') as $instype) {
1549 $tmp = sqlQuery(
1550 "SELECT * FROM insurance_data " .
1551 "WHERE pid = ? AND type = ? " .
1552 "AND (date <= ? OR date IS NULL) ORDER BY date DESC LIMIT 1",
1553 array($patient_id, $instype, $encdate)
1555 if (empty($tmp['provider'])) {
1556 break;
1559 $insarr[] = $tmp;
1562 return $insarr;
1566 * Get all requisition insurance companies
1571 function getAllinsurances($pid)
1573 $insarr = array();
1574 $sql = "SELECT a.type, a.provider, a.plan_name, a.policy_number, a.group_number,
1575 a.subscriber_lname, a.subscriber_fname, a.subscriber_relationship, a.subscriber_employer,
1576 b.name, c.line1, c.line2, c.city, c.state, c.zip
1577 FROM `insurance_data` AS a
1578 RIGHT JOIN insurance_companies AS b
1579 ON a.provider = b.id
1580 RIGHT JOIN addresses AS c
1581 ON a.provider = c.foreign_id
1582 WHERE a.pid = ? ";
1583 $inco = sqlStatement($sql, array($pid));
1585 while ($icl = sqlFetchArray($inco)) {
1586 $insarr[] = $icl;
1588 return $insarr;
1592 * Get the patient's balance due. Normally this excludes amounts that are out
1593 * to insurance. If you want to include what insurance owes, set the second
1594 * parameter to true.
1596 * @param int The PID of the patient.
1597 * @param boolean Indicates if amounts owed by insurance are to be included.
1598 * @param int Optional encounter id. If value is passed, will fetch only bills from specified encounter.
1599 * @return number The balance.
1601 function get_patient_balance($pid, $with_insurance = false, $eid = false)
1603 $balance = 0;
1604 $bindarray = array($pid);
1605 $sqlstatement = "SELECT date, encounter, last_level_billed, " .
1606 "last_level_closed, stmt_count " .
1607 "FROM form_encounter WHERE pid = ?";
1608 if ($eid) {
1609 $sqlstatement .= " AND encounter = ?";
1610 array_push($bindarray, $eid);
1612 $feres = sqlStatement($sqlstatement, $bindarray);
1613 while ($ferow = sqlFetchArray($feres)) {
1614 $encounter = $ferow['encounter'];
1615 $dos = substr($ferow['date'], 0, 10);
1616 $insarr = getEffectiveInsurances($pid, $dos);
1617 $inscount = count($insarr);
1618 if (!$with_insurance && $ferow['last_level_closed'] < $inscount && $ferow['stmt_count'] == 0) {
1619 // It's out to insurance so only the co-pay might be due.
1620 $brow = sqlQuery(
1621 "SELECT SUM(fee) AS amount FROM billing WHERE " .
1622 "pid = ? AND encounter = ? AND " .
1623 "code_type = 'copay' AND activity = 1",
1624 array($pid, $encounter)
1626 $drow = sqlQuery(
1627 "SELECT SUM(pay_amount) AS payments " .
1628 "FROM ar_activity WHERE " .
1629 "deleted IS NULL AND pid = ? AND encounter = ? AND payer_type = 0",
1630 array($pid, $encounter)
1632 // going to comment this out for now since computing future copays doesn't
1633 // equate to cash in hand, which shows in the Billing widget in dashboard 4-23-21
1634 // $copay = !empty($insarr[0]['copay']) ? $insarr[0]['copay'] * 1 : 0;
1635 $copay = 0;
1637 $amt = !empty($brow['amount']) ? $brow['amount'] * 1 : 0;
1638 $pay = !empty($drow['payments']) ? $drow['payments'] * 1 : 0;
1639 $ptbal = $copay + $amt - $pay;
1640 if ($ptbal) { // @TODO check if we want to show patient payment credits.
1641 $balance += $ptbal;
1643 } else {
1644 // Including insurance or not out to insurance, everything is due.
1645 $brow = sqlQuery("SELECT SUM(fee) AS amount FROM billing WHERE " .
1646 "pid = ? AND encounter = ? AND " .
1647 "activity = 1", array($pid, $encounter));
1648 $drow = sqlQuery("SELECT SUM(pay_amount) AS payments, " .
1649 "SUM(adj_amount) AS adjustments FROM ar_activity WHERE " .
1650 "deleted IS NULL AND pid = ? AND encounter = ?", array($pid, $encounter));
1651 $srow = sqlQuery("SELECT SUM(fee) AS amount FROM drug_sales WHERE " .
1652 "pid = ? AND encounter = ?", array($pid, $encounter));
1653 $balance += $brow['amount'] + $srow['amount']
1654 - $drow['payments'] - $drow['adjustments'];
1658 return sprintf('%01.2f', $balance);
1661 function get_patient_balance_excluding($pid, $excluded = -1)
1663 // We join form_encounter here to make sure we only count amounts for
1664 // encounters that exist. We've had some trouble before with encounters
1665 // that were deleted but leaving line items in the database.
1666 $brow = sqlQuery(
1667 "SELECT SUM(b.fee) AS amount " .
1668 "FROM billing AS b, form_encounter AS fe WHERE " .
1669 "b.pid = ? AND b.encounter != 0 AND b.encounter != ? AND b.activity = 1 AND " .
1670 "fe.pid = b.pid AND fe.encounter = b.encounter",
1671 array($pid, $excluded)
1673 $srow = sqlQuery(
1674 "SELECT SUM(s.fee) AS amount " .
1675 "FROM drug_sales AS s, form_encounter AS fe WHERE " .
1676 "s.pid = ? AND s.encounter != 0 AND s.encounter != ? AND " .
1677 "fe.pid = s.pid AND fe.encounter = s.encounter",
1678 array($pid, $excluded)
1680 $drow = sqlQuery(
1681 "SELECT SUM(a.pay_amount) AS payments, " .
1682 "SUM(a.adj_amount) AS adjustments " .
1683 "FROM ar_activity AS a, form_encounter AS fe WHERE " .
1684 "a.deleted IS NULL AND a.pid = ? AND a.encounter != 0 AND a.encounter != ? AND " .
1685 "fe.pid = a.pid AND fe.encounter = a.encounter",
1686 array($pid, $excluded)
1688 return sprintf(
1689 '%01.2f',
1690 $brow['amount'] + $srow['amount'] - $drow['payments'] - $drow['adjustments']
1694 // Function to check if patient is deceased.
1695 // Param:
1696 // $pid - patient id
1697 // $date - date checking if deceased (will default to current date if blank)
1698 // Return:
1699 // If deceased, then will return the number of
1700 // days that patient has been deceased and the deceased date.
1701 // If not deceased, then will return false.
1702 function is_patient_deceased($pid, $date = '')
1705 // Set date to current if not set
1706 $date = (!empty($date)) ? $date : date('Y-m-d H:i:s');
1708 // Query for deceased status (if person is deceased gets days_deceased and date_deceased)
1709 $results = sqlQuery("SELECT DATEDIFF(?,`deceased_date`) AS `days_deceased`, `deceased_date` AS `date_deceased` " .
1710 "FROM `patient_data` " .
1711 "WHERE `pid` = ? AND " .
1712 dateEmptySql('deceased_date', true, true) .
1713 "AND `deceased_date` <= ?", array($date,$pid,$date));
1715 if (empty($results)) {
1716 // Patient is alive, so return false
1717 return false;
1718 } else {
1719 // Patient is dead, so return the number of days patient has been deceased.
1720 // Don't let it be zero days or else will confuse calls to this function.
1721 if ($results['days_deceased'] === 0) {
1722 $results['days_deceased'] = 1;
1725 return $results;
1729 // This computes, sets and returns the dup score for the given patient.
1731 function updateDupScore($pid)
1733 $row = sqlQuery(
1734 "SELECT MAX(" . getDupScoreSQL() . ") AS dupscore " .
1735 "FROM patient_data AS p1, patient_data AS p2 WHERE " .
1736 "p1.pid = ? AND p2.pid < p1.pid",
1737 array($pid)
1739 $dupscore = empty($row['dupscore']) ? 0 : $row['dupscore'];
1740 sqlStatement(
1741 "UPDATE patient_data SET dupscore = ? WHERE pid = ?",
1742 array($dupscore, $pid)
1744 return $dupscore;
1747 function get_unallocated_payment_id($pid)
1749 $query = "SELECT session_id " .
1750 "FROM ar_session " .
1751 "WHERE patient_id = ? AND " .
1752 "adjustment_code = 'pre_payment' AND closed = 0 ORDER BY check_date ASC LIMIT 1";
1753 $res = sqlQuery($query, array($pid));
1754 if ($res['session_id']) {
1755 return $res['session_id'];
1756 } else {
1757 return '';