added days of week to reccurence widget (#286)
[openemr.git] / library / edihistory / test_edih_835_accounting.php
blob703641b9cc5c0fc0487a0d9b5c9c8f0f7dba2c65
1 <?php
2 /*
3 * test_edih_835_accounting.php
4 *
5 * Copyright 2016 Kevin McCormick <kevin@kt61p>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
25 function edih_835_accounting($segments, $delimiters) {
26 // accounting information is in
27 // BPR TRN CLP SVC PLB
28 /*****
31 *$out['check_number'] = trim($seg[2]); TRN
32 *$out['payer_tax_id'] = substr($seg[3], 1); // 9 digits
33 *$out['payer_id'] = trim($seg[4]);
34 *$out['production_date'] = trim($seg[2]); DTM 405
35 *$out['payer_name'] = trim($seg[2]); N1 loop 1000A
36 * $out['payer_street'] = trim($seg[1]); N3
37 * $out['payer_city'] = trim($seg[1]); N4
38 * $out['payer_state'] = trim($seg[2]);
39 * $out['payer_zip'] = trim($seg[3]);
40 * $out['payee_name'] = trim($seg[2]); N1 loop 1000B
41 *$out['payee_street'] = trim($seg[1]);
42 * $out['payee_city'] = trim($seg[1]);
43 * $out['payee_state'] = trim($seg[2]);
44 *$out['payee_zip'] = trim($seg[3]);
45 * CLP segment
46 * // Clear some stuff to start the new claim:
47 $out['subscriber_lname'] = '';
48 $out['subscriber_fname'] = '';
49 $out['subscriber_mname'] = '';
50 $out['subscriber_member_id'] = '';
51 $out['crossover']=0;
52 $out['svc'] = array();
53 * $out['our_claim_id'] = trim($seg[1]);
54 *$out['claim_status_code'] = trim($seg[2]);
55 *$out['amount_charged'] = trim($seg[3]);
56 * $out['amount_approved'] = trim($seg[4]);
57 * $out['amount_patient'] = trim($seg[5]); // pt responsibility, copay + deductible
58 * $out['payer_claim_id'] = trim($seg[7]); // payer's claim number
60 * else if ($segid == 'CAS' && $out['loopid'] == '2100') {
61 // This is a claim-level adjustment and should be unusual.
62 // Handle it by creating a dummy zero-charge service item and
63 // then populating the adjustments into it. See also code in
64 // parse_era_2100() which will later plug in a payment reversal
65 // amount that offsets these adjustments.
66 $i = 0; // if present, the dummy service item will be first.
67 if (!$out['svc'][$i]) {
68 $out['svc'][$i] = array();
69 $out['svc'][$i]['code'] = 'Claim';
70 $out['svc'][$i]['mod'] = '';
71 $out['svc'][$i]['chg'] = '0';
72 $out['svc'][$i]['paid'] = '0';
73 $out['svc'][$i]['adj'] = array();
75 for ($k = 2; $k < 20; $k += 3) {
76 if (!$seg[$k]) break;
77 $j = count($out['svc'][$i]['adj']);
78 $out['svc'][$i]['adj'][$j] = array();
79 $out['svc'][$i]['adj'][$j]['group_code'] = $seg[1];
80 $out['svc'][$i]['adj'][$j]['reason_code'] = $seg[$k];
81 $out['svc'][$i]['adj'][$j]['amount'] = $seg[$k+1];
85 * // QC = Patient
86 else if ($segid == 'NM1' && $seg[1] == 'QC' && $out['loopid'] == '2100') {
87 $out['patient_lname'] = trim($seg[3]);
88 $out['patient_fname'] = trim($seg[4]);
89 $out['patient_mname'] = trim($seg[5]);
90 $out['patient_member_id'] = trim($seg[9]);
92 // IL = Insured or Subscriber
93 else if ($segid == 'NM1' && $seg[1] == 'IL' && $out['loopid'] == '2100') {
94 $out['subscriber_lname'] = trim($seg[3]);
95 $out['subscriber_fname'] = trim($seg[4]);
96 $out['subscriber_mname'] = trim($seg[5]);
97 $out['subscriber_member_id'] = trim($seg[9]);
99 // 82 = Rendering Provider
100 else if ($segid == 'NM1' && $seg[1] == '82' && $out['loopid'] == '2100') {
101 $out['provider_lname'] = trim($seg[3]);
102 $out['provider_fname'] = trim($seg[4]);
103 $out['provider_mname'] = trim($seg[5]);
104 $out['provider_member_id'] = trim($seg[9]);
106 else if ($segid == 'NM1' && $seg[1] == 'TT' && $out['loopid'] == '2100') {
107 $out['crossover'] = 1;//Claim automatic forward case.
111 * else if ($segid == 'REF' && $seg[1] == '1W' && $out['loopid'] == '2100') {
112 $out['claim_comment'] = trim($seg[2]);
115 * else if ($segid == 'DTM' && $seg[1] == '050' && $out['loopid'] == '2100') {
116 $out['claim_date'] = trim($seg[2]); // yyyymmdd
119 * else if ($segid == 'PER' && $out['loopid'] == '2100') {
121 $out['payer_insurance'] = trim($seg[2]);
122 $out['warnings'] .= 'Claim contact information: ' .
123 $seg[4] . "\n";
126 * else if ($segid == 'SVC') {
127 if (! $out['loopid']) return 'Unexpected SVC segment';
128 * $out['loopid'] = '2110';
129 if ($seg[6]) {
130 // SVC06 if present is our original procedure code that they are changing.
131 // We will not put their crap in our invoice, but rather log a note and
132 // treat it as adjustments to our originally submitted coding.
133 $svc = explode($delimiter3, $seg[6]);
134 $tmp = explode($delimiter3, $seg[1]);
135 $out['warnings'] .= "Payer is restating our procedure " . $svc[1] .
136 " as " . $tmp[1] . ".\n";
137 } else {
138 $svc = explode($delimiter3, $seg[1]);
140 if ($svc[0] != 'HC') return 'SVC segment has unexpected qualifier';
141 // TBD: Other qualifiers are possible; see IG pages 140-141.
142 $i = count($out['svc']);
143 $out['svc'][$i] = array();
145 * // It seems some payers append the modifier with no separator!
146 if (strlen($svc[1]) == 7 && empty($svc[2])) {
147 $out['svc'][$i]['code'] = substr($svc[1], 0, 5);
148 $out['svc'][$i]['mod'] = substr($svc[1], 5);
149 } else {
150 $out['svc'][$i]['code'] = $svc[1];
151 $out['svc'][$i]['mod'] = $svc[2] ? $svc[2] . ':' : '';
152 $out['svc'][$i]['mod'] .= $svc[3] ? $svc[3] . ':' : '';
153 $out['svc'][$i]['mod'] .= $svc[4] ? $svc[4] . ':' : '';
154 $out['svc'][$i]['mod'] .= $svc[5] ? $svc[5] . ':' : '';
155 $out['svc'][$i]['mod'] = preg_replace('/:$/','',$out['svc'][$i]['mod']);
157 $out['svc'][$i]['chg'] = $seg[2];
158 $out['svc'][$i]['paid'] = $seg[3];
159 $out['svc'][$i]['adj'] = array();
160 // Note: SVC05, if present, indicates the paid units of service.
161 // It defaults to 1.
163 * // DTM01 identifies the type of service date:
164 // 472 = a single date of service
165 // 150 = service period start
166 // 151 = service period end
167 else if ($segid == 'DTM' && $out['loopid'] == '2110') {
168 $out['dos'] = trim($seg[2]); // yyyymmdd
170 else if ($segid == 'CAS' && $out['loopid'] == '2110') {
171 $i = count($out['svc']) - 1;
172 for ($k = 2; $k < 20; $k += 3) {
173 if (!$seg[$k]) break;
174 if ($seg[1] == 'CO' && $seg[$k+1] < 0) {
175 $out['warnings'] .= "Negative Contractual Obligation adjustment " .
176 "seems wrong. Inverting, but should be checked!\n";
177 $seg[$k+1] = 0 - $seg[$k+1];
179 $j = count($out['svc'][$i]['adj']);
180 $out['svc'][$i]['adj'][$j] = array();
181 $out['svc'][$i]['adj'][$j]['group_code'] = $seg[1];
182 $out['svc'][$i]['adj'][$j]['reason_code'] = $seg[$k];
183 $out['svc'][$i]['adj'][$j]['amount'] = $seg[$k+1];
184 // Note: $seg[$k+2] is "quantity". A value here indicates a change to
185 // the number of units of service. We're ignoring that for now.
188 *else if ($segid == 'LQ' && $seg[1] == 'HE' && $out['loopid'] == '2110') {
189 $i = count($out['svc']) - 1;
190 $out['svc'][$i]['remark'] = $seg[2];
194 * else if ($segid == 'PLB') {
195 // Provider-level adjustments are a General Ledger thing and should not
196 // alter the A/R for the claim, so we just report them as notes.
197 for ($k = 3; $k < 15; $k += 2) {
198 if (!$seg[$k]) break;
199 $out['warnings'] .= 'PROVIDER LEVEL ADJUSTMENT (not claim-specific): $' .
200 sprintf('%.2f', $seg[$k+1]) . " with reason code " . $seg[$k] . "\n";
201 // Note: For PLB adjustment reason codes see IG pages 165-170.
204 else if ($segid == 'SE') {
205 parse_era_2100($out, $cb);
206 $out['loopid'] = '';
207 if ($out['st_control_number'] != trim($seg[2])) {
208 return 'Ending transaction set control number mismatch';
210 if (($out['st_segment_count'] + 1) != trim($seg[1])) {
211 return 'Ending transaction set segment count mismatch';
220 if (is_array($segments) && count($segments) {
221 $acct = array();
222 } else {
223 csv_edihist_log("edih_835_accounting: invalid segments argument");
224 return "835 accounting: invalid segments argument";
226 foreach($segments as $seg) {
227 if ( strncmp('GS'.$de, $seg, 3) === 0 ) {
228 $sar = explode($de, $seg);
229 $gs_date = (isset($sar[4]) && $sar[4]) ? trim($sar[4]) : '';
231 if ( strncmp('BPR'.$de, $seg, 4) === 0 ) {
232 $sar = explode($de, $seg);
233 $check_amount = (isset($sar[2]) && $sar[2]) ? trim($sar[2]) : '';
234 $check_date = (isset($sar[16]) && $sar[16]) ? trim($sar[16]) : '';
236 if ( strncmp('TRN'.$de, $seg, 4) === 0 ) {
237 $sar = explode($de, $seg);
238 $ck = (isset($sar[2]) && $sar[2]) ? trim($sar[2]) : count($out);
239 $out[$ck]['gs_date'] = $gs_date
240 $out[$ck]['check_amount'] = $check_amount;
241 $out[$ck]['check_date'] = $check_date;
242 $out[$ck]['check_number'] = (isset($sar[2]) && $sar[2]) ? trim($sar[2]) : '';
244 if ( strncmp('LX'.$de, $seg, 3) === 0 ) {
246 if ( strncmp('CLP'.$de, $seg, 4) === 0 ) {
247 $sar = explode($de, $seg);
248 $loopid = '2100';
250 $i = (isset($out[$ck]['clp'])) ? count($out[$ck]['clp']) : 0;
252 $out[$ck]['loopid'] = '2100';
253 $out[$ck]['warnings'] = '';
254 // Clear some stuff to start the new claim:
255 $out[$ck]['clp'][$i]['subscriber_lname'] = '';
256 $out[$ck]['clp'][$i]['subscriber_fname'] = '';
257 $out[$ck]['clp'][$i]['subscriber_mname'] = '';
258 $out[$ck]['clp'][$i]['subscriber_member_id'] = '';
259 $out[$ck]['clp'][$i]['crossover']=0;
260 $out[$ck]['clp'][$i]['svc'] = array();
262 // This is the poorly-named "Patient Account Number". For 837p
263 // it comes from CLM01 which we populated as pid-diagid-procid,
264 // where diagid and procid are id values from the billing table.
265 // For HCFA 1500 claims it comes from field 26 which we
266 // populated with our familiar pid-encounter billing key.
268 // The 835 spec calls this the "provider-assigned claim control
269 // number" and notes that it is specifically intended for
270 // identifying the claim in the provider's database.
271 $out[$ck]['clp'][$i][['our_claim_id'] = (isset($sar[1]) && $sar[1]) ? trim($sar[1]) : "";
273 $out[$ck]['clp'][$i]['claim_status_code'] = (isset($sar[2]) && $sar[2]) ? trim($sar[2]) : "";
274 $out[$ck]['clp'][$i]['amount_charged'] = (isset($sar[3]) && $sar[3]) ? trim($sar[3]) : "";
275 $out[$ck]['clp'][$i]['amount_approved'] = (isset($sar[4]) && $sar[4]) ? trim($sar[4]) : "";
276 $out[$ck]['clp'][$i]['amount_patient'] = (isset($sar[5]) && $sar[5]) ? trim($sar[5]) : ""; // pt responsibility, copay + deductible
277 $out[$ck]['clp'][$i]['payer_claim_id'] = (isset($sar[7]) && $sar[7]) ? trim($sar[7]) : ""; // payer's claim number
280 if ( strncmp('CAS'.$de, $seg, 4) === 0 ) {
281 $sar = explode($de, $seg);
282 if ($loop == '2100') {
284 // This is a claim-level adjustment and should be unusual.
285 // Handle it by creating a dummy zero-charge service item and
286 // then populating the adjustments into it. See also code in
287 // parse_era_2100() which will later plug in a payment reversal
288 // amount that offsets these adjustments.
289 $j = 0; // if present, the dummy service item will be first.
290 if (!$out['svc'][$j]) {
291 $out[$ck]['clp'][$i]['svc'][$j] = array();
292 $out[$ck]['clp'][$i]['svc'][$j]['code'] = 'Claim';
293 $out[$ck]['clp'][$i]['svc'][$j]['mod'] = '';
294 $out[$ck]['clp'][$i]['svc'][$j]['chg'] = '0';
295 $out[$ck]['clp'][$i]['svc'][$j]['paid'] = '0';
296 $out[$ck]['clp'][$i]['svc'][$j]['adj'] = array();
298 for ($k = 2; $k < 20; $k += 3) {
299 if (!isset($sar[$k])) break;
300 $k = count($out['svc'][$j]['adj']);
301 $out[$ck]['clp'][$i]['svc'][$j]['adj'][$k] = array();
302 $out[$ck]['clp'][$i]['svc'][$j]['adj'][$k]['group_code'] = $sar[1];
303 $out[$ck]['clp'][$i]['svc'][$j]['adj'][$k]['reason_code'] = $sar[$k];
304 $out[$ck]['clp'][$i]['svc'][$j]['adj'][$k]['amount'] = $sar[$k+1];
306 } elseif ($loopid == '2110') {
307 $sar = explode($de, $seg);
308 $j = count($out[$ck]['clp'][$i]['svc']);
309 $out[$ck]['clp'][$i]['svc'][$j] = array();
310 if (! $out['loopid']) return 'Unexpected SVC segment';
312 if (isset($sar[6]) && $sar[6]) {
313 // SVC06 if present is our original procedure code that they are changing.
314 // We will not put their crap in our invoice, but rather log a note and
315 // treat it as adjustments to our originally submitted coding.
316 $svc = explode($ds, $sar[6]);
317 $tmp = (isset($sar[1]) && $sar[1]) ? explode($ds, $sar[1]): "";
318 $out[$ck]['clp'][$i]['warnings'] .= "Submitted procedure modified " . $svc[1] .
319 " as " . $tmp[1] . ".\n";
320 } else {
321 $svc = explode($delimiter3, $seg[1]);
323 if ($svc[0] != 'HC') return 'SVC segment has unexpected qualifier';
324 // TBD: Other qualifiers are possible; see IG pages 140-141.
325 $j = count($out[$ck]['clp'][$i]['svc']);
326 $out['svc'][$j] = array();
327 // It seems some payers append the modifier with no separator!
328 if (strlen($svc[1]) == 7 && empty($svc[2])) {
329 $out['svc'][$j]['code'] = substr($svc[1], 0, 5);
330 $out['svc'][$j]['mod'] = substr($svc[1], 5);
331 } else {
332 $out['svc'][$j]['code'] = $svc[1];
333 $out['svc'][$j]['mod'] = $svc[2] ? $svc[2] . ':' : '';
334 $out['svc'][$j]['mod'] .= $svc[3] ? $svc[3] . ':' : '';
335 $out['svc'][$j]['mod'] .= $svc[4] ? $svc[4] . ':' : '';
336 $out['svc'][$j]['mod'] .= $svc[5] ? $svc[5] . ':' : '';
337 $out['svc'][$j]['mod'] = preg_replace('/:$/','',$out['svc'][$j]['mod']);
339 $out['svc'][$j]['chg'] = $seg[2];
340 $out['svc'][$j]['paid'] = $seg[3];
341 $out['svc'][$j]['adj'] = array();
342 // Note: SVC05, if present, indicates the paid units of service.
343 // It defaults to 1.
345 } elseif ( strncmp('NM1'.$de, $seg, 4) === 0 ) {
346 $sar = explode($de, $seg);
347 $id = (isset($sar[1]) && $sar[1]) ? trim($sar[1]) : "";
348 if ($id == 'QC') {
349 // QC Patient
350 $out[$ck]['clp'][$i]['patient_lname'] = (isset($sar[3]) && $sar[3]) ? trim($sar[3]) : "";
351 $out[$ck]['clp'][$i]['patient_fname'] = (isset($sar[4]) && $sar[4]) ? trim($sar[4]) : "";
352 $out[$ck]['clp'][$i]['patient_mname'] = (isset($sar[5]) && $sar[5]) ? trim($sar[5]) : "";
353 $out[$ck]['clp'][$i]['patient_member_id'] = (isset($sar[9]) && $sar[9]) ? trim($sar[9]) : "";
354 } elseif ($id == 'IL') {
355 // IL = Insured or Subscriber
356 $out[$ck]['clp'][$i]['subscriber_lname'] = (isset($sar[3]) && $sar[3]) ? trim($sar[3]) : "";
357 $out[$ck]['clp'][$i]['subscriber_fname'] = (isset($sar[4]) && $sar[4]) ? trim($sar[4]) : "";
358 $out[$ck]['clp'][$i]['subscriber_mname'] = (isset($sar[5]) && $sar[5]) ? trim($sar[5]) : "";
359 $out[$ck]['clp'][$i]['subscriber_member_id'] = (isset($sar[9]) && $sar[9]) ? trim($sar[9]) : "";
360 } elseif ($id == '82') {
361 // 82 = Rendering Provider
362 $out[$ck]['clp'][$i]['provider_lname'] = (isset($sar[3]) && $sar[3]) ? trim($sar[3]) : "";
363 $out[$ck]['clp'][$i]['provider_fname'] = (isset($sar[4]) && $sar[4]) ? trim($sar[4]) : "";
364 $out[$ck]['clp'][$i]['provider_mname'] = (isset($sar[5]) && $sar[5]) ? trim($sar[5]) : "";
365 $out[$ck]['clp'][$i]['provider_member_id'] = (isset($sar[9]) && $sar[9]) ? trim($sar[9]) : "";
366 } elseif ($id =='TT') {
367 //Claim automatic forward case.
368 $out[$ck]['clp'][$i]['crossover'] = 1;
370 } elseif (strncmp('PER'.$de, $seg, 4) === 0 ) ($segid == 'PER' && $out['loopid'] == '2100') {
371 $sar = explode($de, $seg);
372 $out['payer_insurance'] = trim($seg[2]);
373 $out['warnings'] .= 'Claim contact information: '.$seg[4];
374 } elseif (strncmp('PLB'.$de, $seg, 4) === 0 ) {
375 $sar = explode($de, $seg);
376 $p = (isset($out[$ck]['plb'])) ? count($out[$ck]['plb']) : 0;
377 $q = 0;
379 $out[$ck]['plb'][$p] = array();
380 $out[$ck]['plb']['adj'] = array();
381 $out[$ck]['plb'][$p]['provider'] = (isset($sar[1]) && $sar[1]) ? trim($sar[1]) : "";
382 $out[$ck]['plb'][$p]['fye'] = (isset($sar[2]) && $sar[2]) ? trim($sar[2]) : "";
383 // PLB02 is provider fiscal year end or CCYY1231
385 $plbar = array_slice($sar, 3);
386 foreach($plbar as $ky=>$plb) {
387 switch($ky % 2) {
388 // PLB04 06 08 ...
389 case 0:
390 $out[$ck]['clp'][$p]['adj'][$q]['amt'] = $plb;
391 break;
392 // PLB03 05 07 ...
393 case 1:
394 if (strpos($plb, $ds)) {
395 $plb02 = explode($ds, $plb);
396 $out[$ck]['per'][$p]['adj'][$q]['code'] = $plb02[0];
397 $out[$ck]['per'][$p]['adj'][$q]['ref'] = $plb02[1];
398 } else {
399 $out[$ck]['per'][$p]['adj'][$q]['code'] = $plb;
400 $out[$ck]['per'][$p]['adj'][$q]['ref'] = '';
402 $q++;
403 break;
407 .= (isset($sar[3]) && $sar[3]) ? trim($sar[3]) : '0';
410 if ( strncmp('SVC'.$de, $seg, 4) === 0 ) {
411 $loopid = '2110';
415 $acctng['lx'][$lx01] = array('ts3amt'=>0, 'fee'=>0, 'clmpmt'=>0, 'clmadj'=>0, 'prvadj'=>0, 'ptrsp'=>0);
416 if ($chk) { $acctng['pmt'] = $bpr02; }
418 // try a little accounting
419 if ($chk) {
420 if ( $acctng['pmt'] == ($acctng['clmpmt'] + $acctng['prvadj']) ) {
421 $bal = 'Balanced';
422 } else {
423 $bal = 'Not Balanced';
425 $pmt_html .= "<tr class='pmt'><td colspan=4>Accounting $bal</td></tr>".PHP_EOL;
426 $pmt_html .= "<tr class='pmt'><td>Fee {$acctng['fee']}</td><td>Adj {$acctng['clmadj']}</td><td>PtRsp {$acctng['ptrsp']}</td></tr>".PHP_EOL;
427 $pmt_html .= "<tr class='pmt'><td>PMT {$acctng['pmt']}</td><td>CLP {$acctng['clmpmt']}</td><td>PLB {$acctng['prvadj']}</td></tr>".PHP_EOL;