3 * Inc file for the 270 / 271 creation and uploading
5 * This program creates the segments for the x12 270 eligibility file
6 * It also allows the reading and storing of the x12 271 file
9 * @link http://www.open-emr.org
10 * @author Terry Hill <terry@lilysystems.com>
11 * @author Brady Miller <brady.g.miller@gmail.com>
12 * @author Jerry Padgett <sjpadgett@gmail.com>
13 * @copyright Copyright (c) 2010 MMF Systems, Inc
14 * @copyright Copyright (c) 2016 Terry Hill <terry@lillysystems.com>
15 * @copyright Copyright (c) 2017 Brady Miller <brady.g.miller@gmail.com>
16 * @copyright Copyright (c) 2019 Jerry Padgett <sjpadgett@gmail.com>
17 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
19 require_once("$srcdir/edihistory/codes/edih_271_code_class.php");
21 use OpenEMR\Common\Http\oeHttp;
22 use OpenEMR\Common\Utils\RandomGenUtils;
24 // @TODO global to become private var when this goes to a class.
28 // SEGMENT FUNCTION START
30 // ISA Segment - EDI-270 format
32 function create_ISA($row, $X12info, $segTer, $compEleSep)
35 $ISA[0] = "ISA"; // Interchange Control Header Segment ID
36 $ISA[1] = "00"; // Author Info Qualifier
37 $ISA[2] = str_pad("0000000", 10, " "); // Author Information
38 $ISA[3] = "00"; // Security Information Qualifier
39 // MEDI-CAL NOTE: For Leased-Line & Dial-Up use '01',
40 // for BATCH use '00'.
41 // '00' No Security Information Present
42 // (No Meaningful Information in I04)
43 $ISA[4] = str_pad("0000000000", 10, " "); // Security Information
44 $ISA[5] = str_pad($X12info['x12_isa05'], 2, " "); // Interchange ID Qualifier
45 $ISA[6] = str_pad($X12info['x12_sender_id'], 15, " "); // INTERCHANGE SENDER ID
46 $ISA[7] = str_pad($X12info['x12_isa07'], 2, " "); // Interchange ID Qualifier
47 $ISA[8] = str_pad($X12info['x12_receiver_id'], 15, " "); // INTERCHANGE RECEIVER ID
48 $ISA[9] = str_pad(date('ymd'), 6, " "); // Interchange Date (YYMMDD)
49 $ISA[10] = str_pad(date('Hi'), 4, " "); // Interchange Time (HHMM)
50 $ISA[11] = "^"; // Interchange Control Standards Identifier
51 $ISA[12] = str_pad("00501", 5, " "); // Interchange Control Version Number
52 $ISA[13] = str_pad("000000001", 9, " "); // INTERCHANGE CONTROL NUMBER
53 $ISA[14] = str_pad($X12info['x12_isa14'], 1, " "); // Acknowledgment Request [0= not requested, 1= requested]
54 $ISA[15] = str_pad($X12info['x12_isa15'], 1, " "); // Usage Indicator [ P = Production Data, T = Test Data ]
55 $ISA['Created'] = implode('*', $ISA); // Data Element Separator
56 $ISA['Created'] = $ISA['Created'] ."*";
57 $ISA['Created'] = $ISA ['Created'] . $compEleSep . $segTer;
59 return trim($ISA['Created']);
61 // GS Segment - EDI-270 format
62 function create_GS($row, $X12info, $segTer, $compEleSep)
65 $GS[0] = "GS"; // Functional Group Header Segment ID
66 $GS[1] = "HS"; // Functional ID Code [ HS = Eligibility, Coverage or Benefit Inquiry (270) ]
67 $GS[2] = $X12info['x12_sender_id']; // Application Sender's ID
68 $GS[3] = $X12info['x12_receiver_id']; // Application Receiver's ID
69 $GS[4] = date('Ymd'); // Date [CCYYMMDD]
70 $GS[5] = date('His'); // Time [HHMM] Group Creation Time
71 $GS[6] = "2"; // Group Control Number No zeros for 5010
72 $GS[7] = "X"; // Responsible Agency Code Accredited Standards Committee X12 ]
73 $GS[8] = "005010X279A1"; // Version Release / Industry[ Identifier Code Query 005010X279A1
74 $GS['Created'] = implode('*', $GS); // Data Element Separator
75 $GS['Created'] = $GS ['Created'] . $segTer; // change the information in the tag or change the tag
76 return trim($GS['Created']);
78 // ST Segment - EDI-270 format
79 function create_ST($row, $X12info, $segTer, $compEleSep)
82 $ST[0] = "ST"; // Transaction Set Header Segment ID
83 $ST[1] = "270"; // Transaction Set Identifier Code (Inquiry Request)
84 $ST[2] = "000000003"; // Transaction Set Control Number - Must match SE's
85 $ST[3] = "005010X279A1"; // Standard 005010X279A1 in $ST[3]
86 $ST['Created'] = implode('*', $ST); // Data Element Separator
87 $ST['Created'] = $ST ['Created'] . $segTer;
88 return trim($ST['Created']);
90 // BHT Segment - EDI-270 format
91 function create_BHT($row, $X12info, $segTer, $compEleSep)
94 $BHT[0] = "BHT"; // Beginning of Hierarchical Transaction Segment ID
95 $BHT[1] = "0022"; // Subscriber Structure Code
96 $BHT[2] = "13"; // Purpose Code - This is a Request
97 $BHT[3] = "PROVTest600"; // Submitter Transaction Identifier
98 // This information is required by the information Receiver when using Real Time transactions.
99 // For BATCH this can be used for optional information.
100 $BHT[4] = str_pad(date('Ymd'), 8, " "); // Date Transaction Set Created
101 $BHT[5] = str_pad(date('Hi'), 4, " "); // Time Transaction Set Created no space after and 1300 is plenty
102 $BHT['Created'] = implode('*', $BHT); // Data Element Separator
103 $BHT['Created'] = $BHT ['Created'] . $segTer;
104 return trim($BHT['Created']);
106 // HL Segment - EDI-270 format
107 function create_HL($row, $nHlCounter, $X12info, $segTer, $compEleSep)
110 $HL[0] = "HL"; // Hierarchical Level Segment ID
112 $HL[1] = $nHlCounter; // Hierarchical ID No.
113 if ($nHlCounter == 1) {
115 $HL[3] = 20; // Description: Identifies the payor, maintainer, or source of the information.
116 $HL[4] = 1; // 1 Additional Subordinate HL Data Segment in This Hierarchical Structure.
117 } else if ($nHlCounter == 2) {
118 $HL[2] = 1; // Hierarchical Parent ID Number
119 $HL[3] = 21; // Hierarchical Level Code. '21' Information Receiver
120 $HL[4] = 1; // 1 Additional Subordinate HL Data Segment in This Hierarchical Structure.
123 $HL[3] = 22; // Hierarchical Level Code.'22' Subscriber
124 $HL[4] = 0; // 0 no Additional Subordinate in the Hierarchical Structure.
126 $HL['Created'] = implode('*', $HL); // Data Element Separator
127 $HL['Created'] = $HL ['Created'] . $segTer;
128 return trim($HL['Created']);
130 // NM1 Segment - EDI-270 format
131 function create_NM1($row, $nm1Cast, $X12info, $segTer, $compEleSep)
134 $NM1[0] = "NM1"; // Subscriber Name Segment ID
135 if ($nm1Cast == 'PR') {
136 $NM1[1] = "PR"; // Entity ID Code - Payer [PR Payer]
137 $NM1[2] = "2"; // Entity Type - Non-Person
138 $NM1[3] = $row["payer_name"]; // Organizational Name
139 $NM1[4] = ""; // Data Element not required.
140 $NM1[5] = ""; // Data Element not required.
141 $NM1[6] = ""; // Data Element not required.
142 $NM1[7] = ""; // Data Element not required.
143 $NM1[8] = $X12info['id_number'] ? $X12info['id_number'] : "46"; // 46 - Electronic Transmitter Identification Number (ETIN)
144 if ($GLOBALS['enable_oa']) {
145 $payerId = $row['eligibility_id'];
147 $payerId = $row['cms_id'];
149 $NM1[9] = $payerId; // Application Sender's ID
150 } else if ($nm1Cast == 'FA') {
151 $NM1[1] = "FA"; // Entity ID Code - Facility [FA Facility]
152 $NM1[2] = "2"; // Entity Type - Non-Person
153 $NM1[3] = $row['facility_name']; // Organizational Name
154 $NM1[4] = ""; // Data Element not required.
155 $NM1[5] = ""; // Data Element not required.
156 $NM1[6] = ""; // Data Element not required.
157 $NM1[7] = ""; // Data Element not required.
159 $NM1[9] = $row['facility_npi'];
160 } else if ($nm1Cast == '1P') {
161 $NM1[1] = "1P"; // Entity ID Code - Provider
162 $NM1[2] = "2"; // Entity Type - Non-Person
163 $NM1[3] = $row['facility_name']; // Organizational Name
164 $NM1[4] = ""; // Data Element not required.
165 $NM1[5] = ""; // Data Element not required.
166 $NM1[6] = ""; // Data Element not required.
167 $NM1[7] = ""; // Data Element not required.
169 $NM1[9] = $row['provider_npi'];
170 } else if ($nm1Cast == 'IL') {
171 $NM1[1] = "IL"; // Insured or Subscriber
172 $NM1[2] = "1"; // Entity Type - Person
173 $NM1[3] = $row['lname']; // last Name
174 $NM1[4] = $row['fname']; // first Name
175 $NM1[5] = $row['mname']; // middle Name
176 $NM1[6] = ""; // data element
177 $NM1[7] = ""; // data element
178 $NM1[8] = "MI"; // Identification Code Qualifier
179 $NM1[9] = $row['policy_number']; // Identification Code
181 $NM1['Created'] = implode('*', $NM1); // Data Element Separator
182 $NM1['Created'] = $NM1['Created'] . $segTer;
183 return trim($NM1['Created']);
185 // REF Segment - EDI-270 format
186 function create_REF($row, $ref, $X12info, $segTer, $compEleSep)
189 $REF[0] = "REF"; // Subscriber Additional Identification does not want this for anything
191 $REF[1] = "4A"; // Reference Identification Qualifier
192 $REF[2] = $row['provider_pin']; // Provider Pin.
194 $REF[1] = "EJ"; // 'EJ' for Patient Account Number does not want this for patient
195 $REF[2] = $row['pid']; // Patient Account No.
197 $REF['Created'] = implode('*', $REF); // Data Element Separator
198 $REF['Created'] = $REF['Created'] . $segTer;
199 return trim($REF['Created']);
201 // TRN Segment - EDI-270 format
202 function create_TRN($row, $tracno, $refiden, $X12info, $segTer, $compEleSep)
205 $TRN[0] = "TRN"; // Subscriber Trace Number Segment ID
206 $TRN[1] = "1"; // Trace Type Code � Current Transaction Trace Numbers
207 $TRN[2] = $tracno; // Trace Number
208 $TRN[3] = "9000000000"; // Originating Company ID � must be 10 positions in length
209 $TRN[4] = $refiden; // Additional Entity Identifier (i.e. Subdivision)
210 $TRN['Created'] = implode('*', $TRN); // Data Element Separator
211 $TRN['Created'] = $TRN['Created'] . $segTer;
212 return trim($TRN['Created']);
214 // DMG Segment - EDI-270 format
215 function create_DMG($row, $X12info, $segTer, $compEleSep)
218 $DMG[0] = "DMG"; // Date or Time or Period Segment ID
219 $DMG[1] = "D8"; // Date Format Qualifier - (D8 means CCYYMMDD)
220 $DMG[2] = $row['dob']; // Subscriber's Birth date
221 $DMG[3] = strtoupper($row['sex'][0]);
222 $DMG['Created'] = implode('*', $DMG); // Data Element Separator
223 $DMG['Created'] = $DMG['Created'] . $segTer;
224 return trim($DMG['Created']);
226 // DTP Segment - EDI-270 format
227 function create_DTP($row, $qual, $X12info, $segTer, $compEleSep)
230 $DTP[0] = "DTP"; // Date or Time or Period Segment ID
231 $DTP[1] = $qual; // Qualifier - Date of Service
232 $DTP[2] = "D8"; // Date Format Qualifier - (D8 means CCYYMMDD)
233 if ($qual == '102') {
234 $DTP[3] = $row['date']; // Date
236 $DTP[3] = !empty($row['pc_eventDate']) && $row['pc_eventDate'] > '20010101' ? $row['pc_eventDate'] : date("Ymd"); // Date of Service
238 $DTP['Created'] = implode('*', $DTP); // Data Element Separator
239 $DTP['Created'] = $DTP['Created'] . $segTer;
240 return trim($DTP['Created']);
242 // EQ Segment - EDI-270 format
243 function create_EQ($row, $X12info, $segTer, $compEleSep)
246 $EQ[0] = "EQ"; // Subscriber Eligibility or Benefit Inquiry Information
247 $EQ[1] = "30"; // Service Type Code
248 $EQ['Created'] = implode('*', $EQ); // Data Element Separator
249 $EQ['Created'] = $EQ['Created'] . $segTer;
250 return trim($EQ['Created']);
252 // SE Segment - EDI-270 format
253 function create_SE($row, $segmentcount, $X12info, $segTer, $compEleSep)
256 $SE[0] = "SE"; // Transaction Set Trailer Segment ID
257 $SE[1] = $segmentcount; // Segment Count
258 $SE[2] = "000000003"; // Transaction Set Control Number - Must match ST's
259 $SE['Created'] = implode('*', $SE); // Data Element Separator
260 $SE['Created'] = $SE['Created'] . $segTer;
261 return trim($SE['Created']);
263 // GE Segment - EDI-270 format
264 function create_GE($row, $X12info, $segTer, $compEleSep)
267 $GE[0] = "GE"; // Functional Group Trailer Segment ID
268 $GE[1] = "1"; // Number of included Transaction Sets
269 $GE[2] = "2"; // Group Control Number
270 $GE['Created'] = implode('*', $GE); // Data Element Separator
271 $GE['Created'] = $GE['Created'] . $segTer;
272 return trim($GE['Created']);
274 // IEA Segment - EDI-270 format
275 function create_IEA($row, $X12info, $segTer, $compEleSep)
278 $IEA[0] = "IEA"; // Interchange Control Trailer Segment ID
279 $IEA[1] = "1"; // Number of included Functional Groups
280 $IEA[2] = "000000001"; // Interchange Control Number
281 $IEA['Created'] = implode('*', $IEA);
282 $IEA['Created'] = $IEA['Created'] . $segTer;
283 return trim($IEA['Created']);
286 function translate_relationship($relationship)
288 switch ($relationship) {
301 // EDI-270 Batch file Generation
302 function print_elig($res, $X12info, $segTer, $compEleSep)
306 // For Header Segment
311 while ($row = sqlFetchArray($res)) {
312 if ($nHlCounter == 1) {
314 $PATEDI = create_ISA($row, $X12info, $segTer, $compEleSep);
316 $PATEDI .= create_GS($row, $X12info, $segTer, $compEleSep);
318 $PATEDI .= create_ST($row, $X12info, $segTer, $compEleSep);
320 $PATEDI .= create_BHT($row, $X12info, $segTer, $compEleSep);
322 $PATEDI .= create_HL($row, 1, $X12info, $segTer, $compEleSep);
323 $PATEDI .= create_NM1($row, 'PR', $X12info, $segTer, $compEleSep);
324 // For Provider Segment
325 $PATEDI .= create_HL($row, 2, $X12info, $segTer, $compEleSep);
326 $PATEDI .= create_NM1($row, 'FA', $X12info, $segTer, $compEleSep);
327 $nHlCounter = $nHlCounter + 2;
328 $segmentcount = 6; // segement counts - start from ST
330 // For Subscriber Segment
331 $PATEDI .= create_HL($row, $nHlCounter, $X12info, $segTer, $compEleSep);
332 $PATEDI .= create_NM1($row, 'IL', $X12info, $segTer, $compEleSep);
333 // send pid so we get it back in 271
334 $PATEDI .= create_REF($row, 'EJ', '', $segTer, '');
335 $PATEDI .= create_DMG($row, $X12info, $segTer, $compEleSep);
336 $PATEDI .= create_DTP($row, '291', $X12info, $segTer, $compEleSep);
337 $PATEDI .= create_EQ($row, $X12info, $segTer, $compEleSep);
338 $segmentcount = $segmentcount + 6;
339 $nHlCounter = $nHlCounter + 1;
340 $rowCount = $rowCount + 1;
342 $refiden = $refiden + 1;
343 if ($rowCount == sqlNumRows($res)) {
344 $segmentcount = $segmentcount + 1;
345 $PATEDI .= create_SE($row, $segmentcount, $X12info, $segTer, $compEleSep);
346 $PATEDI .= create_GE($row, $X12info, $segTer, $compEleSep);
347 $PATEDI .= create_IEA($row, $X12info, $segTer, $compEleSep);
353 function requestEligibleTransaction($pid = 0, $eFlag = false)
360 DATE_FORMAT(p.dob, '%Y%m%d') as dob,
368 i.provider as payer_id,
369 i.subscriber_relationship,
373 DATE_FORMAT(i.subscriber_DOB, '%Y%m%d') as subscriber_dob,
376 DATE_FORMAT(i.date, '%Y%m%d') as date,
377 d.lname as provider_lname,
378 d.fname as provider_fname,
379 d.npi as provider_npi,
380 d.upin as provider_pin,
381 f.federal_ein as federal_ein,
382 f.facility_npi as facility_npi,
383 f.name as facility_name,
385 c.eligibility_id as eligibility_id,
386 c.x12_default_eligibility_id as partner,
388 FROM patient_data AS p
389 LEFT JOIN users AS d on (p.providerID = d.id)
390 LEFT JOIN facility AS f on (f.id = d.facility_id)
391 LEFT JOIN insurance_data AS i ON (i.id =(SELECT id FROM insurance_data AS i WHERE pid = p.pid AND type = 'primary' ORDER BY date DESC LIMIT 1))
392 LEFT JOIN insurance_companies as c ON (c.id = i.provider)
394 $res = sqlStatement($query, array($pid));
396 $details = requestRealTimeEligible($res, '', "~", ':', true);
397 $isError = strpos($details, "Error:");
398 $isError = $isError !== false ? $isError : strpos($details, "AAA");
399 if ($isError !== false) {
400 $details = substr($details, $isError);
401 return "<div>" . nl2br(text($details)) . "</div>";
407 // EDI-270 RealTime Request & Response
408 // RealTime requires one transaction per request.
410 function requestRealTimeEligible($res, $X12info, $segTer, $compEleSep, $eFlag = false)
413 $totalCount = sqlNumRows($res);
414 $down_accum = $log = $error_accum = '';
415 while ($row = sqlFetchArray($res)) {
417 $X12info = getX12Partner($row['partner']);
419 if ($row['providerID'] === 0 || !$row['provider_npi']) {
420 $error_accum .= xlt("Error") . ": " . xlt("Provider Missing Add one in Choices") . "\n";
422 if (!$row['eligibility_id']) {
423 $error_accum .= xlt("Error") . ": " . xlt("Missing Insurance Payer Id") . "\n";
425 if (!$row['policy_number'] || !$row['subscriber_dob']) {
426 $error_accum .= xlt("Error") . ": " . xlt("Missing Subscriber Policy Number or DOB") . "\n";
428 if (!empty($error_accum)) {
432 $PATEDI = create_ISA($row, $X12info, $segTer, $compEleSep);
434 $PATEDI .= create_GS($row, $X12info, $segTer, $compEleSep);
436 $PATEDI .= create_ST($row, $X12info, $segTer, $compEleSep);
438 $PATEDI .= create_BHT($row, $X12info, $segTer, $compEleSep);
440 $PATEDI .= create_HL($row, 1, $X12info, $segTer, $compEleSep);
441 $PATEDI .= create_NM1($row, 'PR', $X12info, $segTer, $compEleSep);
442 // For Provider Segment
443 $PATEDI .= create_HL($row, 2, $X12info, $segTer, $compEleSep);
444 //$PATEDI .= create_NM1($row, 'FA', $X12info, $segTer, $compEleSep); // unsure but this may have to be an option
445 $PATEDI .= create_NM1($row, '1P', $X12info, $segTer, $compEleSep);
446 // For Subscriber Segment
447 $PATEDI .= create_HL($row, 3, $X12info, $segTer, $compEleSep);
448 $PATEDI .= create_NM1($row, 'IL', $X12info, $segTer, $compEleSep);
449 // send pid so we get it back in 271
450 $PATEDI .= create_REF($row, 'EJ', '', $segTer, '');
451 $PATEDI .= create_DMG($row, $X12info, $segTer, $compEleSep);
453 $PATEDI .= create_DTP($row, '291', $X12info, $segTer, $compEleSep);
454 $PATEDI .= create_EQ($row, $X12info, $segTer, $compEleSep);
456 $PATEDI .= create_SE($row, 13, $X12info, $segTer, $compEleSep);
457 $PATEDI .= create_GE($row, $X12info, $segTer, $compEleSep);
458 $PATEDI .= create_IEA($row, $X12info, $segTer, $compEleSep);
460 $result = requestEligibility($X12info['id'], $PATEDI);
462 $e = strpos($result, "Error:");
464 $error_accum = $result;
466 $down_accum .= $result . "\n"; // delimit for next new request
469 $log .= "*** 270 " . xlt("Request Message") . " $rowCount of $totalCount\n" . $PATEDI . "\n" . $error_accum . "\n"; // keep a log.
472 // parse the 271 responses from 270 requests sent.
473 $process = parseEdi271($down_accum);
474 $log = xlt("List of ") . $rowCount . " " . xlt("Requests Sent") . ":\n" . $log . "\n" . $process;
483 function show_elig($res, $X12info, $segTer, $compEleSep)
487 echo " <div id='report_results'>
490 <th style='width:12%;'> ". htmlspecialchars(xl('Facility Name'), ENT_NOQUOTES) ."</th>
491 <th style='width:9%;'> ". htmlspecialchars(xl('Facility NPI'), ENT_NOQUOTES) ."</th>
492 <th style='width:15%;'> ". htmlspecialchars(xl('Insurance Comp'), ENT_NOQUOTES) ."</th>
493 <th style='width:8%;'> ". htmlspecialchars(xl('Policy No'), ENT_NOQUOTES) ."</th>
494 <th style='width:16%;'> ". htmlspecialchars(xl('Patient Name'), ENT_NOQUOTES) ."</th>
495 <th style='width:7%;'> ". htmlspecialchars(xl('DOB'), ENT_NOQUOTES) ."</th>
496 <th style='width:6%;'> ". htmlspecialchars(xl('Gender'), ENT_NOQUOTES) ."</th>
497 <th style='width:9%;'> ". htmlspecialchars(xl('SSN'), ENT_NOQUOTES) ."</th>
498 <th style='width:2%;'> </th>
503 while ($row = sqlFetchArray($res)) {
507 $background = '#FFF';
509 $background = '#FFF';
513 $elig[0] = $row['facility_name']; // Inquiring Provider Name calendadr
514 $elig[1] = $row['facility_npi']; // Inquiring Provider NPI
515 $elig[2] = $row['payer_name']; // Payer Name our insurance co name
516 $elig[3] = $row['policy_number']; // Subscriber ID
517 $elig[4] = $row['subscriber_lname']; // Subscriber Last Name
518 $elig[5] = $row['subscriber_fname']; // Subscriber First Name
519 $elig[6] = $row['subscriber_mname']; // Subscriber Middle Initial
520 $elig[7] = $row['subscriber_dob']; // Subscriber Date of Birth
521 $elig[8] = substr($row['subscriber_sex'], 0, 1); // Subscriber Sex
522 $elig[9] = $row['subscriber_ss']; // Subscriber SSN
523 $elig[10] = translate_relationship($row['subscriber_relationship']); // Pt Relationship to insured
524 $elig[11] = $row['lname']; // Dependent Last Name
525 $elig[12] = $row['fname']; // Dependent First Name
526 $elig[13] = $row['mname']; // Dependent Middle Initial
527 $elig[14] = $row['dob']; // Dependent Date of Birth
528 $elig[15] = substr($row['sex'], 0, 1); // Dependent Sex
529 $elig[16] = $row['pc_eventDate']; // Date of service
530 $elig[17] = "30"; // Service Type
531 $elig[18] = $row['pubpid']; // Patient Account Number pubpid
533 echo " <tr id='PR".$i."_". htmlspecialchars($row['policy_number'], ENT_QUOTES)."'>
534 <td class ='detail' style='width:12%;'>". htmlspecialchars($row['facility_name'], ENT_NOQUOTES) ."</td>
535 <td class ='detail' style='width:9%;'>". htmlspecialchars($row['facility_npi'], ENT_NOQUOTES) ."</td>
536 <td class ='detail' style='width:15%;'>". htmlspecialchars($row['payer_name'], ENT_NOQUOTES) ."</td>
537 <td class ='detail' style='width:8%;'>". htmlspecialchars($row['policy_number'], ENT_NOQUOTES) ."</td>
538 <td class ='detail' style='width:16%;'>". htmlspecialchars($row['subscriber_lname']." ".$row['subscriber_fname'], ENT_NOQUOTES) ."</td>
539 <td class ='detail' style='width:7%;'>". htmlspecialchars($row['subscriber_dob'], ENT_NOQUOTES) ."</td>
540 <td class ='detail' style='width:6%;'>". htmlspecialchars($row['subscriber_sex'], ENT_NOQUOTES) ."</td>
541 <td class ='detail' style='width:9%;'>". htmlspecialchars($row['subscriber_ss'], ENT_NOQUOTES) ."</td>
542 <td class ='detail' style='width:2%;'>
543 <img src=\"" . $GLOBALS['images_static_relative'] . "/deleteBtn.png\" title=" .htmlspecialchars(xl('Delete Row'), ENT_QUOTES) . " style='cursor:pointer;cursor:hand;' onclick='deletetherow(\"" . $i."_". htmlspecialchars($row['policy_number'], ENT_QUOTES) . "\")'>
552 <td class='norecord' colspan=9>
553 <div style='padding:5px;font-family:arial;font-size:13px;text-align:center;'>". htmlspecialchars(xl('No records found'), ENT_NOQUOTES) . "</div>
561 // To Show Eligibility Verification data
562 function show_eligibility_information($pid, $flag = false)
565 "SELECT eligr.*, eligv.insurance_id, eligv.copay, insd.pid, insc.name, " .
566 "Date_Format(eligv.eligibility_check_date, '%W %M %e, %Y %h:%i %p') AS verificationDate " .
567 "FROM eligibility_verification eligv " .
568 "INNER JOIN benefit_eligibility eligr ON eligr.verification_id = eligv.verification_id " .
569 "INNER JOIN insurance_data insd ON insd.id = eligv.insurance_id " .
570 "INNER JOIN insurance_companies insc ON insc.id = insd.provider " .
571 "WHERE insd.pid = ? AND eligv.eligibility_check_date = " .
572 "(SELECT Max(eligibility_verification.eligibility_check_date) FROM eligibility_verification ".
573 "WHERE eligibility_verification.insurance_id = eligv.insurance_id)";
574 $result = sqlStatement($query, array($pid));
576 $showString = "<div class='row'>";
579 while ($benefit = sqlFetchArray($result)) {
582 $showString .= "<div class='col col-sm-12'>\n";
583 $showString .= "<b>" . xlt('Insurance Provider') . ":</b> " . (!empty($benefit['name']) ? text($benefit['name']) : xlt('n/a')) . "<br/>\n";
584 $showString .= "<b>" . xlt('Verified On') . ":</b> " . text($benefit['verificationDate']) . "<br/><br/>\n";
585 $showString .= "</div><br/>\n";
587 $benefit['start_date'] = strpos($benefit['start_date'], "0000") === false ? $benefit['start_date'] : '';
588 $benefit['end_date'] = strpos($benefit['end_date'], "0000") === false ? $benefit['end_date'] : '';
590 switch ($benefit['type']) {
604 $color = "darkgreen";
607 $showString .= "\n<div class='col col-sm-6' >\n";
608 $showString .= !empty($benefit['benefit_type']) ? "<b style='color: $color'>" . xlt('Benefit Type') . ": " . text($benefit['benefit_type']) . "</b><br />\n" : '';
609 $showString .= !empty($benefit['start_date']) ? "<b>" . xlt('Start Date') . ":</b> " . text(date("d/m/Y", strtotime($benefit['start_date']))) . "<br />\n" : '';
610 $showString .= !empty($benefit['end_date']) ? "<b>" . xlt('End Date') . ":</b> " . text(date("d/m/Y", strtotime($benefit['end_date']))) . "<br />\n" : '';
611 $showString .= !empty($benefit['coverage_level']) ? "<b>" . xlt('Coverage Level') . ":</b> " .text($benefit['coverage_level']) . "<br />\n" : '';
612 $showString .= !empty($benefit['coverage_type']) ? "<b>" . xlt('Coverage Type') . ":</b> " . text($benefit['coverage_type']) . "<br />\n" : '';
613 $showString .= !empty($benefit['plan_type']) ? "<b>" . xlt('Plan Type') . ":</b> " . text($benefit['plan_type']) . "<br />\n" : '';
614 $showString .= !empty($benefit['plan_desc']) ? "<b>" . xlt('Plan Description') . ":</b> " . text(text($benefit['plan_desc'])) . "<br />\n" : '';
615 $showString .= !empty($benefit['coverage_period']) ? "<b>" . xlt('Coverage Period') . ":</b> " . text($benefit['coverage_period']) . "<br />\n" : '';
616 $showString .= !empty($benefit['amount']) ? "<b>" . xlt('Amount') . ":</b> " . text($benefit['amount']) . "<br />\n" : '';
617 $showString .= !empty($benefit['percent']) ? "<b>" . xlt('Percentage') . ":</b> " . text($benefit['percent']) . "<br />\n" : '';
618 $showString .= !empty($benefit['network_ind']) ? "<b>" . xlt('Network Indicator') . ":</b> " . text($benefit['network_ind']) . "<br />\n" : '';
619 $showString .= !empty($benefit['message']) ? "<b>" . xlt('Message') . ":</b> " . text($benefit['message']) . "<br />\n" : '';
620 $showString .= "</div>";
623 $showString .= "</div>\n<br/><div class='row'>\n";
629 $showString .= "</div>";
632 $showString = "<br><span><b>" . xlt("Nothing To Report") . "</b></span><br>";
634 $showString .= "</div>\n";
639 // Function to save the values in eligibility_verification table
640 function eligibility_verification_save($subscriber = [])
642 $verification_id = 0;
644 $partner_id = $subscriber['isa_sender_id'];
645 $patient_id = $subscriber['pid'];
647 $query = "SELECT id, copay FROM insurance_data WHERE type = 'primary' and pid = ?";
648 $insId = sqlQuery($query, array($patient_id));
649 if ($insId !== false) {
650 $insurance_id = $insId['id'];
651 $copay = $insId['copay'];
654 $query = "SELECT verification_id FROM eligibility_verification WHERE insurance_id = ?";
655 $resId = sqlQuery($query, array($insurance_id));
656 if ($resId !== false) {
657 $verification_id = $resId['verification_id'];
660 if (!empty($insurance_id)) {
661 $sqlBindArray = array();
662 $query = "REPLACE INTO eligibility_verification SET verification_id = ?, insurance_id = ?, response_id = ?, eligibility_check_date = now(), create_date = now()";
663 array_push($sqlBindArray, $verification_id, $insurance_id, $partner_id);
665 $res = sqlInsert($query, $sqlBindArray);
666 if (!$verification_id) {
667 $verification_id = $res;
671 $query = "DELETE FROM `benefit_eligibility` WHERE `benefit_eligibility`.`verification_id` = ?";
672 $res = sqlStatement($query, array($verification_id));
674 foreach ($subscriber['benefits'] as $benefit) {
681 $benefit['benefit_type'],
682 $benefit['start_date'],
683 $benefit['end_date'],
684 $benefit['coverage_level'],
685 $benefit['coverage_type'],
686 $benefit['plan_type'],
687 $benefit['plan_desc'],
688 $benefit['coverage_period'],
691 $benefit['network_ind'],
695 $query = "INSERT INTO `benefit_eligibility` " .
696 "(`verification_id`, `response_status`, `response_create_date`, `response_modify_date`, `type`, `benefit_type`, `start_date`, " .
697 "`end_date`, `coverage_level`, `coverage_type`, `plan_type`, `plan_description`, `coverage_period`, `amount`, `percent`, `network_ind`, `message`) " .
698 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
699 $res = sqlStatement($query, $bind);
703 // return array of X12 partners
704 // if id return just that id
705 function getX12Partner($id = 0)
707 // @TODO move to class
713 $returnval = sqlQuery("select * from x12_partners WHERE id = ?", array($id));
714 $X12info = $returnval;
716 $rez = sqlStatement("select * from x12_partners");
717 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
718 $returnval[$iter] = $row;
725 // return array of provider usernames
726 function getUsernames()
728 $rez = sqlStatement("select distinct username, lname, fname,id from users " .
729 "where authorized = 1 and username != ''");
730 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
731 $returnval[$iter] = $row;
737 // return formated array
739 function arrFormated(&$item, $key)
741 $item = strstr($item, '_');
742 $item = substr($item, 1, strlen($item) - 1);
746 function requestEligibility($partner = '', $x12_270 = '')
749 if (((int)$X12info['id'] !== (int)$partner) && (int)$partner > 0) {
750 $X12info = getX12Partner($partner);
753 $payloadId = "3b8c13f5-11e2-43bf-bc47-737cca04f3fe"; // a default fallback
754 if (function_exists('openssl_random_pseudo_bytes') === true) {
755 $data = openssl_random_pseudo_bytes(16);
756 $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
757 $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
758 $payloadId = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
761 $boundary = RandomGenUtils::createUniqueToken(12);
762 $rt_passwrd = $X12info['x12_isa04'];
763 $rt_user = $X12info['x12_isa02'];
764 $sender_id = $X12info['x12_sender_id'];
765 $receiver_id = $X12info['x12_receiver_id'];
766 $now_date = date("Y-m-d\TH:i:s\Z");
768 'Content-Type' => "multipart/form-data; boundary=$boundary",
769 'Host' => ' wsd.officeally.com'
772 // IMPORTANT: Do not change the format of $mime_body below.
773 // HTTP MIME Multipart is non-normative. LFs matter...
775 $mime_body = <<<MIMEBODY
777 Content-Disposition: form-data; name="ProcessingMode"
781 Content-Disposition: form-data; name="TimeStamp"
785 Content-Disposition: form-data; name="PayloadID"
789 Content-Disposition: form-data; name="CORERuleVersion"
793 Content-Disposition: form-data; name="ReceiverID"
797 Content-Disposition: form-data; name="SenderID"
801 Content-Disposition: form-data; name="PayloadType"
803 X12_270_Request_005010X279A1
805 Content-Disposition: form-data; name="UserName"
809 Content-Disposition: form-data; name="Password"
813 Content-Disposition: form-data; name="Payload"
819 $response = oeHttp::bodyFormat('body')
820 //->setDebug('5000')/* @todo uncomment and set proxy port to debug eg Fiddler */
821 ->usingHeaders($headers)
822 ->post('https://wsd.officeally.com/TransactionSite/rtx.aspx', $mime_body); // @TODO put request urls in x12 partner's for versatility.
824 $formBody = $response->body();
825 $contentType = $response->header('Content-Type')[0];
826 $hContentLength = (int)$response->header('Content-Length')[0];
827 $cksum = ($hContentLength - strlen($formBody)) === 0 ? true : false; // validate content size
828 $formData = mimeParse($formBody, $contentType);
832 $errors .= "Error:" . xlt("Request Content Fails Integrity Test");
834 if ($response->status() !== 200) {
835 $errors .= "\nError:" . xlt("Http Error") . ": " . $response->getReasonPhrase() . " : " . $response->status();
837 if ($formData['ErrorCode'] != "Success") {
838 $errors .= "\nError:" . $formData['ErrorCode'] . "\n" . $formData['ErrorMessage'];
841 $errors .= $formData['Payload'] ? "\nError:" . $formData['Payload'] : '';
845 $x12_271 = $formData['Payload'];
850 function mimeParse($formBody = '', $contentType)
852 $mimeBody = preg_replace('~\r\n?~', "\r", $formBody);
853 list($contentType, $bound, $cs) = explode(";", trim($contentType)); // $contentType & $cs are throwaways
854 $bound = explode("=", trim($bound, ' '))[1];
855 $mimeFields = preg_split("/-+$bound/", $mimeBody);
856 array_pop($mimeFields);
857 $hold = $isMatches = [];
858 foreach ($mimeFields as $id => $field) {
862 preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $field, $isMatches);
863 if (preg_match('/^(.*)\[\]$/i', $isMatches[1], $hold)) {
864 $mimeData[$hold[1]][] = $isMatches[2];
866 $mimeData[$isMatches[1]] = $isMatches[2];
872 function getPatientMatch($fn, $ln, $sex, $dob)
874 $fn = "%" . $fn . "%";
875 $ln = "%" . $ln . "%";
876 $sex = "%" . $sex . "%";
877 $dob = date("Y-m-d", strtotime($dob));
878 $sql = "SELECT pid FROM patient_data WHERE fname LIKE ? && lname LIKE ? && sex LIKE ? && DOB LIKE ?";
879 $rtn = sqlQuery($sql, array($fn, $ln, $sex, $dob));
881 return $rtn['pid'] ? $rtn['pid'] : 0;
884 function parseEdi271($content)
887 $codes = new edih_271_codes('*', '^');
888 $target = $GLOBALS['edi_271_file_path'];
890 // not sure if want to save yet
891 $target = $target . time();
893 $responses = explode("\n", $content);
894 if (empty($responses)) {
895 $responses = $content;
898 // Loop through each 271. '\n' delims records in batch.
899 foreach ($responses as $new) {
904 $subscribers = array();
914 $segments = explode("~", $new);
916 if (count($segments) < 6) {
917 if (file_exists($target)) {
923 foreach ($segments as $segment) {
924 $elements = explode("*", $segment);
925 $ecnt = count($elements);
927 for ($i = 0; $i < $ecnt; $i++) {
928 $elements[$i] = text(trim($elements[$i]));
930 // Switch Case for Segment
931 switch ($elements[0]) {
933 $loop[1] = $elements[2];
934 $in['isa_sender_id'] = $elements[6];
935 $in['isa_receiver_id'] = $elements[8];
936 $in['isa_control_number'] = $elements[13];
940 $loop['id'] = (int)$elements[1];
941 $loop['parent'] = (int)$elements[2];
942 $loop['error'] = (int)$elements[4];
946 if ($loop['id'] === 1) {//"PR" payer
947 $in['payer_org'] = $elements[3];
948 $in['payer_member_id'] = $elements[9];
949 } elseif ($loop['id'] === 2) { //"1P" or "FA"
950 $in['provider_org'] = $elements[3];
951 $in['provider_member_id'] = $elements[9];
952 } elseif ($elements[1] == "IL" || $loop['context'] == "TRN") { //"IL"
953 $in['trace'] = $trace;
954 $in['subscriber_lname'] = $elements[3];
955 $in['subscriber_fname'] = $elements[4];
956 $in['subscriber_mname'] = $elements[5];
957 $in['subscriber_member_id'] = $elements[9];
958 $in['verify_date'] = date('Y/m/d H:i:s');
960 $loop['context'] = $elements[0];
964 if ($elements[1] == "D8") {
965 $in['subscriber_dob'] = $elements[2];
967 $in['subscriber_sex'] = $elements[3];
968 $loop['context'] = $elements[0];
969 // 2100A-C should be done so get our patient id.
970 if (!(int)$in['pid']) {
971 $in['pid'] = (int)getPatientMatch(
972 $in['subscriber_fname'],
973 $in['subscriber_lname'],
974 $in['subscriber_sex'],
975 $in['subscriber_dob']
983 break; // subscriber not set yet
987 $in['benefits'] = $benefits ? $benefits : [];
988 array_push($subscribers, $in);
989 $loop['context'] = $elements[0];
995 if ($elements[1] == "EJ") {
996 $in['pid'] = (int)$elements[2];
998 $in['pid'] = (int)getPatientMatch(
999 $in['subscriber_fname'],
1000 $in['subscriber_lname'],
1001 $in['subscriber_sex'],
1002 $in['subscriber_dob']
1009 if ($elements[2] == "D8") {
1010 $loop['start_date'] = $elements[3] ? date("Y-m-d", strtotime($elements[3])) : '';
1011 $loop['end_date'] = '';
1012 } elseif ($elements[2] == "RD8") {
1013 $tmp = explode('-', $elements[3]);
1014 $loop['start_date'] = $tmp[0] ? date("Y-m-d", strtotime($tmp[0])) : '';
1015 $loop['end_date'] = !empty($tmp[1]) ? date("Y-m-d", strtotime($tmp[1])) : '';
1017 if ($loop['context'] == "EB") {
1018 $bcnt = count($benefits) - 1;
1019 $benefits[$bcnt]['start_date'] = $loop['start_date'];
1020 $benefits[$bcnt]['end_date'] = $loop['end_date'];
1026 'type' => $elements[1],
1027 'benefit_type' => $codes->get_271_code("EB01", $elements[1]) ? $codes->get_271_code("EB01", $elements[1]) : $elements[1],
1030 'coverage_level' => $elements[2] ? $codes->get_271_code("EB02", $elements[2]) :$elements[2],
1031 'coverage_type' => $elements[3] ? $codes->get_271_code("EB03", $elements[3]) :$elements[3],
1032 'plan_type' => $elements[4] ? $codes->get_271_code("EB04", $elements[4]) : $elements[4],
1033 'plan_description' => $elements[5],
1034 'coverage_period' => $elements[6] ? $codes->get_271_code("EB06", $elements[6]) : $elements[6],
1035 'amount' => $elements[7] ? number_format($elements[7], 2, '.', '') : '',
1036 'percent' => $elements[8],
1037 'network_ind' => $elements[12] ? $codes->get_271_code("EB12", $elements[12]) : $elements[12],
1038 'message' => '' // any MSG segments that may be assoc with this EB.
1040 $loop['context'] = "EB";
1041 array_push($benefits, $eb);
1046 'request_ind' => $elements[1],
1047 'reason_code' => $elements[3] . " : " . $codes->get_271_code("AAA03", $elements[3]),
1048 'follow_up' => $elements[4] . " : " . $codes->get_271_code("AAA04", $elements[4])
1050 array_push($AAA, $error);
1054 $bcnt = count($benefits) - 1;
1056 $benefits[$bcnt]['message'] = $benefits[$bcnt]['message'] ? $benefits[$bcnt]['message'] . ":" : '';
1057 $benefits[$bcnt]['message'] .= $elements[1];
1062 $in['benefits'] = $benefits ? $benefits : [];
1064 array_push($subscribers, $in);
1066 $loop['context'] = $elements[0];
1073 if (count($subscribers) < 1) {
1074 $elog = xlt("Error") . ": " . xlt("Unknown Transaction Error Maybe Subscriber Effective or DOB Dates");
1076 foreach ($subscribers as $subscriber) {
1077 eligibility_verification_save($subscriber);
1082 // some debug logging
1083 if (!$GLOBALS['disable_eligibility_log']) {
1084 $log .= "*------------------- " . xlt("271 Returned") . " --------------------*\n" . $new . "\n" . (isset($AAA[0]) ? (xlt("AAA Segments") . ":\n" . print_r($AAA, true)) : "\n") . $elog;
1085 $log .= makeEligibilityReport($subscribers);
1093 function makeEligibilityReport($subscribers = [])
1096 foreach ($subscribers as $subscriber) {
1098 xlt("Subscriber Member") . ": " . $subscriber['subscriber_fname'] . " " . $subscriber['subscriber_lname'] . " " . $subscriber['subscriber_mname'] .
1099 " " .xlt("Member Id") . ": " . $subscriber['subscriber_member_id'] . " ---*\n";
1100 $cnt = count($subscriber['benefits']);
1102 $binfo .= "*** " . xlt("Nothing returned to report") . " ***\n";
1104 foreach ($subscriber['benefits'] as $key => $benefits) {
1105 foreach ($benefits as $key => $benefit) {
1106 if ($key == 'type') {
1109 if (empty($benefit) || $key == 'type') {
1112 $binfo .= "\t" . ucwords(str_replace('_', ' ', $key)) . ": " . $benefit . "\n";