Skip to content

Commit

Permalink
Eligibility Fixes (openemr#7774)
Browse files Browse the repository at this point in the history
* Eligibility Fixes
Change eligibilty password and login to use SFTP inputs.
Rework MIME parsing.
Fix conditional preventing any request calls.

* Prevent auto select unassigned for multiselect if other items are selected. i.e only select for empty selects.

* add title for review encounters

* refactor out oeHttp
  • Loading branch information
sjpadgett authored Oct 24, 2024
1 parent 929c0a0 commit 13d1752
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 106 deletions.
14 changes: 7 additions & 7 deletions interface/patient_file/encounter/forms.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@
?>
<!DOCTYPE html>
<html>

<head>

<title class="title"></title>
<meta http-equiv="Content-Type" content="text/html; charset=<?php echo $GLOBALS['charset']; ?>">
<?php require $GLOBALS['srcdir'] . '/js/xl/dygraphs.js.php'; ?>

<?php Header::setupHeader(['common','esign','dygraphs', 'utility']); ?>
Expand Down Expand Up @@ -293,11 +294,11 @@
?>

<?php if ($reviewMode) { ?>
$("body table:first").hide();
$(".encounter-summary-column").hide();
$(".btn").hide();
$(".encounter-summary-column:first").show();
$(".title:first").text(<?php echo xlj("Review"); ?> + " " + $(".title:first").text() + " ( " + <?php echo js_escape($encounter); ?> + " )");
$("body table:first").hide();
$(".encounter-summary-column").hide();
$(".btn").hide();
$(".encounter-summary-column:first").show();
$(".title:first").text(<?php echo xlj("Review Encounter"); ?> + " (" + <?php echo js_escape($encounter); ?> + ")");
<?php } ?>
});

Expand All @@ -310,7 +311,6 @@ function deleteme() {
return false;
}


// create new follow-up Encounter.
function createFollowUpEncounter() {

Expand Down
20 changes: 14 additions & 6 deletions library/options.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,22 @@ function generate_select_list(
preg_match_all('/select2/m', ($class ?? ''), $matches, PREG_SET_ORDER, 0);
if (array_key_exists('placeholder', $attributes) && count($matches) > 0) {
// We have a placeholder attribute as well as a select2 class indicating there
// should be provide a truley empty option.
// should be provided a truly empty option.
$_options[] = [];
} else {
$_options[] = [
'label' => $selectEmptyName,
'value' => '',
'isSelected' => true,
];
if ($multiple && !empty($currvalue)) {
$_options[] = [
'label' => $selectEmptyName,
'value' => '',
'isSelected' => false,
];
} else {
$_options[] = [
'label' => $selectEmptyName,
'value' => '',
'isSelected' => true,
];
}
}
}

Expand Down
198 changes: 107 additions & 91 deletions src/Billing/EDI270.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
require_once(dirname(__FILE__) . "/../../library/edihistory/codes/edih_271_code_class.php");

use edih_271_codes;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\MultipartStream;
use OpenEMR\Billing\BillingProcessor\BillingClaimBatchControlNumber;
use OpenEMR\Common\Http\oeHttp;
use OpenEMR\Common\Crypto\CryptoGen;
use OpenEMR\Common\Utils\RandomGenUtils;

// @TODO global to become private var when this goes to a class.
Expand All @@ -49,13 +51,13 @@ public static function createISA($row, $X12info, $segTer, $compEleSep)
$ISA = array();
$ISA[0] = "ISA"; // Interchange Control Header Segment ID
$ISA[1] = "00"; // Author Info Qualifier
$ISA[2] = str_pad("0000000", 10, " "); // Author Information
$ISA[2] = str_pad("", 10, " "); // Author Information
$ISA[3] = "00"; // Security Information Qualifier
// MEDI-CAL NOTE: For Leased-Line & Dial-Up use '01',
// for BATCH use '00'.
// '00' No Security Information Present
// (No Meaningful Information in I04)
$ISA[4] = str_pad("0000000000", 10, " "); // Security Information
$ISA[4] = str_pad("", 10, " "); // Security Information
$ISA[5] = str_pad($X12info['x12_isa05'], 2, " "); // Interchange ID Qualifier
$ISA[6] = str_pad($X12info['x12_sender_id'], 15, " "); // INTERCHANGE SENDER ID
$ISA[7] = str_pad($X12info['x12_isa07'], 2, " "); // Interchange ID Qualifier
Expand Down Expand Up @@ -430,8 +432,11 @@ public static function requestEligibleTransaction($pid = 0, $eFlag = false)
LEFT JOIN insurance_companies as c ON (c.id = i.provider)
WHERE p.pid = ?";
$res = sqlStatement($query, array($pid));

$details = self::requestRealTimeEligible($res, '', "~", ':', true);
$resArray = array();
while ($row = sqlFetchArray($res)) {
$resArray[] = $row;
}
$details = self::requestRealTimeEligible($resArray, '', "~", ':', true);
if ($details === false) {
$details = "Error: Nothing returned from X12 Partner.";
}
Expand Down Expand Up @@ -666,9 +671,6 @@ public static function showEligibilityInformation($pid, $flag = false)
}
$col++;
}
if ($col === 2) {
$showString .= "</div>";
}
if ($title === 1) {
$showString .= "<br /><span><b>" . xlt("Nothing To Report") . "</b></span><br />";
}
Expand Down Expand Up @@ -789,82 +791,83 @@ public static function requestEligibility($partner = '', $x12_270 = '')
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
$payloadId = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

