From 76065cc1a912511083751bc166d824b32b3e531b Mon Sep 17 00:00:00 2001 From: stephen waite Date: Thu, 9 Mar 2023 08:08:52 -0500 Subject: [PATCH] fix: line item adjustments for secondary claims (#6269) * fix: line item adjustments for secondary claims * log invalid zips but output anyways * rename --- src/Billing/Claim.php | 52 ++++++++++++++++++++++------ src/Billing/X125010837P.php | 84 ++++++++++++++++++++------------------------- 2 files changed, 79 insertions(+), 57 deletions(-) diff --git a/src/Billing/Claim.php b/src/Billing/Claim.php index 2e0245989..d073e47ac 100644 --- a/src/Billing/Claim.php +++ b/src/Billing/Claim.php @@ -6,7 +6,7 @@ * @author Rod Roark * @author Stephen Waite * @copyright Copyright (c) 2009-2020 Rod Roark - * @copyright Copyright (c) 2017 Stephen Waite + * @copyright Copyright (c) 2017-2023 Stephen Waite * @link https://github.com/openemr/openemr/tree/master * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 */ @@ -48,6 +48,7 @@ class Claim public $pay_to_provider; // to be implemented in facility ui private $encounterService; public $billing_prov_id; + public $line_item_adjs; // adjustment array with key of [group code][reason code] needed for secondary claims public function __construct($pid, $encounter_id) { @@ -365,33 +366,33 @@ class Claim $date = $tmp; } - if ($tmp && $value['pmt'] == 0) { // not original charge and not a payment + if ($tmp && (($value['pmt'] ?? null) == 0)) { // not original charge and not a payment $rsn = $value['rsn']; $chg = 0 - $value['chg']; // adjustments are negative charges $gcode = 'CO'; // default group code = contractual obligation $rcode = '45'; // default reason code = max fee exceeded (code 42 is obsolete) - if (preg_match("/Ins adjust $inslabel/i", $rsn, $tmp)) { + if (preg_match("/Ins adjust/i", $rsn, $tmp)) { // From manual post. Take the defaults. - } elseif (preg_match("/To copay $inslabel/i", $rsn, $tmp) && !$chg) { + } elseif (preg_match("/To copay/i", $rsn, $tmp) && !$chg) { $coinsurance = $ptresp; // from manual post continue; - } elseif (preg_match("/To ded'ble $inslabel/i", $rsn, $tmp) && !$chg) { + } elseif (preg_match("/To ded'ble/i", $rsn, $tmp) && !$chg) { $deductible = $ptresp; // from manual post continue; - } elseif (preg_match("/$inslabel copay: (\S+)/i", $rsn, $tmp) && !$chg) { + } elseif (preg_match("/copay: (\S+)/i", $rsn, $tmp) && !$chg) { $coinsurance = $tmp[1]; // from 835 as of 6/2007 continue; - } elseif (preg_match("/$inslabel coins: (\S+)/i", $rsn, $tmp) && !$chg) { + } elseif (preg_match("/coins: (\S+)/i", $rsn, $tmp) && !$chg) { $coinsurance = $tmp[1]; // from 835 and manual post as of 6/2007 continue; - } elseif (preg_match("/$inslabel dedbl: (\S+)/i", $rsn, $tmp) && !$chg) { + } elseif (preg_match("/dedbl: (\S+)/i", $rsn, $tmp) && !$chg) { $deductible = $tmp[1]; // from 835 and manual post as of 6/2007 continue; - } elseif (preg_match("/$inslabel ptresp: (\S+)/i", $rsn, $tmp) && !$chg) { + } elseif (preg_match("/ptresp: (\S+)/i", $rsn, $tmp) && !$chg) { continue; // from 835 as of 6/2007 - } elseif (preg_match("/$inslabel adjust code (\S+)/i", $rsn, $tmp)) { + } elseif (preg_match("/adjust code (\S+)/i", $rsn, $tmp)) { $rcode = $tmp[1]; // from 835 } elseif (preg_match("/$inslabel/i", $rsn, $tmp)) { // Take the defaults. @@ -404,7 +405,7 @@ class Claim // Other adjustments default to Ins1. } elseif ( preg_match("/Co-pay: (\S+)/i", $rsn, $tmp) || - preg_match("/Coinsurance: (\S+)/i", $rsn, $tmp) + preg_match("/Coins: (\S+)/i", $rsn, $tmp) ) { $coinsurance = 0 + $tmp[1]; // from 835 before 6/2007 continue; @@ -1777,4 +1778,33 @@ class Claim { return $this->x12Zip($this->billing_prov_id['zip']); } + + /** + * Group an array of adjustment group codes into a new array with the keys based on + * the group code $a[1] and the adjustment reason code $a[2]. + * If there are multiple for the same group and reason combine and add + * the amount $a[3] and return the date of the payment $a[0]. + * + * @param array $aarr Payer adjustment array from the X12837 script with payer adjustments + * @return array Returns a grouped array to the 837 for output in the CAS segment + */ + public function getLineItemAdjustments($aarr) + { + $this->line_item_adjs = []; + foreach ($aarr as $a) { + if (!array_key_exists($a[1], $this->line_item_adjs)) { + $this->line_item_adjs[$a[1]] = []; + } + + if (!array_key_exists($a[2] ?? null, $this->line_item_adjs[$a[1]])) { + $this->line_item_adjs[$a[1]][$a[2]] = $a[3]; + } else { + $this->line_item_adjs[$a[1]][$a[2]] += $a[3]; + $this->line_item_adjs[$a[1]][$a[2]] = number_format($this->line_item_adjs[$a[1]][$a[2]], 2, '.', ''); + } + $this->line_item_adjs['payer_paid_date'] = $a[0]; + } + + return $this->line_item_adjs; + } } diff --git a/src/Billing/X125010837P.php b/src/Billing/X125010837P.php index ce0239bcc..b9c4f6715 100644 --- a/src/Billing/X125010837P.php +++ b/src/Billing/X125010837P.php @@ -294,13 +294,11 @@ class X125010837P $log .= "*** Billing facility has no state.\n"; } $out .= "*"; - - // X12 likes 9 a true digit zip in loop 2010AA - if (strlen($claim->billingFacilityZip()) == 9) { - $out .= $claim->billingFacilityZip(); - } else { + // X12 requires a 9 digit zip in loop 2010AA but we output it anyways + if (strlen($claim->billingFacilityZip()) != 9) { $log .= "*** Billing facility zip is not 9 digits.\n"; } + $out .= $claim->billingFacilityZip(); $out .= "~\n"; if ($claim->billingFacilityNPI() && $claim->billingFacilityETIN()) { @@ -371,11 +369,11 @@ class X125010837P $log .= "*** Pay to provider has no state.\n"; } $out .= "*"; - if (strlen($claim->billingFacilityZip()) == 9) { - $out .= $claim->billingFacilityZip(); - } else { + // X12 requires a 9 digit zip but we output it anyways + if (strlen($claim->billingFacilityZip()) != 9) { $log .= "*** Pay to provider zip is not 9 digits.\n"; } + $out .= $claim->billingFacilityZip(); $out .= "~\n"; } @@ -474,13 +472,14 @@ class X125010837P } $out .= "*"; if ( - (strlen($claim->x12Zip($claim->insuredZip())) == 5) - || (strlen($claim->x12Zip($claim->insuredZip())) == 9) + !( + (strlen($claim->x12Zip($claim->insuredZip())) == 5) + || (strlen($claim->x12Zip($claim->insuredZip())) == 9) + ) ) { - $out .= $claim->x12Zip($claim->insuredZip()); - } else { $log .= "*** Insured zip is not 5 or 9 digits.\n"; } + $out .= $claim->x12Zip($claim->insuredZip()); $out .= "~\n"; ++$edicount; @@ -1513,43 +1512,36 @@ class X125010837P "~\n"; $tmpdate = $payerpaid[0]; - // new logic for putting same adjustment group codes - // on the same line - $adj_group_code[0] = ''; - $adj_count = 0; - $aarr_count = count($aarr); - foreach ($aarr as $a) { - ++$adj_count; - $adj_group_code[$adj_count] = $a[1]; - // when the adj group code changes increment edi - // counter and add line ending - if ($adj_group_code[$adj_count] !== $adj_group_code[($adj_count - 1)]) { - // increment when there was a prior segment with the - // same adj group code - if ($adj_count !== 1) { - ++$edicount; - $out .= "~\n"; - } - $out .= "CAS" . // Previous payer's line level adjustments. Page 558. - "*" . $a[1] . - "*" . $a[2] . - "*" . $a[3]; - if (($aarr_count == 1) || ($adj_count !== 1)) { - ++$edicount; - $out .= "~\n"; - } - } else { - $out .= "*" . // since it's the same adj group code don't include it - "*" . $a[2] . - "*" . $a[3]; - if ($adj_count == $aarr_count) { - ++$edicount; - $out .= "~\n"; + $cas = $claim->getLineItemAdjustments($aarr); + + // $key is the group code or payer_paid_date + foreach ($cas as $key => $value) { + if ($key == 'payer_paid_date') { + if (!$tmpdate) { + $tmpdate = $value; } + continue; } - if (!$tmpdate) { - $tmpdate = $a[0]; + + $out .= "CAS" . + "*" . + $key . "*"; + $size = count($value); + $cntr = 0; + // $k is the reason code + // $v is the amount + foreach ($value as $k => $v) { + $cntr++; + $out .= $k . + "*" . + $v; + if ($cntr < $size) { + $out .= "*" . + "*"; + } } + $out .= "~\n"; + ++$edicount; } if ($tmpdate) { -- 2.11.4.GIT