if ($fldtype == 'G') { if ($_POST["G1_{$prefix}{$qcode}"]) { $data = $_POST["G1_{$prefix}{$qcode}"] * 7 + $_POST["G2_{$prefix}{$qcode}"]; } } else { $data = $_POST["{$prefix}{$qcode}"]; } if (!isset($data) || $data === '') { continue; } if (!is_array($data)) { $data = array($data); } foreach ($data as $datum) { // Note this will auto-assign the seq value. sqlBeginTrans(); $answer_seq = sqlQuery("SELECT IFNULL(MAX(answer_seq),0) + 1 AS increment FROM procedure_answers WHERE procedure_order_id = ? AND procedure_order_seq = ? AND question_code = ? ", array($formid, $poseq, $qcode)); sqlStatement("INSERT INTO procedure_answers SET " . "procedure_order_id = ?, " . "procedure_order_seq = ?, " . "question_code = ?, " . "answer_seq = ?, " . "answer = ?", array($formid, $poseq, $qcode, $answer_seq['increment'], strip_escape_custom($datum))); sqlCommitTrans(); } } } $alertmsg = ''; if ($_POST['bn_xmit']) { $hl7 = ''; $alertmsg = gen_hl7_order($formid, $hl7); if (empty($alertmsg)) { $alertmsg = send_hl7_order($ppid, $hl7); } if (empty($alertmsg)) { sqlStatement("UPDATE procedure_order SET date_transmitted = NOW() WHERE " . "procedure_order_id = ?", array($formid));
/** * Parse and save. * * @param string &$hl7 The input HL7 text * @param string &$matchreq Array of shared patient matching requests * @param int $lab_id Lab ID * @param char $direction B=Bidirectional, R=Results-only * @param bool $dryrun True = do not update anything, just report errors * @param array $matchresp Array of responses to match requests; key is relative segment number, * value is an existing pid or 0 to specify creating a patient * @return array Array of errors and match requests, if any */ function receive_hl7_results(&$hl7, &$matchreq, $lab_id = 0, $direction = 'B', $dryrun = false, $matchresp = NULL) { global $rhl7_return; // This will hold returned error messages and related variables. $rhl7_return = array(); $rhl7_return['mssgs'] = array(); $rhl7_return['needmatch'] = false; // indicates if this file is pending a match request $rhl7_segnum = 0; if (substr($hl7, 0, 3) != 'MSH') { return rhl7LogMsg(xl('Input does not begin with a MSH segment'), true); } // This array holds everything to be written to the database. // We save and postpone these writes in case of errors while processing the message, // so we can look up data from parent results when child results are encountered, // and for other logic simplification. // Each element of this array is another array containing the following possible keys: // 'rep' - row of data to write to procedure_report // 'res' - array of rows to write to procedure_result for this procedure_report // 'fid' - unique lab-provided identifier for this report // $amain = array(); // End-of-line delimiter for text in procedure_result.comments and other multi-line notes. $commentdelim = "\n"; // Ensoftek: Different labs seem to send different EOLs. Edit HL7 input to a character we know. $hl7 = (string) str_replace(array("\r\n", "\r", "\n"), "\r", $hl7); $today = time(); $in_message_id = ''; $in_ssn = ''; $in_dob = ''; $in_lname = ''; $in_fname = ''; $in_orderid = 0; $in_procedure_code = ''; $in_report_status = ''; $in_encounter = 0; $patient_id = 0; // for results-only patient matching logic $porow = false; $pcrow = false; $oprow = false; $code_seq_array = array(); // tracks sequence numbers of order codes $results_category_id = 0; // document category ID for lab results // This is so we know where we are if a segment like NTE that can appear in // different places is encountered. $context = ''; // This will be "ORU" or "MDM". $msgtype = ''; // Stuff collected for MDM documents. $mdm_datetime = ''; $mdm_docname = ''; $mdm_text = ''; // Delimiters $d0 = "\r"; $d1 = substr($hl7, 3, 1); // typically | $d2 = substr($hl7, 4, 1); // typically ^ $d3 = substr($hl7, 5, 1); // typically ~ $d4 = substr($hl7, 6, 1); // typically \ $d5 = substr($hl7, 7, 1); // typically & // We'll need the document category IDs for any embedded documents. $catrow = sqlQuery("SELECT id FROM categories WHERE name = ?", array($GLOBALS['lab_results_category_name'])); if (empty($catrow['id'])) { return rhl7LogMsg(xl('Document category for lab results does not exist') . ': ' . $GLOBALS['lab_results_category_name'], true); } else { $results_category_id = $catrow['id']; $mdm_category_id = $results_category_id; $catrow = sqlQuery("SELECT id FROM categories WHERE name = ?", array($GLOBALS['gbl_mdm_category_name'])); if (!empty($catrow['id'])) { $mdm_category_id = $catrow['id']; } } $segs = explode($d0, $hl7); foreach ($segs as $seg) { if (empty($seg)) { continue; } // echo "<!-- $dryrun $seg -->\n"; // debugging ++$rhl7_segnum; $a = explode($d1, $seg); if ($a[0] == 'MSH') { if (!$dryrun) { rhl7FlushMain($amain, $commentdelim); } $amain = array(); if ('MDM' == $msgtype && !$dryrun) { $rc = rhl7FlushMDM($patient_id, $mdm_docname, $mdm_datetime, $mdm_text, $mdm_category_id, $oprow ? $oprow['username'] : 0); if ($rc) { return rhl7LogMsg($rc); } $patient_id = 0; } $context = $a[0]; // Ensoftek: Could come is as 'ORU^R01^ORU_R01'. Handle all cases when 'ORU^R01' is seen. if (strstr($a[8], "ORU^R01")) { $msgtype = 'ORU'; } else { if ($a[8] == 'MDM^T02' || $a[8] == 'MDM^T04' || $a[8] == 'MDM^T08') { $msgtype = 'MDM'; $mdm_datetime = ''; $mdm_docname = ''; $mdm_text = ''; } else { return rhl7LogMsg(xl('MSH.8 message type is not supported') . ": '" . $a[8] . "'", true); } } $in_message_id = $a[9]; } else { if ($a[0] == 'PID') { $context = $a[0]; if ('MDM' == $msgtype && !$dryrun) { $rc = rhl7FlushMDM($patient_id, $mdm_docname, $mdm_datetime, $mdm_text, $mdm_category_id, $oprow ? $oprow['username'] : 0); if ($rc) { return rhl7LogMsg($rc); } } $porow = false; $pcrow = false; $oprow = false; $in_orderid = 0; $in_ssn = preg_replace('/[^0-9]/', '', $a[4]); $in_dob = rhl7Date($a[7]); $tmp = explode($d2, $a[5]); $in_lname = rhl7Text($tmp[0]); $in_fname = rhl7Text($tmp[1]); $in_mname = rhl7Text($tmp[2]); $patient_id = 0; // Patient matching is needed for a results-only interface or MDM message type. if ('R' == $direction || 'MDM' == $msgtype) { $ptarr = array('ss' => strtoupper($in_ss), 'fname' => strtoupper($in_fname), 'lname' => strtoupper($in_lname), 'mname' => strtoupper($in_mname), 'DOB' => strtoupper($in_dob)); $patient_id = match_patient($ptarr); if ($patient_id == -1) { // Result is indeterminate. // Make a stringified form of $ptarr to use as a key. $ptstring = serialize($ptarr); // Check if the user has specified the patient. if (isset($matchresp[$ptstring])) { // This will be an existing pid, or 0 to specify creating a patient. $patient_id = intval($matchresp[$ptstring]); } else { if ($dryrun) { // Nope, ask the user to match. $matchreq[$ptstring] = true; $rhl7_return['needmatch'] = true; } else { // Should not happen, but it would be bad to abort now. Create the patient. $patient_id = 0; rhl7LogMsg(xl('Unexpected non-match, creating new patient for segment') . ' ' . $rhl7_segnum, false); } } } if ($patient_id == 0 && !$dryrun) { // We must create the patient. $patient_id = create_skeleton_patient($ptarr); } if ($patient_id == -1) { $patient_id = 0; } } // end results-only/MDM logic } else { if ('PD1' == $a[0]) { // TBD: Save primary care provider name ($a[4]) somewhere? } else { if ('PV1' == $a[0]) { if ('ORU' == $msgtype) { // Save placer encounter number if present. if ($direction != 'R' && !empty($a[19])) { $tmp = explode($d2, $a[19]); $in_encounter = intval($tmp[0]); } } else { if ('MDM' == $msgtype) { // For documents we want the ordering provider. // Try Referring Provider first. $oprow = match_provider(explode($d2, $a[8])); // If no match, try Other Provider. if (empty($oprow)) { $oprow = match_provider(explode($d2, $a[52])); } } } } else { if ('ORC' == $a[0] && 'ORU' == $msgtype) { $context = $a[0]; $arep = array(); $porow = false; $pcrow = false; if ($direction != 'R' && $a[2]) { $in_orderid = intval($a[2]); } } else { if ('TXA' == $a[0] && 'MDM' == $msgtype) { $context = $a[0]; $mdm_datetime = rhl7DateTime($a[4]); $mdm_docname = rhl7Text($a[12]); } else { if ($a[0] == 'NTE' && ($context == 'ORC' || $context == 'TXA')) { // Is this ever used? } else { if ('OBR' == $a[0] && 'ORU' == $msgtype) { $context = $a[0]; $arep = array(); if ($direction != 'R' && $a[2]) { $in_orderid = intval($a[2]); $porow = false; $pcrow = false; } $tmp = explode($d2, $a[4]); $in_procedure_code = $tmp[0]; $in_procedure_name = $tmp[1]; $in_report_status = rhl7ReportStatus($a[25]); // Filler identifier is supposed to be unique for each incoming report. $in_filler_id = $a[3]; // Child results will have these pointers to their parent. $in_parent_obrkey = ''; $in_parent_obxkey = ''; $parent_arep = false; // parent report, if any $parent_ares = false; // parent result, if any if (!empty($a[29])) { // This is a child so there should be a parent. $tmp = explode($d2, $a[29]); $in_parent_obrkey = str_replace($d5, $d2, $tmp[1]); $tmp = explode($d2, $a[26]); $in_parent_obxkey = str_replace($d5, $d2, $tmp[0]) . $d1 . $tmp[1]; // Look for the parent report. foreach ($amain as $arr) { if (isset($arr['fid']) && $arr['fid'] == $in_parent_obrkey) { $parent_arep = $arr['rep']; // Now look for the parent result within that report. foreach ($arr['res'] as $tmpres) { if (isset($tmpres['obxkey']) && $tmpres['obxkey'] == $in_parent_obxkey) { $parent_ares = $tmpres; break; } } break; } } } if ($parent_arep) { $in_orderid = $parent_arep['procedure_order_id']; } if ($direction == 'R') { // Save their order ID to procedure_order.control_id. // Look for an existing order using that plus lab_id. // Ordering provider is OBR.16 (NPI^Last^First). // Might not need to create a dummy encounter. // Need also provider_id (probably), patient_id, date_ordered, lab_id. // We have observation date/time in OBR.7. // We have report date/time in OBR.22. // We do not have an order date. $external_order_id = empty($a[2]) ? $a[3] : $a[2]; $porow = false; if (!$in_orderid && $external_order_id) { $porow = sqlQuery("SELECT * FROM procedure_order " . "WHERE lab_id = ? AND control_id = ? " . "ORDER BY procedure_order_id DESC LIMIT 1", array($lab_id, $external_order_id)); } if (!empty($porow)) { $in_orderid = intval($porow['procedure_order_id']); } if (!$in_orderid) { // Create order. // Need to identify the ordering provider and, if possible, a recent encounter. $datetime_report = rhl7DateTime($a[22]); $date_report = substr($datetime_report, 0, 10) . ' 00:00:00'; $encounter_id = 0; $provider_id = 0; // Look for the most recent encounter within 30 days of the report date. $encrow = sqlQuery("SELECT encounter FROM form_encounter WHERE " . "pid = ? AND date <= ? AND DATE_ADD(date, INTERVAL 30 DAY) > ? " . "ORDER BY date DESC, encounter DESC LIMIT 1", array($patient_id, $date_report, $date_report)); if (!empty($encrow)) { $encounter_id = intval($encrow['encounter']); $provider_id = intval($encrow['provider_id']); } if (!$provider_id) { // Attempt ordering provider matching by name or NPI. $oprow = match_provider(explode($d2, $a[16])); if (!empty($oprow)) { $provider_id = intval($oprow['id']); } } if (!$dryrun) { // Now create the procedure order. $in_orderid = sqlInsert("INSERT INTO procedure_order SET " . "date_ordered = ?, " . "provider_id = ?, " . "lab_id = ?, " . "date_collected = ?, " . "date_transmitted = ?, " . "patient_id = ?, " . "encounter_id = ?, " . "control_id = ?", array($datetime_report, $provider_id, $lab_id, rhl7DateTime($a[22]), rhl7DateTime($a[7]), $patient_id, $encounter_id, $external_order_id)); // If an encounter was identified then link the order to it. if ($encounter_id && $in_orderid) { addForm($encounter_id, "Procedure Order", $in_orderid, "procedure_order", $patient_id); } } } // end no $porow } // end results-only if (empty($porow)) { $porow = sqlQuery("SELECT * FROM procedure_order WHERE " . "procedure_order_id = ?", array($in_orderid)); // The order must already exist. Currently we do not handle electronic // results returned for manual orders. if (empty($porow) && !($dryrun && $direction == 'R')) { return rhl7LogMsg(xl('Procedure order not found') . ": {$in_orderid}", true); } if ($in_encounter) { if ($direction != 'R' && $porow['encounter_id'] != $in_encounter) { return rhl7LogMsg(xl('Encounter ID') . " '" . $porow['encounter_id'] . "' " . xl('for OBR placer order number') . " '{$in_orderid}' " . xl('does not match the PV1 encounter number') . " '{$in_encounter}'"); } } else { // They did not return an encounter number to verify, so more checking // might be done here to make sure the patient seems to match. } // Save the lab's control ID if there is one. $tmp = explode($d2, $a[3]); $control_id = $tmp[0]; if ($control_id && empty($porow['control_id'])) { sqlStatement("UPDATE procedure_order SET control_id = ? WHERE " . "procedure_order_id = ?", array($control_id, $in_orderid)); } $code_seq_array = array(); } // Find the order line item (procedure code) that matches this result. // If there is more than one, then we select the one whose sequence number // is next after the last sequence number encountered for this procedure // code; this assumes that result OBRs are returned in the same sequence // as the corresponding OBRs in the order. if (!isset($code_seq_array[$in_procedure_code])) { $code_seq_array[$in_procedure_code] = 0; } $pcquery = "SELECT pc.* FROM procedure_order_code AS pc " . "WHERE pc.procedure_order_id = ? AND pc.procedure_code = ? " . "ORDER BY (procedure_order_seq <= ?), procedure_order_seq LIMIT 1"; $pcqueryargs = array($in_orderid, $in_procedure_code, $code_seq_array[$in_procedure_code]); $pcrow = sqlQuery($pcquery, $pcqueryargs); if (empty($pcrow)) { // There is no matching procedure in the order, so it must have been // added after the original order was sent, either as a manual request // from the physician or as a "reflex" from the lab. // procedure_source = '2' indicates this. if (!$dryrun) { sqlBeginTrans(); $procedure_order_seq = sqlQuery("SELECT IFNULL(MAX(procedure_order_seq),0) + 1 AS increment FROM procedure_order_code WHERE procedure_order_id = ? ", array($in_orderid)); sqlInsert("INSERT INTO procedure_order_code SET " . "procedure_order_id = ?, " . "procedure_order_seq = ?, " . "procedure_code = ?, " . "procedure_name = ?, " . "procedure_source = '2'", array($in_orderid, $procedure_order_seq['increment'], $in_procedure_code, $in_procedure_name)); $pcrow = sqlQuery($pcquery, $pcqueryargs); sqlCommitTrans(); } else { // Dry run, make a dummy procedure_order_code row. $pcrow = array('procedure_order_id' => $in_orderid, 'procedure_order_seq' => 0); } } $code_seq_array[$in_procedure_code] = 0 + $pcrow['procedure_order_seq']; $arep = array(); $arep['procedure_order_id'] = $in_orderid; $arep['procedure_order_seq'] = $pcrow['procedure_order_seq']; $arep['date_collected'] = rhl7DateTime($a[7]); $arep['date_collected_tz'] = rhl7DateTimeZone($a[7]); $arep['date_report'] = rhl7DateTime($a[22]); $arep['date_report_tz'] = rhl7DateTimeZone($a[22]); $arep['report_status'] = $in_report_status; $arep['report_notes'] = ''; $arep['specimen_num'] = ''; // If this is a child report, add some info from the parent. if (!empty($parent_ares)) { $arep['report_notes'] .= xl('This is a child of result') . ' ' . $parent_ares['result_code'] . ' ' . xl('with value') . ' "' . $parent_ares['result'] . '".' . "\n"; } if (!empty($parent_arep)) { $arep['report_notes'] .= $parent_arep['report_notes']; $arep['specimen_num'] = $parent_arep['specimen_num']; } // Create the main array entry for this report and its results. $i = count($amain); $amain[$i] = array(); $amain[$i]['rep'] = $arep; $amain[$i]['fid'] = $in_filler_id; $amain[$i]['res'] = array(); } else { if ($a[0] == 'NTE' && $context == 'OBR') { // Append this note to those for the most recent report. $amain[count($amain) - 1]['rep']['report_notes'] .= rhl7Text($a[3], true) . "\n"; } else { if ('OBX' == $a[0] && 'ORU' == $msgtype) { $tmp = explode($d2, $a[3]); $result_code = rhl7Text($tmp[0]); $result_text = rhl7Text($tmp[1]); // If this is a text result that duplicates the previous result except // for its value, then treat it as an extension of that result's value. $i = count($amain) - 1; $j = count($amain[$i]['res']) - 1; if ($j >= 0 && $context == 'OBX' && $a[2] == 'TX' && $amain[$i]['res'][$j]['result_data_type'] == 'L' && $amain[$i]['res'][$j]['result_code'] == $result_code && $amain[$i]['res'][$j]['date'] == rhl7DateTime($a[14]) && $amain[$i]['res'][$j]['facility'] == rhl7Text($a[15]) && $amain[$i]['res'][$j]['abnormal'] == rhl7Abnormal($a[8]) && $amain[$i]['res'][$j]['result_status'] == rhl7ReportStatus($a[11])) { $amain[$i]['res'][$j]['comments'] = substr($amain[$i]['res'][$j]['comments'], 0, strlen($amain[$i]['res'][$j]['comments']) - 1) . '~' . rhl7Text($a[5]) . $commentdelim; continue; } $context = $a[0]; $ares = array(); $ares['result_data_type'] = substr($a[2], 0, 1); // N, S, F or E $ares['comments'] = $commentdelim; if ($a[2] == 'ED') { // This is the case of results as an embedded document. We will create // a normal patient document in the assigned category for lab results. $tmp = explode($d2, $a[5]); $fileext = strtolower($tmp[0]); $filename = date("Ymd_His") . '.' . $fileext; $data = rhl7DecodeData($tmp[3], $tmp[4]); if ($data === FALSE) { return rhl7LogMsg(xl('Invalid encapsulated data encoding type') . ': ' . $tmp[3]); } if (!$dryrun) { $d = new Document(); $rc = $d->createDocument($porow['patient_id'], $results_category_id, $filename, rhl7MimeType($fileext), $data); if ($rc) { return rhl7LogMsg($rc); } $ares['document_id'] = $d->get_id(); } } else { if ($a[2] == 'CWE') { $ares['result'] = rhl7CWE($a[5], $d2); } else { if ($a[2] == 'SN') { $ares['result'] = trim(str_replace($d2, ' ', $a[5])); } else { if ($a[2] == 'TX' || strlen($a[5]) > 200) { // OBX-5 can be a very long string of text with "~" as line separators. // The first line of comments is reserved for such things. $ares['result_data_type'] = 'L'; $ares['result'] = ''; $ares['comments'] = rhl7Text($a[5]) . $commentdelim; } else { $ares['result'] = rhl7Text($a[5]); } } } } $ares['result_code'] = $result_code; $ares['result_text'] = $result_text; $ares['date'] = rhl7DateTime($a[14]); $ares['facility'] = rhl7Text($a[15]); // Ensoftek: Units may have mutiple segments(as seen in MU2 samples), parse and take just first segment. $tmp = explode($d2, $a[6]); $ares['units'] = rhl7Text($tmp[0]); $ares['range'] = rhl7Text($a[7]); $ares['abnormal'] = rhl7Abnormal($a[8]); // values are lab dependent $ares['result_status'] = rhl7ReportStatus($a[11]); // Ensoftek: Performing Organization Details. Goes into "Pending Review/Patient Results--->Notes--->Facility" section. $performingOrganization = getPerformingOrganizationDetails($a[23], $a[24], $a[25], $d2, $commentdelim); if (!empty($performingOrganization)) { $ares['facility'] .= $performingOrganization . $commentdelim; } /**** // Probably need a better way to report this, if it matters. if (!empty($a[19])) { $ares['comments'] .= xl('Analyzed') . ' ' . rhl7DateTime($a[19]) . '.' . $commentdelim; } ****/ // obxkey is to allow matching this as a parent result. $ares['obxkey'] = $a[3] . $d1 . $a[4]; // Append this result to those for the most recent report. // Note the 'procedure_report_id' item is not yet present. $amain[count($amain) - 1]['res'][] = $ares; } else { if ('OBX' == $a[0] && 'MDM' == $msgtype) { $context = $a[0]; if ($a[2] == 'TX') { if ($mdm_text !== '') { $mdm_text .= "\r\n"; } $mdm_text .= rhl7Text($a[5]); } else { return rhl7LogMsg(xl('Unsupported MDM OBX result type') . ': ' . $a[2]); } } else { if ('ZEF' == $a[0] && 'ORU' == $msgtype) { // ZEF segment is treated like an OBX with an embedded Base64-encoded PDF. $context = 'OBX'; $ares = array(); $ares['result_data_type'] = 'E'; $ares['comments'] = $commentdelim; // $fileext = 'pdf'; $filename = date("Ymd_His") . '.' . $fileext; $data = rhl7DecodeData('Base64', $a[2]); if ($data === FALSE) { return rhl7LogMsg(xl('ZEF segment internal error')); } if (!$dryrun) { $d = new Document(); $rc = $d->createDocument($porow['patient_id'], $results_category_id, $filename, rhl7MimeType($fileext), $data); if ($rc) { return rhl7LogMsg($rc); } $ares['document_id'] = $d->get_id(); } $ares['date'] = $arep['date_report']; // $arep is left over from the OBR logic. // Append this result to those for the most recent report. // Note the 'procedure_report_id' item is not yet present. $amain[count($amain) - 1]['res'][] = $ares; } else { if ('NTE' == $a[0] && 'OBX' == $context && 'ORU' == $msgtype) { // Append this note to the most recent result item's comments. $alast = count($amain) - 1; $rlast = count($amain[$alast]['res']) - 1; $amain[$alast]['res'][$rlast]['comments'] .= rhl7Text($a[3], true) . $commentdelim; } else { if ('SPM' == $a[0] && 'ORU' == $msgtype) { rhl7UpdateReportWithSpecimen($amain, $a, $d2); } else { if ('TQ1' == $a[0] && 'ORU' == $msgtype) { // Ignore and do nothing. } else { return rhl7LogMsg(xl('Segment name') . " '{$a[0]}' " . xl('is misplaced or unknown')); } } } } } } } } } } } } } } } } // Write all reports and their results to the database. // This will do nothing if a dry run or MDM message type. if ('ORU' == $msgtype && !$dryrun) { rhl7FlushMain($amain, $commentdelim); } if ('MDM' == $msgtype && !$dryrun) { // Write documents. $rc = rhl7FlushMDM($patient_id, $mdm_docname, $mdm_datetime, $mdm_text, $mdm_category_id, $oprow ? $oprow['username'] : 0); if ($rc) { return rhl7LogMsg($rc); } } return $rhl7_return; }
public function save(&$bill, &$prod, $main_provid = NULL, $main_supid = NULL, $default_warehouse = NULL, $mark_as_closed = false) { global $code_types; if (isset($main_provid) && $main_supid == $main_provid) { $main_supid = 0; } $copay_update = FALSE; $update_session_id = ''; $ct0 = ''; // takes the code type of the first fee type code type entry from the fee sheet, against which the copay is posted $cod0 = ''; // takes the code of the first fee type code type entry from the fee sheet, against which the copay is posted $mod0 = ''; // takes the modifier of the first fee type code type entry from the fee sheet, against which the copay is posted if (is_array($bill)) { foreach ($bill as $iter) { // Skip disabled (billed) line items. if (!empty($iter['billed'])) { continue; } $id = $iter['id']; $code_type = $iter['code_type']; $code = $iter['code']; $del = !empty($iter['del']); $units = empty($iter['units']) ? 1 : intval($iter['units']); $price = empty($iter['price']) ? 0 : 0 + trim($iter['price']); $pricelevel = empty($iter['pricelevel']) ? '' : $iter['pricelevel']; $modifier = empty($iter['mod']) ? '' : trim($iter['mod']); $justify = empty($iter['justify']) ? '' : trim($iter['justify']); $notecodes = empty($iter['notecodes']) ? '' : trim($iter['notecodes']); $provid = empty($iter['provid']) ? 0 : intval($iter['provid']); $fee = sprintf('%01.2f', $price * $units); if (!$cod0 && $code_types[$code_type]['fee'] == 1) { $mod0 = $modifier; $cod0 = $code; $ct0 = $code_type; } if ($code_type == 'COPAY') { if ($fee < 0) { $fee = $fee * -1; } if (!$id) { // adding new copay from fee sheet into ar_session and ar_activity tables $session_id = idSqlStatement("INSERT INTO ar_session " . "(payer_id, user_id, pay_total, payment_type, description, patient_id, payment_method, " . "adjustment_code, post_to_date) " . "VALUES ('0',?,?,'patient','COPAY',?,'','patient_payment',now())", array($_SESSION['authId'], $fee, $this->pid)); sqlBeginTrans(); $sequence_no = sqlQuery("SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE " . "pid = ? AND encounter = ?", array($this->pid, $this->encounter)); SqlStatement("INSERT INTO ar_activity (pid, encounter, sequence_no, code_type, code, modifier, " . "payer_type, post_time, post_user, session_id, " . "pay_amount, account_code) VALUES (?,?,?,?,?,?,0,now(),?,?,?,'PCP')", array($this->pid, $this->encounter, $sequence_no['increment'], $ct0, $cod0, $mod0, $_SESSION['authId'], $session_id, $fee)); sqlCommitTrans(); } else { // editing copay saved to ar_session and ar_activity $session_id = $id; $res_amount = sqlQuery("SELECT pay_amount FROM ar_activity WHERE pid=? AND encounter=? AND session_id=?", array($this->pid, $this->encounter, $session_id)); if ($fee != $res_amount['pay_amount']) { sqlStatement("UPDATE ar_session SET user_id=?,pay_total=?,modified_time=now(),post_to_date=now() WHERE session_id=?", array($_SESSION['authId'], $fee, $session_id)); sqlStatement("UPDATE ar_activity SET code_type=?, code=?, modifier=?, post_user=?, post_time=now()," . "pay_amount=?, modified_time=now() WHERE pid=? AND encounter=? AND account_code='PCP' AND session_id=?", array($ct0, $cod0, $mod0, $_SESSION['authId'], $fee, $this->pid, $this->encounter, $session_id)); } } if (!$cod0) { $copay_update = TRUE; $update_session_id = $session_id; } continue; } # Code to create justification for all codes based on first justification if ($GLOBALS['replicate_justification'] == '1') { if ($justify != '') { $autojustify = $justify; } } if ($GLOBALS['replicate_justification'] == '1' && $justify == '' && check_is_code_type_justify($code_type)) { $justify = $autojustify; } if ($justify) { $justify = str_replace(',', ':', $justify) . ':'; } $auth = "1"; $ndc_info = ''; if (!empty($iter['ndcnum'])) { $ndc_info = 'N4' . trim($iter['ndcnum']) . ' ' . $iter['ndcuom'] . trim($iter['ndcqty']); } // If the item is already in the database... if ($id) { if ($del) { $this->logFSMessage(xl('Service deleted')); deleteBilling($id); } else { $tmp = sqlQuery("SELECT * FROM billing WHERE id = ? AND (billed = 0 or billed is NULL) AND activity = 1", array($id)); if (!empty($tmp)) { $tmparr = array('code' => $code, 'authorized' => $auth); if (isset($iter['units'])) { $tmparr['units'] = $units; } if (isset($iter['price'])) { $tmparr['fee'] = $fee; } if (isset($iter['pricelevel'])) { $tmparr['pricelevel'] = $pricelevel; } if (isset($iter['mod'])) { $tmparr['modifier'] = $modifier; } if (isset($iter['provid'])) { $tmparr['provider_id'] = $provid; } if (isset($iter['ndcnum'])) { $tmparr['ndc_info'] = $ndc_info; } if (isset($iter['justify'])) { $tmparr['justify'] = $justify; } if (isset($iter['notecodes'])) { $tmparr['notecodes'] = $notecodes; } foreach ($tmparr as $key => $value) { if ($tmp[$key] != $value) { if ('fee' == $key) { $this->logFSMessage(xl('Price changed')); } if ('units' == $key) { $this->logFSMessage(xl('Quantity changed')); } if ('provider_id' == $key) { $this->logFSMessage(xl('Service provider changed')); } sqlStatement("UPDATE billing SET `{$key}` = ? WHERE id = ?", array($value, $id)); } } } } } else { if (!$del) { $this->logFSMessage(xl('Service added')); $code_text = lookup_code_descriptions($code_type . ":" . $code); addBilling($this->encounter, $code_type, $code, $code_text, $this->pid, $auth, $provid, $modifier, $units, $fee, $ndc_info, $justify, 0, $notecodes, $pricelevel); } } } } // end for // if modifier is not inserted during loop update the record using the first // non-empty modifier and code if ($copay_update == TRUE && $update_session_id != '' && $mod0 != '') { sqlStatement("UPDATE ar_activity SET code_type = ?, code = ?, modifier = ?" . " WHERE pid = ? AND encounter = ? AND account_code = 'PCP' AND session_id = ?", array($ct0, $cod0, $mod0, $this->pid, $this->encounter, $update_session_id)); } // Doing similarly to the above but for products. if (is_array($prod)) { foreach ($prod as $iter) { // Skip disabled (billed) line items. if (!empty($iter['billed'])) { continue; } $drug_id = $iter['drug_id']; $selector = empty($iter['selector']) ? '' : $iter['selector']; $sale_id = $iter['sale_id']; // present only if already saved $units = max(1, intval(trim($iter['units']))); $price = empty($iter['price']) ? 0 : 0 + trim($iter['price']); $pricelevel = empty($iter['pricelevel']) ? '' : $iter['pricelevel']; $fee = sprintf('%01.2f', $price * $units); $del = !empty($iter['del']); $rxid = 0; $warehouse_id = empty($iter['warehouse']) ? '' : $iter['warehouse']; $somechange = false; // If the item is already in the database... if ($sale_id) { $tmprow = sqlQuery("SELECT ds.prescription_id, ds.quantity, ds.inventory_id, ds.fee, " . "ds.sale_date, di.warehouse_id " . "FROM drug_sales AS ds " . "LEFT JOIN drug_inventory AS di ON di.inventory_id = ds.inventory_id " . "WHERE ds.sale_id = ?", array($sale_id)); $rxid = 0 + $tmprow['prescription_id']; if ($del) { if (!empty($tmprow)) { // Delete this sale and reverse its inventory update. $this->logFSMessage(xl('Product deleted')); sqlStatement("DELETE FROM drug_sales WHERE sale_id = ?", array($sale_id)); if (!empty($tmprow['inventory_id'])) { sqlStatement("UPDATE drug_inventory SET on_hand = on_hand + ? WHERE inventory_id = ?", array($tmprow['quantity'], $tmprow['inventory_id'])); } } if ($rxid) { sqlStatement("DELETE FROM prescriptions WHERE id = ?", array($rxid)); } } else { // Modify the sale and adjust inventory accordingly. if (!empty($tmprow)) { foreach (array('quantity' => $units, 'fee' => $fee, 'pricelevel' => $pricelevel, 'selector' => $selector, 'sale_date' => $this->visit_date) as $key => $value) { if ($tmprow[$key] != $value) { $somechange = true; if ('fee' == $key) { $this->logFSMessage(xl('Price changed')); } if ('pricelevel' == $key) { $this->logFSMessage(xl('Price level changed')); } if ('selector' == $key) { $this->logFSMessage(xl('Template selector changed')); } if ('quantity' == $key) { $this->logFSMessage(xl('Quantity changed')); } sqlStatement("UPDATE drug_sales SET `{$key}` = ? WHERE sale_id = ?", array($value, $sale_id)); if ($key == 'quantity' && $tmprow['inventory_id']) { sqlStatement("UPDATE drug_inventory SET on_hand = on_hand - ? WHERE inventory_id = ?", array($units - $tmprow['quantity'], $tmprow['inventory_id'])); } } } if ($tmprow['inventory_id'] && $warehouse_id && $warehouse_id != $tmprow['warehouse_id']) { // Changing warehouse. Requires deleting and re-adding the sale. // Not setting $somechange because this alone does not affect a prescription. $this->logFSMessage(xl('Warehouse changed')); sqlStatement("DELETE FROM drug_sales WHERE sale_id = ?", array($sale_id)); sqlStatement("UPDATE drug_inventory SET on_hand = on_hand + ? WHERE inventory_id = ?", array($units, $tmprow['inventory_id'])); $tmpnull = null; $sale_id = sellDrug($drug_id, $units, $fee, $this->pid, $this->encounter, empty($iter['rx']) ? 0 : $rxid, $this->visit_date, '', $warehouse_id, false, $tmpnull, $pricelevel, $selector); } } // Delete Rx if $rxid and flag not set. if ($GLOBALS['gbl_auto_create_rx'] && $rxid && empty($iter['rx'])) { sqlStatement("UPDATE drug_sales SET prescription_id = 0 WHERE sale_id = ?", array($sale_id)); sqlStatement("DELETE FROM prescriptions WHERE id = ?", array($rxid)); } } } else { if (!$del) { $somechange = true; $this->logFSMessage(xl('Product added')); $tmpnull = null; $sale_id = sellDrug($drug_id, $units, $fee, $this->pid, $this->encounter, 0, $this->visit_date, '', $warehouse_id, false, $tmpnull, $pricelevel, $selector); if (!$sale_id) { die(xlt("Insufficient inventory for product ID") . " \"" . text($drug_id) . "\"."); } } } // If a prescription applies, create or update it. if (!empty($iter['rx']) && !$del && ($somechange || empty($rxid))) { // If an active rx already exists for this drug and date we will // replace it, otherwise we'll make a new one. if (empty($rxid)) { $rxid = ''; } // Get default drug attributes; prefer the template with the matching selector. $drow = sqlQuery("SELECT dt.*, " . "d.name, d.form, d.size, d.unit, d.route, d.substitute " . "FROM drugs AS d, drug_templates AS dt WHERE " . "d.drug_id = ? AND dt.drug_id = d.drug_id " . "ORDER BY (dt.selector = ?) DESC, dt.quantity, dt.dosage, dt.selector LIMIT 1", array($drug_id, $selector)); if (!empty($drow)) { $rxobj = new Prescription($rxid); $rxobj->set_patient_id($this->pid); $rxobj->set_provider_id(isset($main_provid) ? $main_provid : $this->provider_id); $rxobj->set_drug_id($drug_id); $rxobj->set_quantity($units); $rxobj->set_per_refill($units); $rxobj->set_start_date_y(substr($this->visit_date, 0, 4)); $rxobj->set_start_date_m(substr($this->visit_date, 5, 2)); $rxobj->set_start_date_d(substr($this->visit_date, 8, 2)); $rxobj->set_date_added($this->visit_date); // Remaining attributes are the drug and template defaults. $rxobj->set_drug($drow['name']); $rxobj->set_unit($drow['unit']); $rxobj->set_dosage($drow['dosage']); $rxobj->set_form($drow['form']); $rxobj->set_refills($drow['refills']); $rxobj->set_size($drow['size']); $rxobj->set_route($drow['route']); $rxobj->set_interval($drow['period']); $rxobj->set_substitute($drow['substitute']); // $rxobj->persist(); // Set drug_sales.prescription_id to $rxobj->get_id(). $oldrxid = $rxid; $rxid = 0 + $rxobj->get_id(); if ($rxid != $oldrxid) { sqlStatement("UPDATE drug_sales SET prescription_id = ? WHERE sale_id = ?", array($rxid, $sale_id)); } } } } } // end for // Set default and/or supervising provider for the encounter. if (isset($main_provid) && $main_provid != $this->provider_id) { $this->logFSMessage(xl('Default provider changed')); sqlStatement("UPDATE form_encounter SET provider_id = ? WHERE pid = ? AND encounter = ?", array($main_provid, $this->pid, $this->encounter)); $this->provider_id = $main_provid; } if (isset($main_supid) && $main_supid != $this->supervisor_id) { sqlStatement("UPDATE form_encounter SET supervisor_id = ? WHERE pid = ? AND encounter = ?", array($main_supid, $this->pid, $this->encounter)); $this->supervisor_id = $main_supid; } // Save-and-Close is currently specific to Family Planning but might be more // generally useful. It provides the ability to mark an encounter as billed // directly from the Fee Sheet, if there are no charges. if ($mark_as_closed) { $tmp1 = sqlQuery("SELECT SUM(ABS(fee)) AS sum FROM drug_sales WHERE " . "pid = ? AND encounter = ? AND billed = 0", array($this->pid, $this->encounter)); $tmp2 = sqlQuery("SELECT SUM(ABS(fee)) AS sum FROM billing WHERE " . "pid = ? AND encounter = ? AND billed = 0 AND activity = 1", array($this->pid, $this->encounter)); if ($tmp1['sum'] + $tmp2['sum'] == 0) { sqlStatement("update drug_sales SET billed = 1 WHERE " . "pid = ? AND encounter = ? AND billed = 0", array($this->pid, $this->encounter)); sqlStatement("UPDATE billing SET billed = 1, bill_date = NOW() WHERE " . "pid = ? AND encounter = ? AND billed = 0 AND activity = 1", array($this->pid, $this->encounter)); } else { // Would be good to display an error message here... they clicked // Save and Close but the close could not be done. However the // framework does not provide an easy way to do that. } } }
function arPostAdjustment($patient_id, $encounter_id, $session_id, $amount, $code, $payer_type, $reason, $debug, $time = '', $codetype = '') { $codeonly = $code; $modifier = ''; $tmp = strpos($code, ':'); if ($tmp) { $codeonly = substr($code, 0, $tmp); $modifier = substr($code, $tmp + 1); } if (empty($time)) { $time = date('Y-m-d H:i:s'); } sqlBeginTrans(); $sequence_no = sqlQuery("SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array($patient_id, $encounter_id)); $query = "INSERT INTO ar_activity ( " . "pid, encounter, sequence_no, code_type, code, modifier, payer_type, post_user, post_time, " . "session_id, memo, adj_amount " . ") VALUES ( " . "'{$patient_id}', " . "'{$encounter_id}', " . "'{$sequence_no['increment']}', " . "'{$codetype}', " . "'{$codeonly}', " . "'{$modifier}', " . "'{$payer_type}', " . "'" . $_SESSION['authUserID'] . "', " . "'{$time}', " . "'{$session_id}', " . "'{$reason}', " . "'{$amount}' " . ")"; sqlStatement($query); sqlCommitTrans(); return; }
function DistributionInsert($CountRow,$created_time,$user_id) {//Function inserts the distribution.Payment,Adjustment,Deductible,Takeback & Follow up reasons are inserted as seperate rows. //It automatically pushes to next insurance for billing. //In the screen a drop down of Ins1,Ins2,Ins3,Pat are given.The posting can be done for any level. $Affected='no'; if (isset($_POST["Payment$CountRow"]) && $_POST["Payment$CountRow"]*1>0) { if(trim(formData('type_name' ))=='insurance') { if(trim(formData("HiddenIns$CountRow" ))==1) { $AccountCode="IPP"; } if(trim(formData("HiddenIns$CountRow" ))==2) { $AccountCode="ISP"; } if(trim(formData("HiddenIns$CountRow" ))==3) { $AccountCode="ITP"; } } elseif(trim(formData('type_name' ))=='patient') { $AccountCode="PP"; } sqlBeginTrans(); $sequence_no = sqlQuery( "SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array(trim(formData('hidden_patient_code' )), trim(formData("HiddenEncounter$CountRow" )))); sqlStatement("insert into ar_activity set " . "pid = '" . trim(formData('hidden_patient_code' )) . "', encounter = '" . trim(formData("HiddenEncounter$CountRow" )) . "', sequence_no = '" . $sequence_no['increment'] . "', code_type = '" . trim(formData("HiddenCodetype$CountRow" )) . "', code = '" . trim(formData("HiddenCode$CountRow" )) . "', modifier = '" . trim(formData("HiddenModifier$CountRow" )) . "', payer_type = '" . trim(formData("HiddenIns$CountRow" )) . "', post_time = '" . trim($created_time ) . "', post_user = '******', session_id = '" . trim(formData('payment_id')) . "', modified_time = '" . trim($created_time ) . "', pay_amount = '" . trim(formData("Payment$CountRow" )) . "', adj_amount = '" . 0 . "', account_code = '" . "$AccountCode" . "'"); sqlCommitTrans(); $Affected='yes'; } if (isset($_POST["AdjAmount$CountRow"]) && $_POST["AdjAmount$CountRow"]*1!=0) { if(trim(formData('type_name' ))=='insurance') { $AdjustString="Ins adjust Ins".trim(formData("HiddenIns$CountRow" )); $AccountCode="IA"; } elseif(trim(formData('type_name' ))=='patient') { $AdjustString="Pt adjust"; $AccountCode="PA"; } sqlBeginTrans(); $sequence_no = sqlQuery( "SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array(trim(formData('hidden_patient_code' )), trim(formData("HiddenEncounter$CountRow" )))); sqlInsert("insert into ar_activity set " . "pid = '" . trim(formData('hidden_patient_code' )) . "', encounter = '" . trim(formData("HiddenEncounter$CountRow" )) . "', sequence_no = '" . $sequence_no['increment'] . "', code_type = '" . trim(formData("HiddenCodetype$CountRow" )) . "', code = '" . trim(formData("HiddenCode$CountRow" )) . "', modifier = '" . trim(formData("HiddenModifier$CountRow" )) . "', payer_type = '" . trim(formData("HiddenIns$CountRow" )) . "', post_time = '" . trim($created_time ) . "', post_user = '******', session_id = '" . trim(formData('payment_id')) . "', modified_time = '" . trim($created_time ) . "', pay_amount = '" . 0 . "', adj_amount = '" . trim(formData("AdjAmount$CountRow" )) . "', memo = '" . "$AdjustString" . "', account_code = '" . "$AccountCode" . "'"); sqlCommitTrans(); $Affected='yes'; } if (isset($_POST["Deductible$CountRow"]) && $_POST["Deductible$CountRow"]*1>0) { sqlBeginTrans(); $sequence_no = sqlQuery( "SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array(trim(formData('hidden_patient_code' )), trim(formData("HiddenEncounter$CountRow" )))); sqlInsert("insert into ar_activity set " . "pid = '" . trim(formData('hidden_patient_code' )) . "', encounter = '" . trim(formData("HiddenEncounter$CountRow" )) . "', sequence_no = '" . $sequence_no['increment'] . "', code_type = '" . trim(formData("HiddenCodetype$CountRow" )) . "', code = '" . trim(formData("HiddenCode$CountRow" )) . "', modifier = '" . trim(formData("HiddenModifier$CountRow" )) . "', payer_type = '" . trim(formData("HiddenIns$CountRow" )) . "', post_time = '" . trim($created_time ) . "', post_user = '******', session_id = '" . trim(formData('payment_id')) . "', modified_time = '" . trim($created_time ) . "', pay_amount = '" . 0 . "', adj_amount = '" . 0 . "', memo = '" . "Deductible $".trim(formData("Deductible$CountRow" )) . "', account_code = '" . "Deduct" . "'"); sqlCommitTrans(); $Affected='yes'; } if (isset($_POST["Takeback$CountRow"]) && $_POST["Takeback$CountRow"]*1>0) { sqlBeginTrans(); $sequence_no = sqlQuery( "SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array(trim(formData('hidden_patient_code' )), trim(formData("HiddenEncounter$CountRow" )))); sqlInsert("insert into ar_activity set " . "pid = '" . trim(formData('hidden_patient_code' )) . "', encounter = '" . trim(formData("HiddenEncounter$CountRow" )) . "', sequence_no = '" . $sequence_no['increment'] . "', code_type = '" . trim(formData("HiddenCodetype$CountRow" )) . "', code = '" . trim(formData("HiddenCode$CountRow" )) . "', modifier = '" . trim(formData("HiddenModifier$CountRow" )) . "', payer_type = '" . trim(formData("HiddenIns$CountRow" )) . "', post_time = '" . trim($created_time ) . "', post_user = '******', session_id = '" . trim(formData('payment_id')) . "', modified_time = '" . trim($created_time ) . "', pay_amount = '" . trim(formData("Takeback$CountRow" ))*-1 . "', adj_amount = '" . 0 . "', account_code = '" . "Takeback" . "'"); sqlCommitTrans(); $Affected='yes'; } if (isset($_POST["FollowUp$CountRow"]) && $_POST["FollowUp$CountRow"]=='y') { sqlBeginTrans(); $sequence_no = sqlQuery( "SELECT IFNULL(MAX(sequence_no),0) + 1 AS increment FROM ar_activity WHERE pid = ? AND encounter = ?", array(trim(formData('hidden_patient_code' )), trim(formData("HiddenEncounter$CountRow" )))); sqlInsert("insert into ar_activity set " . "pid = '" . trim(formData('hidden_patient_code' )) . "', encounter = '" . trim(formData("HiddenEncounter$CountRow" )) . "', sequence_no = '" . $sequence_no['increment'] . "', code_type = '" . trim(formData("HiddenCodetype$CountRow" )) . "', code = '" . trim(formData("HiddenCode$CountRow" )) . "', modifier = '" . trim(formData("HiddenModifier$CountRow" )) . "', payer_type = '" . trim(formData("HiddenIns$CountRow" )) . "', post_time = '" . trim($created_time ) . "', post_user = '******', session_id = '" . trim(formData('payment_id')) . "', modified_time = '" . trim($created_time ) . "', pay_amount = '" . 0 . "', adj_amount = '" . 0 . "', follow_up = '" . "y" . "', follow_up_note = '" . trim(formData("FollowUpReason$CountRow" )) . "'"); sqlCommitTrans(); $Affected='yes'; } if($Affected=='yes') { if(trim(formData('type_name' ))!='patient') { $ferow = sqlQuery("select last_level_closed from form_encounter where pid ='".trim(formData('hidden_patient_code' ))."' and encounter='".trim(formData("HiddenEncounter$CountRow" ))."'"); //multiple charges can come. if($ferow['last_level_closed']<trim(formData("HiddenIns$CountRow" ))) { sqlStatement("update form_encounter set last_level_closed='".trim(formData("HiddenIns$CountRow" ))."' where pid ='".trim(formData('hidden_patient_code' ))."' and encounter='".trim(formData("HiddenEncounter$CountRow" ))."'"); //last_level_closed gets increased. //----------------------------------- // Determine the next insurance level to be billed. $ferow = sqlQuery("SELECT date, last_level_closed " . "FROM form_encounter WHERE " . "pid = '".trim(formData('hidden_patient_code' ))."' AND encounter = '".trim(formData("HiddenEncounter$CountRow" ))."'"); $date_of_service = substr($ferow['date'], 0, 10); $new_payer_type = 0 + $ferow['last_level_closed']; if ($new_payer_type <= 3 && !empty($ferow['last_level_closed']) || $new_payer_type == 0) ++$new_payer_type; $new_payer_id = arGetPayerID(trim(formData('hidden_patient_code' )), $date_of_service, $new_payer_type); if($new_payer_id>0) { arSetupSecondary(trim(formData('hidden_patient_code' )), trim(formData("HiddenEncounter$CountRow" )),0); } //----------------------------------- } } } }