$boundary = RandomGenUtils::createUniqueToken(12);
$rt_passwrd = $X12info['x12_isa04'];
$rt_user = $X12info['x12_isa02'];

$cryptoGen = new CryptoGen();
$decrypted_password = $cryptoGen->decryptStandard($X12info['x12_sftp_pass']);
$rt_password = $decrypted_password;
$rt_user = $X12info['x12_sftp_login'];
$sender_id = $X12info['x12_sender_id'];
$receiver_id = $X12info['x12_receiver_id'];
$now_date = date("Y-m-d\TH:i:s\Z");
$headers = array(
'Content-Type' => "multipart/form-data; boundary=$boundary",
'Host' => ' wsd.officeally.com'
);

// IMPORTANT: Do not change the format of $mime_body below.
// HTTP MIME Multipart is non-normative. LFs matter...
//
$mime_body = <<<MIMEBODY
--$boundary
Content-Disposition: form-data; name="ProcessingMode"
RealTime
--$boundary
Content-Disposition: form-data; name="TimeStamp"
$now_date
--$boundary
Content-Disposition: form-data; name="PayloadID"
$payloadId
--$boundary
Content-Disposition: form-data; name="CORERuleVersion"
2.2.0
--$boundary
Content-Disposition: form-data; name="ReceiverID"
$url = $X12info['x12_eligibility_endpoint'];
$multipart_form = [
[
'name' => 'PayloadType',
'contents' => 'X12_270_Request_005010X279A1',
],
[
'name' => 'ProcessingMode',
'contents' => 'RealTime',
],
[
'name' => 'PayloadId',
'contents' => $payloadId,
],
[
'name' => 'TimeStamp',
'contents' => $now_date,
],
[
'name' => 'UserName',
'contents' => $rt_user,
],
[
'name' => 'Password',
'contents' => $rt_password,
],
[
'name' => 'SenderId',
'contents' => $sender_id,
],
[
'name' => 'ReceiverId',
'contents' => $receiver_id,
],
[
'name' => 'CORERuleVersion',
'contents' => '2.2.0',
],
[
'name' => 'Payload',
'contents' => $x12_270,
]

];

$params = [
'headers' => [
'Connection' => 'close',
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
],
'body' => new MultipartStream($multipart_form, $boundary), // here is all the magic
];

$receiver_id
--$boundary
Content-Disposition: form-data; name="SenderID"
$sender_id
--$boundary
Content-Disposition: form-data; name="PayloadType"
X12_270_Request_005010X279A1
--$boundary
Content-Disposition: form-data; name="UserName"
$rt_user
--$boundary
Content-Disposition: form-data; name="Password"
$rt_passwrd
--$boundary
Content-Disposition: form-data; name="Payload"
$x12_270
--$boundary--
MIMEBODY;
// send the request
$response = oeHttp::bodyFormat('body')
//->setDebug('5000')/* @todo uncomment and set proxy port to debug eg Fiddler */
->usingHeaders($headers)
->post($X12info['x12_eligibility_endpoint'], $mime_body);

$formBody = $response->body();
$contentType = $response->header('Content-Type')[0];
$hContentLength = (int)$response->header('Content-Length')[0];
$cksum = ($hContentLength - strlen($formBody)) === 0 ? true : false; // validate content size
$formData = self::mimeParse($formBody, $contentType);
$response = (new Client())->request('POST', $url, $params);

