a pretty comprehensive xml import module
[openemr.git] / library / gen_x12_837.inc.php
blob86e46f2021dcfd38fafbfc16db9c75ec6f23777b
1 <?php
2 // Copyright (C) 2007 Rod Roark <rod@sunsetsystems.com>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
9 require_once("Claim.class.php");
11 function gen_x12_837($pid, $encounter, &$log) {
13 $today = time();
14 $out = '';
15 $claim = new Claim($pid, $encounter);
16 $edicount = 0;
18 $log .= "Generating claim $pid-$encounter for " .
19 $claim->patientFirstName() . ' ' .
20 $claim->patientMiddleName() . ' ' .
21 $claim->patientLastName() . ' on ' .
22 date('Y-m-d H:i', $today) . ".\n";
24 $out .= "ISA" .
25 "*00" .
26 "* " .
27 "*00" .
28 "* " .
29 "*ZZ" .
30 "*" . $claim->x12gssenderid() .
31 "*ZZ" .
32 "*" . $claim->x12gsreceiverid() .
33 "*030911" .
34 "*1630" .
35 "*U" .
36 "*00401" .
37 "*000000001" .
38 "*0" .
39 "*P" .
40 "*:" .
41 "~\n";
43 $out .= "GS" .
44 "*HC" .
45 "*" . $claim->x12gssenderid() .
46 "*" . $claim->x12gsreceiverid() .
47 "*" . date('Ymd', $today) .
48 "*" . date('Hi', $today) .
49 "*1" .
50 "*X" .
51 "*" . $claim->x12gsversionstring() .
52 "~\n";
54 ++$edicount;
55 $out .= "ST" .
56 "*837" .
57 "*0021" .
58 "~\n";
60 ++$edicount;
61 $out .= "BHT" .
62 "*0019" .
63 "*00" .
64 "*0123" .
65 "*" . date('Ymd', $today) .
66 "*1023" .
67 "*CH" .
68 "~\n";
70 ++$edicount;
71 $out .= "REF" .
72 "*87" .
73 "*" . $claim->x12gsversionstring() .
74 "~\n";
76 ++$edicount;
77 $out .= "NM1" . // Loop 1000A Submitter
78 "*41" .
79 "*2" .
80 "*" . $claim->billingFacilityName() .
81 "*" .
82 "*" .
83 "*" .
84 "*" .
85 "*46" .
86 "*" . $claim->billingFacilityETIN() .
87 "~\n";
89 ++$edicount;
90 $out .= "PER" .
91 "*IC" .
92 "*" . $claim->billingContactName() .
93 "*TE" .
94 "*" . $claim->billingContactPhone() .
95 "~\n";
97 ++$edicount;
98 $out .= "NM1" . // Loop 1000B Receiver
99 "*40" .
100 "*2" .
101 "*" . $claim->clearingHouseName() .
102 "*" .
103 "*" .
104 "*" .
105 "*" .
106 "*46" .
107 "*" . $claim->clearingHouseETIN() .
108 "~\n";
110 $HLcount = 1;
112 ++$edicount;
113 $out .= "HL" . // Loop 2000A Billing/Pay-To Provider HL Loop
114 "*$HLcount" .
115 "*" .
116 "*20" .
117 "*1" .
118 "~\n";
120 $HLBillingPayToProvider = $HLcount++;
122 ++$edicount;
123 $out .= "NM1" . // Loop 2010AA Billing Provider
124 "*85" .
125 "*2" .
126 "*" . $claim->billingFacilityName() .
127 "*" .
128 "*" .
129 "*" .
130 "*";
131 if ($claim->billingFacilityNPI()) {
132 $out .= "*XX*" . $claim->billingFacilityNPI();
133 } else {
134 $log .= "*** Billing facility has no NPI.\n";
135 $out .= "*24*" . $claim->billingFacilityETIN();
137 $out .= "~\n";
139 ++$edicount;
140 $out .= "N3" .
141 "*" . $claim->billingFacilityStreet() .
142 "~\n";
144 ++$edicount;
145 $out .= "N4" .
146 "*" . $claim->billingFacilityCity() .
147 "*" . $claim->billingFacilityState() .
148 "*" . $claim->billingFacilityZip() .
149 "~\n";
151 // Add a REF*EI*<ein> segment if NPI was specified in the NM1 above.
153 if ($claim->billingFacilityNPI() && $claim->billingFacilityETIN()) {
154 ++$edicount;
155 $out .= "REF" .
156 "*EI" .
157 "*" . $claim->billingFacilityETIN() .
158 "~\n";
161 ++$edicount;
162 $out .= "REF" .
163 "*" . $claim->providerNumberType() .
164 "*" . $claim->providerNumber() .
165 "~\n";
167 ++$edicount;
168 $out .= "NM1" . // Loop 2010AB Pay-To Provider
169 "*87" .
170 "*2" .
171 "*" . $claim->billingFacilityName() .
172 "*" .
173 "*" .
174 "*" .
175 "*";
176 if ($claim->billingFacilityNPI())
177 $out .= "*XX*" . $claim->billingFacilityNPI();
178 else
179 $out .= "*24*" . $claim->billingFacilityETIN();
180 $out .= "~\n";
182 ++$edicount;
183 $out .= "N3" .
184 "*" . $claim->billingFacilityStreet() .
185 "~\n";
187 ++$edicount;
188 $out .= "N4" .
189 "*" . $claim->billingFacilityCity() .
190 "*" . $claim->billingFacilityState() .
191 "*" . $claim->billingFacilityZip() .
192 "~\n";
194 if ($claim->billingFacilityNPI() && $claim->billingFacilityETIN()) {
195 ++$edicount;
196 $out .= "REF" .
197 "*EI" .
198 "*" . $claim->billingFacilityETIN() .
199 "~\n";
202 $PatientHL = 0;
204 ++$edicount;
205 $out .= "HL" . // Loop 2000B Subscriber HL Loop
206 "*$HLcount" .
207 "*$HLBillingPayToProvider" .
208 "*22" .
209 "*$PatientHL" .
210 "~\n";
212 $HLSubscriber = $HLcount++;
214 ++$edicount;
215 $out .= "SBR" . // Subscriber Information
216 "*" . $claim->payerSequence() .
217 "*" . $claim->insuredRelationship() .
218 "*" . $claim->groupNumber() .
219 "*" . $claim->groupName() .
220 "*" . $claim->insuredTypeCode() . // applies for secondary medicare
221 "*" .
222 "*" .
223 "*" .
224 "*" . $claim->claimType() . // Zirmed replaces this
225 "~\n";
227 ++$edicount;
228 $out .= "NM1" . // Loop 2010BA Subscriber
229 "*IL" .
230 "*1" .
231 "*" . $claim->insuredLastName() .
232 "*" . $claim->insuredFirstName() .
233 "*" . $claim->insuredMiddleName() .
234 "*" .
235 "*" .
236 "*MI" .
237 "*" . $claim->policyNumber() .
238 "~\n";
240 ++$edicount;
241 $out .= "N3" .
242 "*" . $claim->insuredStreet() .
243 "~\n";
245 ++$edicount;
246 $out .= "N4" .
247 "*" . $claim->insuredCity() .
248 "*" . $claim->insuredState() .
249 "*" . $claim->insuredZip() .
250 "~\n";
252 ++$edicount;
253 $out .= "DMG" .
254 "*D8" .
255 "*" . $claim->insuredDOB() .
256 "*" . $claim->insuredSex() .
257 "~\n";
259 ++$edicount;
260 $out .= "NM1" . // Loop 2010BB Payer
261 "*PR" .
262 "*2" .
263 "*" . $claim->payerName() .
264 "*" .
265 "*" .
266 "*" .
267 "*" .
268 "*PI" .
269 "*" . $claim->payerID() . // Zirmed ignores this if using Payer Name Matching.
270 "~\n";
272 // if (!$claim->payerID()) {
273 // $log .= "*** CMS ID is missing for payer '" . $claim->payerName() . "'.\n";
274 // }
276 ++$edicount;
277 $out .= "N3" .
278 "*" . $claim->payerStreet() .
279 "~\n";
281 ++$edicount;
282 $out .= "N4" .
283 "*" . $claim->payerCity() .
284 "*" . $claim->payerState() .
285 "*" . $claim->payerZip() .
286 "~\n";
288 if (! $claim->isSelfOfInsured()) {
289 ++$edicount;
290 $out .= "HL" . // Loop 2000C Patient Information
291 "*$HLcount" .
292 "*$HLSubscriber" .
293 "*23" .
294 "*0" .
295 "~\n";
297 $HLcount++;
299 ++$edicount;
300 $out .= "PAT" .
301 "*" . $claim->insuredRelationship() .
302 "~\n";
304 ++$edicount;
305 $out .= "NM1" . // Loop 2010CA Patient
306 "*QC" .
307 "*1" .
308 "*" . $claim->patientLastName() .
309 "*" . $claim->patientFirstName() .
310 "*" . $claim->patientMiddleName() .
311 "~\n";
313 ++$edicount;
314 $out .= "N3" .
315 "*" . $claim->patientStreet() .
316 "~\n";
318 ++$edicount;
319 $out .= "N4" .
320 "*" . $claim->patientCity() .
321 "*" . $claim->patientState() .
322 "*" . $claim->patientZip() .
323 "~\n";
325 ++$edicount;
326 $out .= "DMG" .
327 "*D8" .
328 "*" . $claim->patientDOB() .
329 "*" . $claim->patientSex() .
330 "~\n";
331 } // end of patient different from insured
333 $proccount = $claim->procCount();
335 $clm_total_charges = 0;
336 for ($prockey = 0; $prockey < $proccount; ++$prockey) {
337 $clm_total_charges += $claim->cptCharges($prockey);
340 if (!$clm_total_charges) {
341 $log .= "*** This claim has no charges!\n";
344 ++$edicount;
345 $out .= "CLM" . // Loop 2300 Claim
346 "*$pid-$encounter" .
347 "*" . sprintf("%.2f",$clm_total_charges) . // Zirmed computes and replaces this
348 "*" .
349 "*" .
350 "*" . $claim->facilityPOS() . "::1" .
351 "*Y" .
352 "*A" .
353 "*Y" .
354 "*Y" .
355 "*C" .
356 "~\n";
358 ++$edicount;
359 $out .= "DTP" . // Date of Onset
360 "*431" .
361 "*D8" .
362 "*" . $claim->onsetDate() .
363 "~\n";
365 if (strcmp($claim->facilityPOS(),'21') == 0) {
366 ++$edicount;
367 $out .= "DTP" . // Date of Hospitalization
368 "*435" .
369 "*D8" .
370 "*" . $claim->onsetDate() .
371 "~\n";
374 $patientpaid = $claim->patientPaidAmount();
375 if ($patientpaid != 0) {
376 ++$edicount;
377 $out .= "AMT" . // Patient paid amount. Page 220.
378 "*F5" .
379 "*" . $patientpaid .
380 "~\n";
383 if ($claim->priorAuth()) {
384 ++$edicount;
385 $out .= "REF" . // Prior Authorization Number
386 "*G1" .
387 "*" . $claim->priorAuth() .
388 "~\n";
391 if ($claim->cliaCode()) {
392 // Required by Medicare when in-house labs are done.
393 ++$edicount;
394 $out .= "REF" . // Clinical Laboratory Improvement Amendment Number
395 "*X4" .
396 "*" . $claim->cliaCode() .
397 "~\n";
400 $da = $claim->diagArray();
401 ++$edicount;
402 $out .= "HI"; // Health Diagnosis Codes
403 $diag_type_code = 'BK';
404 foreach ($da as $diag) {
405 $out .= "*$diag_type_code:" . $diag;
406 $diag_type_code = 'BF';
408 $out .= "~\n";
410 if ($claim->referrerLastName()) {
411 // Medicare requires referring provider's name and UPIN.
412 ++$edicount;
413 $out .= "NM1" . // Loop 2310A Referring Provider
414 "*DN" .
415 "*1" .
416 "*" . $claim->referrerLastName() .
417 "*" . $claim->referrerFirstName() .
418 "*" . $claim->referrerMiddleName() .
419 "*" .
420 "*";
421 if ($claim->referrerNPI()) { $out .=
422 "*XX" .
423 "*" . $claim->referrerNPI();
424 } else { $out .=
425 "*34" .
426 "*" . $claim->referrerSSN();
428 $out .= "~\n";
430 ++$edicount;
431 $out .= "REF" . // Referring Provider Secondary Identification
432 "*1G" .
433 "*" . $claim->referrerUPIN() .
434 "~\n";
437 ++$edicount;
438 $out .= "NM1" . // Loop 2310B Rendering Provider
439 "*82" .
440 "*1" .
441 "*" . $claim->providerLastName() .
442 "*" . $claim->providerFirstName() .
443 "*" . $claim->providerMiddleName() .
444 "*" .
445 "*";
446 if ($claim->providerNPI()) { $out .=
447 "*XX" .
448 "*" . $claim->providerNPI();
449 } else { $out .=
450 "*34" .
451 "*" . $claim->providerSSN();
452 $log .= "*** Rendering provider has no NPI.\n";
454 $out .= "~\n";
456 ++$edicount;
457 $out .= "PRV" . // Rendering Provider Information
458 "*PE" .
459 "*ZZ" .
460 "*207Q00000X" .
461 "~\n";
463 ++$edicount;
464 $out .= "NM1" . // Loop 2310B Service Location
465 "*77" .
466 "*2" .
467 "*" . $claim->facilityName() .
468 "*" .
469 "*" .
470 "*" .
471 "*";
472 if ($claim->facilityNPI()) { $out .=
473 "*XX*" . $claim->facilityNPI();
474 } else { $out .=
475 "*24*" . $claim->facilityETIN();
476 $log .= "*** Service location has no NPI.\n";
478 $out .= "~\n";
480 ++$edicount;
481 $out .= "N3" .
482 "*" . $claim->facilityStreet() .
483 "~\n";
485 ++$edicount;
486 $out .= "N4" .
487 "*" . $claim->facilityCity() .
488 "*" . $claim->facilityState() .
489 "*" . $claim->facilityZip() .
490 "~\n";
492 $prev_pt_resp = $clm_total_charges; // for computation below
494 // Loops 2320 and 2330*, other subscriber/payer information.
496 for ($ins = 1; $ins < $claim->payerCount(); ++$ins) {
498 ++$edicount;
499 $out .= "SBR" . // Loop 2320, Subscriber Information - page 318
500 "*" . $claim->payerSequence($ins) .
501 "*" . $claim->insuredRelationship($ins) .
502 "*" . $claim->groupNumber($ins) .
503 "*" . $claim->groupName($ins) .
504 "*" .
505 "*" .
506 "*" .
507 "*" .
508 "*" . $claim->claimType($ins) .
509 "~\n";
511 // Things that apply only to previous payers, not future payers.
513 if ($claim->payerSequence($ins) < $claim->payerSequence()) {
515 // Generate claim-level adjustments.
516 $aarr = $claim->payerAdjustments($ins);
517 foreach ($aarr as $a) {
518 ++$edicount;
519 $out .= "CAS" . // Previous payer's claim-level adjustments. Page 323.
520 "*" . $a[1] .
521 "*" . $a[2] .
522 "*" . $a[3] .
523 "~\n";
526 $payerpaid = $claim->payerTotals($ins);
527 ++$edicount;
528 $out .= "AMT" . // Previous payer's paid amount. Page 332.
529 "*D" .
530 "*" . $payerpaid[1] .
531 "~\n";
533 // Patient responsibility amount as of this previous payer.
534 $prev_pt_resp -= $payerpaid[1]; // reduce by payments
535 $prev_pt_resp -= $payerpaid[2]; // reduce by adjustments
537 ++$edicount;
538 $out .= "AMT" . // Patient responsibility amount per previous payer. Page 335.
539 "*F2" .
540 "*" . sprintf('%.2f', $prev_pt_resp) .
541 "~\n";
543 } // End of things that apply only to previous payers.
545 ++$edicount;
546 $out .= "DMG" . // Other subscriber demographic information. Page 342.
547 "*D8" .
548 "*" . $claim->insuredDOB($ins) .
549 "*" . $claim->insuredSex($ins) .
550 "~\n";
552 ++$edicount;
553 $out .= "OI" . // Other Insurance Coverage Information. Page 344.
554 "*" .
555 "*" .
556 "*Y" .
557 "*B" .
558 "*" .
559 "*Y" .
560 "~\n";
562 ++$edicount;
563 $out .= "NM1" . // Loop 2330A Subscriber info for other insco. Page 350.
564 "*IL" .
565 "*1" .
566 "*" . $claim->insuredLastName($ins) .
567 "*" . $claim->insuredFirstName($ins) .
568 "*" . $claim->insuredMiddleName($ins) .
569 "*" .
570 "*" .
571 "*MI" .
572 "*" . $claim->policyNumber($ins) .
573 "~\n";
575 ++$edicount;
576 $out .= "NM1" . // Loop 2330B Payer info for other insco. Page 359.
577 "*PR" .
578 "*2" .
579 "*" . $claim->payerName($ins) .
580 "*" .
581 "*" .
582 "*" .
583 "*" .
584 "*PI" .
585 "*" . $claim->payerID($ins) .
586 "~\n";
588 // if (!$claim->payerID($ins)) {
589 // $log .= "*** CMS ID is missing for payer '" . $claim->payerName($ins) . "'.\n";
590 // }
592 } // End loops 2320/2330*.
594 $loopcount = 0;
596 // Procedure loop starts here.
598 for ($prockey = 0; $prockey < $proccount; ++$prockey) {
599 ++$loopcount;
601 ++$edicount;
602 $out .= "LX" . // Loop 2400 LX Service Line. Page 398.
603 "*$loopcount" .
604 "~\n";
606 ++$edicount;
607 $out .= "SV1" . // Professional Service. Page 400.
608 "*HC:" . $claim->cptKey($prockey) .
609 "*" . sprintf('%.2f', $claim->cptCharges($prockey)) .
610 "*UN" .
611 "*" . $claim->cptUnits($prockey) .
612 "*" .
613 "*" .
614 "*" . $claim->diagIndex($prockey) .
615 "~\n";
617 if (!$claim->cptCharges($prockey)) {
618 $log .= "*** Procedure '" . $claim->cptKey($prockey) . "' has no charges!\n";
621 if (!$claim->diagIndex($prockey)) {
622 $log .= "*** Procedure '" . $claim->cptKey($prockey) . "' is not justified!\n";
625 ++$edicount;
626 $out .= "DTP" . // Date of Service. Page 435.
627 "*472" .
628 "*D8" .
629 "*" . $claim->serviceDate() .
630 "~\n";
632 // Loop 2410, Drug Information. Medicaid insurers seem to want this
633 // with HCPCS codes.
635 $ndc = $claim->cptNDCID($prockey);
636 if ($ndc) {
637 ++$edicount;
638 $out .= "LIN" . // Drug Identification. Page 500+ (Addendum pg 71).
639 "*4" .
640 "*N4" .
641 "*" . $ndc .
642 "~\n";
644 if (!preg_match('/^\d\d\d\d\d-\d\d\d\d-\d\d$/', $ndc, $tmp)) {
645 $log .= "*** NDC code '$ndc' has invalid format!\n";
648 ++$edicount;
649 $out .= "CTP" . // Drug Pricing. Page 500+ (Addendum pg 74).
650 "*" .
651 "*" .
652 "*0" . // dummy price, required by HIPAA
653 "*" . $claim->cptNDCQuantity($prockey) .
654 "*" . $claim->cptNDCUOM($prockey) .
655 "~\n";
658 // Loop 2430, adjudication by previous payers.
660 for ($ins = 1; $ins < $claim->payerCount(); ++$ins) {
661 if ($claim->payerSequence($ins) > $claim->payerSequence())
662 continue; // payer is future, not previous
664 $payerpaid = $claim->payerTotals($ins, $claim->cptKey($prockey));
665 $aarr = $claim->payerAdjustments($ins, $claim->cptKey($prockey));
667 if ($payerpaid[1] == 0 && !count($aarr)) {
668 $log .= "*** Procedure '" . $claim->cptKey($prockey) .
669 "' has no payments or adjustments from previous payer!\n";
670 continue;
673 ++$edicount;
674 $out .= "SVD" . // Service line adjudication. Page 554.
675 "*" . $claim->payerID($ins) .
676 "*" . $payerpaid[1] .
677 "*HC:" . $claim->cptKey($prockey) .
678 "*" .
679 "*" . $claim->cptUnits($prockey) .
680 "~\n";
682 $tmpdate = $payerpaid[0];
683 foreach ($aarr as $a) {
684 ++$edicount;
685 $out .= "CAS" . // Previous payer's line level adjustments. Page 558.
686 "*" . $a[1] .
687 "*" . $a[2] .
688 "*" . $a[3] .
689 "~\n";
690 if (!$tmpdate) $tmpdate = $a[0];
693 if ($tmpdate) {
694 ++$edicount;
695 $out .= "DTP" . // Previous payer's line adjustment date. Page 566.
696 "*573" .
697 "*D8" .
698 "*$tmpdate" .
699 "~\n";
701 } // end loop 2430
702 } // end this procedure
704 ++$edicount;
705 $out .= "SE" . // SE Trailer
706 "*$edicount" .
707 "*0021" .
708 "~\n";
710 $out .= "GE" . // GE Trailer
711 "*1" .
712 "*1" .
713 "~\n";
715 $out .= "IEA" . // IEA Trailer
716 "*1" .
717 "*000000001" .
718 "~\n";
720 $log .= "\n";
721 return $out;