$formBody = $response->getBody();
$contentType = $response->getHeader('Content-Type')[0];
$hContentLength = (int)$response->getHeader('Content-Length')[0];
$cksum = ($hContentLength - strlen($formBody)) === 0; // validate content size
$formData = self::mimeParse($formBody, $contentType);
// validate the response
$errors = '';
if (!$cksum) {
$errors .= "Error:" . xlt("Request Content Fails Integrity Test");
}
if ($response->status() !== 200) {
$errors .= "\nError:" . xlt("Http Error") . ": " . $response->getReasonPhrase() . " : " . $response->status();
if ($response->getStatusCode() !== 200) {
$errors .= "\nError:" . xlt("Http Error") . ": " . $response->getReasonPhrase() . " : " . $response->getStatusCode();
}
if ($formData['ErrorCode'] != "Success") {
$errors .= "\nError:" . $formData['ErrorCode'] . "\n" . $formData['ErrorMessage'];
Expand All @@ -874,28 +877,41 @@ public static function requestEligibility($partner = '', $x12_270 = '')
return $errors;
}

$x12_271 = $formData['Payload'];

return $x12_271;
return $formData['Payload'];
}

public static function mimeParse(string $formBody = null, $contentType)
public static function mimeParse(string $formBody, $contentType)
{
$mimeBody = preg_replace('~\r\n?~', "\r", $formBody);
list($contentType, $bound, $cs) = explode(";", trim($contentType)); // $contentType & $cs are throwaways
$bound = explode("=", trim($bound, ' '))[1];
$mimeFields = preg_split("/-+$bound/", $mimeBody);
array_pop($mimeFields);
$hold = $isMatches = [];
foreach ($mimeFields as $id => $field) {
// Normalize
$mimeBody = preg_replace('~\r\n?~', "\r\n", $formBody);
// Extract boundary from content type
list($contentType, $boundaryDirective) = explode(";", trim($contentType));
$boundary = trim(explode("=", trim($boundaryDirective))[1], '"');
$boundary = preg_quote($boundary, '/');
// Split the body using boundary
$pattern = "/--" . $boundary . "(--)?\r?\n/";
$mimeFields = preg_split($pattern, $mimeBody);
// Remove any empty elements, like the final one after the closing boundary
$mimeFields = array_filter($mimeFields);
$mimeData = [];
foreach ($mimeFields as $field) {
if (empty($field)) {
continue;
}
preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $field, $isMatches);
if (preg_match('/^(.*)\[\]$/i', $isMatches[1], $hold)) {
$mimeData[$hold[1]][] = $isMatches[2];
} else {
$mimeData[$isMatches[1]] = $isMatches[2];
// extract name and content from the field
if (preg_match('/Content-Disposition:.*?name="([^"]+)"[\r\n]+(?:[^\r\n]+\r\n)?([\s\S]+)\r\n$/', $field, $matches)) {
$name = $matches[1];
$content = trim($matches[2]);
// Check if the field is an array (i.e., name ends with "[]")
if (substr($name, -2) === '[]') {
$name = substr($name, 0, -2);
if (!isset($mimeData[$name])) {
$mimeData[$name] = [];
}
$mimeData[$name][] = $content;
} else {
$mimeData[$name] = $content;
}
}
}
return $mimeData;
Expand Down Expand Up @@ -927,7 +943,7 @@ public static function parseEdi271($content)
$responses = $content;
}

// Loop through each 271. '\n' delims records in batch.
// Loop through each 271. '\n' delims records in batch.
foreach ($responses as $new) {
if (empty($new)) {
continue;
Expand Down Expand Up @@ -1017,7 +1033,7 @@ public static function parseEdi271($content)
$trace++;
if ($in['pid']) {
$in['benefits'] = $benefits ? $benefits : [];
array_push($subscribers, $in);
$subscribers[] = $in;
$loop['context'] = $elements[0];
$benefits = [];
}
Expand Down
4 changes: 2 additions & 2 deletions templates/x12_partners/general_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@
</div>
</div>
<div class="form-row my-sm-2">
<label for="x12_sftp_login" class="col-form-label col-sm-2">{xlt t='SFTP Login Credentials'}</label>
<label for="x12_sftp_login" class="col-form-label col-sm-2">{xlt t='SFTP or Eligibility Login Credentials'}</label>
<div class="col-sm-8">
<input type="text" maxlength="45" id="x12_sftp_login" name="x12_sftp_login" class="form-control" value="{$partner->get_x12_sftp_login()|attr}" onKeyDown="PreventIt(event)">
</div>
</div>
<div class="form-row my-sm-2">
<label for="x12_sftp_pass" class="col-form-label col-sm-2">{xlt t='SFTP Pass Credentials'}</label>
<label for="x12_sftp_pass" class="col-form-label col-sm-2">{xlt t='SFTP or Eligibility Pass Credentials'}</label>
<div class="col-sm-8">
<input type="password" maxlength="45" id="x12_sftp_pass" name="x12_sftp_pass" class="form-control" value="{$partner->get_x12_sftp_pass()|attr}" onKeyDown="PreventIt(event)">
</div>
Expand Down

0 comments on commit 13d1752

Please sign in to comment.