You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2106 lines
70 KiB
2106 lines
70 KiB
<?php |
|
|
|
/** |
|
* edih_csv_inc.php |
|
* |
|
* @package OpenEMR |
|
* @link http://www.open-emr.org |
|
* @author Kevin McCormick Longview, Texas |
|
* @author Stephen Waite <stephen.waite@cmsvt.com> |
|
* @copyright Copyright (c) 2012 Kevin McCormick Longview, Texas |
|
* @copyright Copyright (c) 2021 Stephen Waite <stephen.waite@cmsvt.com> |
|
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 |
|
*/ |
|
|
|
/** |
|
* The purpose of this file is to hold functions of general utility for |
|
* my edi_claim_history project. It began as a php "class" but I am now |
|
* thinking that instantiating the class is too much bother and probably |
|
* a waste of memory, since the contents of the file have to be read into |
|
* memory anyway. |
|
* |
|
* <pre> |
|
* ******* important ********* |
|
* function csv_parameters($type="ALL") |
|
* This function must have the correct values or nothing will work |
|
* function csv_verify_file( $file_path, $type, $val_array=FALSE ) |
|
* critical for file verification and x12 parsing |
|
* function (in ibr_uploads.php) ibr_upload_match_file($param_ar, $fidx, &$html_str) |
|
* contains a regular expression that must be correct |
|
* |
|
* Also, the constant IBR_HISTORY_DIR must be correct |
|
* ************************** |
|
* </pre> |
|
* |
|
* The claim_history x12 files are claim (837) acknowledgement (997/999) claim status (277) and claim payment (835) |
|
* Also eligibility request (270) and eligibility response (271) |
|
* |
|
* <pre> |
|
* Basic workflow: |
|
* Each file type has a row in the array from csv_paramaters() |
|
* type directory files_csv claims_csv column regex |
|
* |
|
* 1. open submitted file in edih_x12_class to verify and produce properties |
|
* 2. Read the parameters array and choose the parameters using 'type' |
|
* 2. Search the matched type 'directory' for the filename files matching the 'regex' regular expressions and |
|
* compare the results to the files listed in the 'files_csv' files.csv record -- unmatched files are "new" |
|
* 3. Each "new" x12 file should be read by csv_x12_segments -- returns array('path', 'delimiters', 'segments') |
|
* ibr, ebr, ack -- basically Availity formats have their own read functions |
|
* 4. Pass the array to various functions which parse for claims information |
|
* 5. Write the results to files.csv or claims.csv and create html output for display |
|
* |
|
* 6. Other outputs as called for in ibr_history.php -- from user input from claim_history.html |
|
* </pre> |
|
* |
|
* Key usability issue is the "new" files are in the users home directory -- downloaded there |
|
* while the OpenEMR is on the server -- so there is a basic issue of access to the files |
|
* |
|
* The ibr_uploads.php script handles uploads of zip archives or multiple file uploads |
|
* |
|
* The csv data files are just php written .csv files, so anything different may cause errors |
|
* You can open and edit them in OpenOffice, but you must save them in "original format" |
|
* |
|
* TO_DO Some type of "find in files" search would be helpful for locating all references to a claim, patient, etc. |
|
* [ grep -nHIrF 'findtext'] |
|
* |
|
* TO_DO functions to zip old files, put them aside, and remove them from csv tables |
|
*/ |
|
|
|
/** |
|
* Constant that is checked in included files to prevent direct access. |
|
* concept taken from Joomla |
|
*/ |
|
define('_EDIH', 1); |
|
//DIRECTORY_SEPARATOR; |
|
if (!defined('DS')) { |
|
define('DS', DIRECTORY_SEPARATOR); |
|
} |
|
|
|
/** |
|
* Log messages to the log file |
|
* |
|
* @param string $msg_str the log message |
|
* @return int number of characters written |
|
*/ |
|
function csv_edihist_log($msg_str) |
|
{ |
|
// |
|
//$dir = dirname(__FILE__).DS.'log'; |
|
//$dir = $GLOBALS['OE_EDIH_DIR'].DS.'log'; |
|
//$logfile = $GLOBALS['OE_EDIH_DIR'] . "/log/edi_history_log.txt"; |
|
$logfile = 'edih_log_' . date('Y-m-d') . '.txt'; |
|
$dir = csv_edih_basedir() . DS . 'log'; |
|
$rslt = 0; |
|
if (is_string($msg_str) && strlen($msg_str)) { |
|
$tm = date('Ymd:Hms') . ' ' . $msg_str . PHP_EOL; |
|
// |
|
$rslt = file_put_contents($dir . DS . $logfile, $tm, FILE_APPEND); |
|
} else { |
|
// |
|
$fnctn = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function']; |
|
csv_edihist_log('invalid message string ' . $fnctn); |
|
} |
|
|
|
// |
|
return $rslt; // number of characters written |
|
} |
|
|
|
/** |
|
* read the edi_history_log.txt file into an |
|
* html formatted ordered list |
|
* |
|
* @return string |
|
*/ |
|
function csv_log_html($logname = '') |
|
{ |
|
check_file_dir_name($logname); |
|
$html_str = "<div class='filetext'>" . PHP_EOL . "<ol class='logview'>" . PHP_EOL; |
|
$fp = csv_edih_basedir() . DS . 'log' . DS . $logname; |
|
if (is_file($fp)) { |
|
$fh = fopen($fp, 'r'); |
|
if ($fh !== false) { |
|
while (($buffer = fgets($fh)) !== false) { |
|
$html_str .= "<li>" . text($buffer) . "</li>" . PHP_EOL; |
|
} |
|
|
|
$html_str .= "</ol>" . PHP_EOL . "</div>" . PHP_EOL; |
|
if (!feof($fh)) { |
|
$html_str .= "<p>Error in logfile: unexpected file ending</p>" . PHP_EOL; |
|
} |
|
|
|
fclose($fh); |
|
} else { |
|
$html_str = "<p>Error: unable to open log file</p>" . PHP_EOL; |
|
} |
|
} |
|
|
|
return $html_str; |
|
} |
|
|
|
|
|
/** |
|
* list log files and store old logs in an archive |
|
* |
|
* @param bool |
|
* @return array (json) |
|
*/ |
|
function csv_log_manage($list = true) |
|
{ |
|
// |
|
//$dir = dirname(__FILE__).DS.'log'; |
|
$dir = csv_edih_basedir() . DS . 'log'; |
|
$list_ar = array(); |
|
$old_ar = array(); |
|
$lognames = scandir($dir); |
|
if ($list) { |
|
foreach ($lognames as $log) { |
|
if (!strpos($log, '_log_')) { |
|
continue; |
|
} |
|
|
|
$list_ar[] = $log; |
|
} |
|
|
|
$s = (count($list_ar)) ? rsort($list_ar) : false; |
|
// |
|
return json_encode($list_ar); |
|
// |
|
} else { |
|
// list is false, must be archive |
|
$datetime1 = date_create(date('Y-m-d')); |
|
// |
|
foreach ($lognames as $log) { |
|
if ($log == '.' || $log == '..') { |
|
continue; |
|
} |
|
|
|
// |
|
$pos1 = strrpos($log, '_'); |
|
if ($pos1) { |
|
$ldate = substr($log, $pos1 + 1, 10); |
|
$datetime2 = date_create($ldate); |
|
$interval = date_diff($datetime1, $datetime2); |
|
//echo '== date difference '.$ldate.' '.$interval->format('%R%a days').PHP_EOL; |
|
if ($interval->format('%R%a') < -7) { |
|
// older log files are put in zip archive |
|
if (is_file($dir . DS . $log)) { |
|
$old_ar[] = $log; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// |
|
$ok = false; |
|
$archname = $dir . DS . 'edih-log-archive.zip'; |
|
$filelimit = 200; |
|
// |
|
if (count($old_ar)) { |
|
$zip = new ZipArchive(); |
|
if (is_file($archname)) { |
|
$ok = $zip->open($archname, ZipArchive::CHECKCONS); |
|
} else { |
|
$ok = $zip->open($archname, ZipArchive::CREATE); |
|
} |
|
|
|
// |
|
if ($ok) { |
|
if ($zip->numFiles >= $filelimit) { |
|
$zip->close(); |
|
$dte = $datetime1->format('Ymd'); |
|
$ok = rename($dir . DS . $archname, $dir . DS . $dte . '_' . $archname); |
|
csv_edihist_log('csv_log_archive: rename full archive ' . $dte . '_' . $archname); |
|
if ($ok) { |
|
$ok = $zip->open($archname, ZipArchive::CREATE); |
|
if (!$ok) { |
|
csv_edihist_log('csv_log_archive: cannot create ' . $archname); |
|
} |
|
} else { |
|
csv_edihist_log('csv_log_archive: cannot rename ' . $archname); |
|
} |
|
} |
|
|
|
// |
|
if ($ok) { |
|
foreach ($old_ar as $lg) { |
|
if (is_file($dir . DS . $lg)) { |
|
$a = $zip->addFile($dir . DS . $lg, $lg); |
|
if ($a) { |
|
csv_edihist_log('csv_log_archive: add to archive ' . $lg); |
|
} else { |
|
csv_edihist_log('csv_log_archive: error archiving ' . $lg); |
|
} |
|
} |
|
} |
|
|
|
$c = $zip->close(); |
|
if ($c) { |
|
foreach ($old_ar as $lg) { |
|
$u = unlink($dir . DS . $lg); |
|
if ($u) { |
|
continue; |
|
} else { |
|
csv_edihist_log('csv_log_archive: error removing ' . $dir . DS . $lg); |
|
} |
|
} |
|
} else { |
|
csv_edihist_log('csv_log_archive: error closing log file archive'); |
|
} |
|
} else { |
|
csv_edihist_log('csv_log_manage: error failed to open ' . $archname); |
|
} |
|
} |
|
} |
|
|
|
// |
|
return json_encode($old_ar); |
|
} |
|
|
|
|
|
/** |
|
* open or save a user notes file |
|
* |
|
* @param string |
|
* @param bool |
|
* @return string |
|
*/ |
|
function csv_notes_file($content = '', $open = true) |
|
{ |
|
// |
|
$str_html = ''; |
|
//$fp = $GLOBALS['OE_EDIH_DIR'].'/edi_notes.txt'; |
|
$fp = csv_edih_basedir() . DS . 'archive' . DS . 'edi_notes.txt'; |
|
if (! is_writable($fp)) { |
|
$fh = fopen($fp, 'a+b'); |
|
fclose($fh); |
|
} |
|
|
|
// for retrieving notes |
|
if ($open) { |
|
// if contents were previously deleted by user and file is empty, |
|
// the text 'empty' is put in content in save operation |
|
$ftxt = file_get_contents($fp); |
|
if ($ftxt === false) { |
|
$str_html .= 'csv_notes_file: file error <br />' . PHP_EOL; |
|
csv_edihist_log('csv_notes_file: file error'); |
|
} |
|
|
|
if (substr($ftxt, 0, 5) == 'empty' && strlen($ftxt) == 5) { |
|
$ftxt = '## ' . date("F j, Y, g:i a"); |
|
} elseif (!$ftxt) { |
|
$ftxt = '## ' . date("F j, Y, g:i a"); |
|
} |
|
|
|
$str_html .= PHP_EOL . text($ftxt) . PHP_EOL; |
|
// next stanza for saving content |
|
} elseif (strlen($content)) { |
|
//echo "csv_notes_file: we have content<br />".PHP_EOL; |
|
// use finfo php class |
|
if (class_exists('finfo')) { |
|
$finfo = new finfo(FILEINFO_MIME); |
|
$mimeinfo = $finfo->buffer($content); |
|
if (strncmp($mimeinfo, 'text/plain; charset=us-ascii', 28) !== 0) { |
|
csv_edihist_log('csv_notes_file: invalid mime-type ' . $mimeinfo); |
|
$str_html = 'csv_notes_file: invalid mime-type <br />' . text($mimeinfo); |
|
// |
|
return $str_html; |
|
} |
|
} elseif (preg_match('/[^\x20-\x7E\x0A\x0D]|(<\?)|(<%)|(<asp)|(<ASP)|(#!)|(\$\{)|(<scr)|(<SCR)/', $content, $matches, PREG_OFFSET_CAPTURE)) { |
|
csv_edihist_log('csv_notes_file: Filtered character in file content -- character: ' . $matches[0][0] . ' position: ' . $matches[0][1]); |
|
$str_html .= 'Filtered character in file content not accepted <br />' . PHP_EOL; |
|
$str_html .= ' character: ' . text($matches[0][0]) . ' position: ' . text($matches[0][1]) . '<br />' . PHP_EOL; |
|
// |
|
return $str_html; |
|
} |
|
} else { |
|
$ftxt = ($content) ? $content : 'empty'; |
|
$saved = file_put_contents($fp, $ftxt); |
|
$str_html .= ($saved) ? '<p>Save Error with notes file</p>' : '<p>Notes content saved</p>'; |
|
} |
|
|
|
// |
|
return $str_html; |
|
} |
|
|
|
/** |
|
* generates path to edi history files |
|
* |
|
* @return string|bool directory path |
|
*/ |
|
function csv_edih_basedir() |
|
{ |
|
// should be something like /var/www/htdocs/openemr/sites/default |
|
if (isset($GLOBALS['OE_SITE_DIR'])) { |
|
// debug |
|
//echo 'csv_edih_basedir OE_SITE_DIR '.$GLOBALS['OE_SITE_DIR'].'<br />'.PHP_EOL; |
|
return $GLOBALS['OE_SITE_DIR'] . DS . 'documents' . DS . 'edi' . DS . 'history'; |
|
} else { |
|
csv_edihist_log('csv_edih_basedir: failed to obtain OpenEMR Site directory'); |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* generates path to edi_history tmp dir for file upload operations |
|
* |
|
* @uses csv_edih_basedir() |
|
* @return string directory path |
|
*/ |
|
function csv_edih_tmpdir() |
|
{ |
|
// |
|
$bdir = csv_edih_basedir(); |
|
$tdir = ($bdir) ? $bdir . DS . 'tmp' : false; |
|
//$systmp = sys_get_temp_dir(); |
|
//$systmp = stripcslashes($systmp); |
|
//$systdir = $systmp.DS.'edihist'; |
|
//if ( $tdir && (is_dir($tdir) || mkdir($tdir, 0755) ) ) { |
|
if ($tdir) { |
|
return $tdir; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
/** |
|
* Initial setup function |
|
* |
|
* Create the directory tree and write the column headers into the csv files |
|
* This function will accept a directory argument and it appends the value |
|
* from IBR_HISTORY_DIR to the path. Then a directory for each type of file |
|
* and the csv files are created under that. |
|
* |
|
* @uses csv_parameters() |
|
* @uses csv_table_header() |
|
* @uses csv_edih_basedir() |
|
* |
|
* @param string &$out_str referenced, should be created in calling function |
|
* @return boolean |
|
*/ |
|
function csv_setup() |
|
{ |
|
// |
|
$isOK = false; |
|
$out_str = ''; |
|
$chr = 0; |
|
// $GLOBALS['OE_SITE_DIR'] should be like /var/www/htdocs/openemr/sites/default |
|
$sitedir = $GLOBALS['OE_SITE_DIR']; |
|
//$sitedir = csv_edih_basedir(); |
|
// |
|
if (is_readable($sitedir)) { |
|
$basedir = $sitedir . DS . 'documents' . DS . 'edi'; |
|
$edihist_dir = $basedir . DS . 'history'; |
|
$csv_dir = $edihist_dir . DS . 'csv'; |
|
$archive_dir = $edihist_dir . DS . 'archive'; |
|
$log_dir = $edihist_dir . DS . 'log'; |
|
$tmp_dir = $edihist_dir . DS . 'tmp'; |
|
} else { |
|
//csv_edihist_log('setup: failed to obtain OpenEMR Site directory'); |
|
echo 'setup: failed to obtain OpenEMR Site directory<br />' . PHP_EOL; |
|
return false; |
|
} |
|
|
|
// |
|
if (is_writable($basedir)) { |
|
$isOK = true; |
|
//csv_edihist_log('setup: directory '.$basedir); |
|
$out_str .= 'EDI_History Setup should not overwrite existing data.<br />' . PHP_EOL; |
|
$out_str .= 'Setup: directory ' . text($basedir) . '<br />' . PHP_EOL; |
|
// |
|
if (is_dir($edihist_dir) || mkdir($edihist_dir, 0755)) { |
|
$out_str .= 'created folder ' . text($edihist_dir) . '<br />' . PHP_EOL; |
|
$isOK = true; |
|
if (is_dir($csv_dir) || mkdir($csv_dir, 0755)) { |
|
$out_str .= 'created folder ' . text($csv_dir) . '<br />' . PHP_EOL; |
|
$isOK = true; |
|
} else { |
|
$isOK = false; |
|
$out_str .= 'Setup: Failed to create csv folder... ' . '<br />' . PHP_EOL; |
|
die('Failed to create csv folder... ' . text($archive_dir)); |
|
} |
|
|
|
if (is_dir($archive_dir) || mkdir($archive_dir, 0755)) { |
|
$out_str .= 'created folder ' . text($archive_dir) . '<br />' . PHP_EOL; |
|
$isOK = true; |
|
} else { |
|
$isOK = false; |
|
$out_str .= 'Setup: Failed to create archive folder... ' . '<br />' . PHP_EOL; |
|
die('Failed to create archive folder... '); |
|
} |
|
|
|
if (is_dir($log_dir) || mkdir($log_dir, 0755)) { |
|
$out_str .= 'created folder ' . text($log_dir) . '<br />' . PHP_EOL; |
|
$isOK = true; |
|
} else { |
|
$isOK = false; |
|
$out_str .= 'Setup: Failed to create log folder... ' . '<br />' . PHP_EOL; |
|
die('Failed to create log folder... '); |
|
} |
|
|
|
if (is_dir($tmp_dir) || mkdir($tmp_dir, 0755)) { |
|
$out_str .= 'created folder ' . text($tmp_dir) . PHP_EOL; |
|
$isOK = true; |
|
} else { |
|
$isOK = false; |
|
$out_str .= 'Setup: Failed to create tmp folder... ' . '<br />' . PHP_EOL; |
|
die('Failed to create tmp folder... '); |
|
} |
|
} else { |
|
$isOK = false; |
|
$out_str .= 'Setup failed: cannot write to folder ' . text($basedir) . '<br />' . PHP_EOL; |
|
die('Setup failed: cannot write to ' . text($basedir)); |
|
} |
|
} else { |
|
$isOK = false; |
|
$out_str .= 'Setup: Failed to create history folder... ' . '<br />' . PHP_EOL; |
|
die('Failed to create history folder... ' . text($edihist_dir)); |
|
} |
|
|
|
if ($isOK) { |
|
$p_ar = csv_parameters('ALL'); |
|
$old_csv = array('f837' => 'batch', 'f835' => 'era'); |
|
foreach ($p_ar as $key => $val) { |
|
// rename existing csv files to old_filename |
|
if (is_dir($csv_dir)) { |
|
if ($dh = opendir($csv_dir)) { |
|
while (($file = readdir($dh)) !== false) { |
|
if (is_file($csv_dir . DS . $file) && strpos($file, 'csv')) { |
|
$rn = rename($csv_dir . DS . $file, $csv_dir . DS . 'old_' . $file); |
|
if ($rn) { |
|
$out_str .= 'renamed csv/' . text($file) . ' to old_' . text($file) . '<br />' . PHP_EOL; |
|
} else { |
|
$out_str .= 'attempt to rename csv/' . text($file) . ' failed<br />' . PHP_EOL; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//; |
|
// make the edi files storage subdirs |
|
$tp = $p_ar[$key]['type']; |
|
$type_dir = $p_ar[$key]['directory']; |
|
// |
|
if (is_dir($type_dir)) { |
|
$out_str .= 'folder for ' . text($tp) . ' exists ' . text($type_dir) . '<br />' . PHP_EOL; |
|
} elseif (mkdir($type_dir, 0755)) { |
|
if ($tp == 'f835') { |
|
// in upgrade case the f835 directory should not exist |
|
// move 'era' files from /era to /f835 |
|
if (is_dir($edihist_dir . DS . 'era')) { |
|
$fct = 0; |
|
$rct = 0; |
|
if ($dh = opendir($edihist_dir . DS . 'era')) { |
|
while (($file = readdir($dh)) !== false) { |
|
if (is_file($edihist_dir . DS . 'era' . DS . $file)) { |
|
$rct++; |
|
$rn = rename($edihist_dir . DS . 'era' . DS . $file, $type_dir . DS . $file); |
|
$fct = ($rn) ? $fct + 1 : $fct; |
|
} |
|
} |
|
} |
|
|
|
$out_str .= 'created type folder ' . text($type_dir) . ' and moved ' . text($fct) . ' of ' . text($rct) . ' files from /era<br />' . PHP_EOL; |
|
} |
|
} else { |
|
$out_str .= 'created type folder ' . text($type_dir) . '<br />' . PHP_EOL; |
|
} |
|
} else { |
|
$out_str .= 'Setup failed to create directory for ' . text($tp) . '<br />' . PHP_EOL; |
|
} |
|
} |
|
} else { |
|
$out_str .= 'Setup failed: Can not create directories <br />' . PHP_EOL; |
|
} |
|
|
|
if ($isOK) { |
|
csv_edihist_log($out_str); |
|
return true; |
|
} else { |
|
return $out_str; |
|
} |
|
} |
|
|
|
|
|
/** |
|
* Empty all contents of tmp dir /documents/edi/history/tmp |
|
* |
|
* @uses csv_edih_tmpdir() |
|
* @param none |
|
* @return bool |
|
*/ |
|
function csv_clear_tmpdir() |
|
{ |
|
// |
|
$tmpdir = csv_edih_tmpdir(); |
|
if (basename($tmpdir) != 'tmp') { |
|
csv_edihist_log('tmp dir not /documents/edi/history/tmp'); |
|
return false; |
|
} |
|
|
|
$tmp_files = scandir($tmpdir); |
|
if (count($tmp_files) > 2) { |
|
foreach ($tmp_files as $idx => $tmpf) { |
|
if ($tmpf == "." || $tmpf == "..") { |
|
// can't delete . and .. |
|
continue; |
|
} elseif (is_file($tmpdir . DS . $tmpf)) { |
|
unlink($tmpdir . DS . $tmpf); |
|
} elseif (is_dir($tmpdir . DS . $tmpf)) { |
|
$tdir_ar = scandir($tmpdir . DS . $tmpf); |
|
foreach ($tdir_ar as $tfn) { |
|
if ($tfn == "." || $tfn == "..") { |
|
continue; |
|
} elseif (is_file($tmpdir . DS . $tmpf . DS . $tfn)) { |
|
unlink($tmpdir . DS . $tmpf . DS . $tfn); |
|
} |
|
} |
|
|
|
rmdir($tmpdir . DS . $tmpf); |
|
} |
|
} |
|
} |
|
|
|
$tmp_files = scandir($tmpdir); |
|
if (count($tmp_files) > 2) { |
|
csv_edihist_log('tmp dir contents remain in ... /documents/edi/history/tmp'); |
|
return false; |
|
} else { |
|
return true; |
|
} |
|
} |
|
|
|
/** |
|
* open and verify a default edih_x12_file object |
|
* |
|
* @uses csv_check_filepath() |
|
* |
|
* @param string filepath or filename |
|
* @parm string file x12 type |
|
* @return object edih_x12_file class |
|
*/ |
|
function csv_check_x12_obj($filepath, $type = '') |
|
{ |
|
// |
|
$x12obj = false; |
|
$ok = false; |
|
// |
|
$fp = csv_check_filepath($filepath, $type); |
|
// |
|
if ($fp) { |
|
$x12obj = new edih_x12_file($fp); |
|
if ('edih_x12_file' == get_class($x12obj)) { |
|
if ($x12obj->edih_valid() == 'ovigs') { |
|
$ok = count($x12obj->edih_segments()); |
|
$ok = ($ok) ? count($x12obj->edih_envelopes()) : false; |
|
$ok = ($ok) ? count($x12obj->edih_delimiters()) : false; |
|
if (!$ok) { |
|
csv_edihist_log("csv_check_x12_obj: object missing properties [$filepath]"); |
|
csv_edihist_log($x12obj->edih_message()); |
|
return false; |
|
} |
|
} else { |
|
csv_edihist_log("csv_check_x12_obj: invalid object $filepath"); |
|
return false; |
|
} |
|
} else { |
|
csv_edihist_log("csv_check_x12_obj: object not edih_x12_file $filepath"); |
|
return false; |
|
} |
|
} else { |
|
csv_edihist_log("csv_check_x12_obj: invalid file path $filepath"); |
|
return false; |
|
} |
|
|
|
// |
|
return $x12obj; |
|
} |
|
|
|
/** |
|
* Check that the file path we are working with is a readable file. |
|
* |
|
* If it is a file we have uploaded and we have only the file name |
|
* this function will type the file and find it in the uploaded files directories |
|
* and return the complete path. |
|
* |
|
* @uses csv_parameters() |
|
* @param string $filename name of a file that is one of our types |
|
* @param string $type optional; one of our file types |
|
* @return string either an empty string or a readable filepath |
|
*/ |
|
function csv_check_filepath($filename, $type = 'ALL') |
|
{ |
|
// |
|
// if file is readable, just return it |
|
if (is_file($filename) && is_readable($filename)) { |
|
return $filename; |
|
} |
|
|
|
// |
|
$goodpath = ''; |
|
$fp = ''; |
|
$fn = basename($filename); |
|
// |
|
if ($type && $type != 'ALL') { |
|
$p = csv_parameters($type); |
|
if (is_array($p) && array_key_exists('type', $p)) { |
|
$fp = $p['directory'] . DS . $fn; |
|
} |
|
} else { |
|
$p_ar = csv_parameters("ALL"); |
|
foreach ($p_ar as $tp => $par) { |
|
if (!$p_ar[$tp]['regex'] || !preg_match($p_ar[$tp]['regex'], $fn)) { |
|
continue; |
|
} else { |
|
$fp = $p_ar[$tp]['directory'] . DS . $fn; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (is_file($fp) && is_readable($fp)) { |
|
$goodpath = realpath($fp); |
|
} |
|
|
|
// |
|
return $goodpath; |
|
} |
|
|
|
/** |
|
* verify file type parameter |
|
* |
|
* @param string file type |
|
* @param bool return GS02 code or fXXX |
|
* @return string file type or empty |
|
*/ |
|
function csv_file_type($type, $gs_code = false) |
|
{ |
|
// |
|
if (!$type) { |
|
csv_edihist_log('csv_file_type: invalid or missing type argument ' . $type); |
|
return false; |
|
} else { |
|
$tp_type = (string)$type; |
|
} |
|
|
|
// |
|
if (strpos('|f837|batch|HC', $tp_type)) { |
|
$tp = ($gs_code) ? 'HC' : 'f837'; |
|
} elseif (strpos('|f835|era|HP', $tp_type)) { |
|
$tp = ($gs_code) ? 'HP' : 'f835'; |
|
} elseif (strpos('|f999|f997|ack|ta1|FA', $tp_type)) { |
|
$tp = ($gs_code) ? 'FA' : 'f997'; |
|
} elseif (strpos('|f277|HN', $tp_type)) { |
|
$tp = ($gs_code) ? 'HN' : 'f277'; |
|
} elseif (strpos('|f276|HR', $tp_type)) { |
|
$tp = ($gs_code) ? 'HR' : 'f276'; |
|
} elseif (strpos('|f271|HB', $tp_type)) { |
|
$tp = ($gs_code) ? 'HB' : 'f271'; |
|
} elseif (strpos('|f270|HS', $tp_type)) { |
|
$tp = ($gs_code) ? 'HS' : 'f270'; |
|
} elseif (strpos('|f278|HI', $tp_type)) { |
|
$tp = ($gs_code) ? 'HI' : 'f278'; |
|
} else { |
|
$tp = ''; |
|
} |
|
|
|
// |
|
if (!$tp) { |
|
csv_edihist_log('csv_file_type error: incorrect type ' . $tp_type); |
|
} |
|
|
|
return $tp; |
|
} |
|
|
|
|
|
/** |
|
* The array that holds the various parameters used in dealing with files |
|
* |
|
* A key function since it holds the paths, columns, etc. |
|
* Unfortunately, there is an issue with matching the type in * the case of the |
|
* values '997', '277', '999', etc, becasue these strings may be recast |
|
* from strings to integers, so the 'type' originally supplied is lost. |
|
* This introduces an inconsistency when the 'type' is used in comparison tests. |
|
* We call the csv_file_type() function to return a usable file type identifier. |
|
* The 'datecolumn' and 'fncolumn' entries are used in csv_to_html() to filter by date |
|
* or place links to files. |
|
* |
|
* @param string $type -- default = ALL or one of batch, ibr, ebr, dpr, f997, f277, era, ack, text |
|
* @return array |
|
*/ |
|
function csv_parameters($type = 'ALL') |
|
{ |
|
// |
|
// This will need the OpenEMR 'oe_site_dir' to replace global |
|
// |
|
$p_ar = array(); |
|
|
|
$tp = ($type === 'ALL') ? $type : csv_file_type($type); |
|
if (!$tp) { |
|
csv_edihist_log('csv_parameters() error: incorrect type ' . $type); |
|
return $p_ar; |
|
} |
|
|
|
//$edihist_dir = $GLOBALS['OE_SITE_DIR'].'/documents/edi/history'; |
|
$edihist_dir = csv_edih_basedir(); |
|
// |
|
// the batch file directory is a special case - decide whether to use OpenEMR batch files or make our own copies |
|
// OpenEMR copies each batch file to sites/default/documents/edi and this project never writes to that directory |
|
// batch reg ex -- '/20[01][0-9]-[01][0-9]-[0-3][0-9]-[0-9]{4}-batch*\.txt/' '/\d{4}-\d{2}-\d{2}-batch*\.txt$/' |
|
// |
|
$p_ar['f837'] = array('type' => 'f837', 'directory' => $GLOBALS['OE_SITE_DIR'] . DS . 'documents' . DS . 'edi', 'claims_csv' => $edihist_dir . DS . 'csv' . DS . 'claims_f837.csv', |
|
'files_csv' => $edihist_dir . DS . 'csv' . DS . 'files_f837.csv', 'filedate' => 'Date', 'claimdate' => 'SvcDate', 'regex' => '/\-batch(.*)\.txt$/'); |
|
// |
|
//$p_ar['csv'] = array("type"=>'csv', "directory"=>$edihist_dir.'/csv', "claims_csv"=>'ibr_parameters.csv', |
|
// "files_csv"=>'', "column"=>'', "regex"=>'/\.csv$/'); |
|
$p_ar['f997'] = array('type' => 'f997', 'directory' => $edihist_dir . DS . 'f997', 'claims_csv' => $edihist_dir . DS . 'csv' . DS . 'claims_f997.csv', |
|
'files_csv' => $edihist_dir . DS . 'csv' . DS . 'files_f997.csv', 'filedate' => 'Date', 'claimdate' => 'RspDate', 'regex' => '/\.(99[79]|ta1|ack)$/i'); |
|
$p_ar['f276'] = array('type' => 'f276', 'directory' => $edihist_dir . DS . 'f276', 'claims_csv' => $edihist_dir . DS . 'csv' . DS . 'claims_f276.csv', |
|
'files_csv' => $edihist_dir . DS . 'csv' . DS . 'files_f276.csv', 'filedate' => 'Date', 'claimdate' => 'ReqDate', 'regex' => '/\.276([ei]br)?$/'); |
|
$p_ar['f277'] = array('type' => 'f277', 'directory' => $edihist_dir . DS . 'f277', 'claims_csv' => $edihist_dir . DS . 'csv' . DS . 'claims_f277.csv', |
|
'files_csv' => $edihist_dir . DS . 'csv' . DS . 'files_f277.csv', 'filedate' => 'Date', 'claimdate' => 'SvcDate', 'regex' => '/\.277([ei]br)?$/i'); |
|
$p_ar['f270'] = array('type' => 'f270', 'directory' => $edihist_dir . DS . 'f270', 'claims_csv' => $edihist_dir . DS . 'csv' . DS . 'claims_f270.csv', |
|
'files_csv' => $edihist_dir . DS . 'csv' . DS . 'files_f270.csv', 'filedate' => 'Date', 'claimdate' => 'ReqDate', 'regex' => '/\.270([ei]br)?$/i'); |
|
$p_ar['f271'] = array('type' => 'f271', 'directory' => $edihist_dir . DS . 'f271', 'claims_csv' => $edihist_dir . DS . 'csv' . DS . 'claims_f271.csv', |
|
'files_csv' => $edihist_dir . DS . 'csv' . DS . 'files_f271.csv', 'filedate' => 'Date', 'claimdate' => 'RspDate', 'regex' => '/\.271([ei]br)?$/i'); |
|
$p_ar['f278'] = array('type' => 'f278', 'directory' => $edihist_dir . DS . 'f278', 'claims_csv' => $edihist_dir . DS . 'csv' . DS . 'claims_f278.csv', |
|
'files_csv' => $edihist_dir . DS . 'csv' . DS . 'files_f278.csv', 'filedate' => 'Date', 'claimdate' => 'FileDate', 'regex' => '/\.278/'); |
|
// OpenEMR stores era files, but the naming scheme is confusing, so we will just use our own directory for them |
|
$p_ar['f835'] = array('type' => 'f835', 'directory' => $edihist_dir . DS . 'f835', 'claims_csv' => $edihist_dir . DS . 'csv' . DS . 'claims_f835.csv', |
|
'files_csv' => $edihist_dir . DS . 'csv' . DS . 'files_f835.csv', 'filedate' => 'Date', 'claimdate' => 'SvcDate', 'regex' => '/835[0-9]{5}\.835*|\.(era|ERA|835)$/i'); |
|
// |
|
if (array_key_exists($tp, $p_ar)) { |
|
return $p_ar[$tp]; |
|
} else { |
|
return $p_ar; |
|
} |
|
} |
|
|
|
/** |
|
* determine if a csv table has data for select dropdown |
|
* |
|
* @param string default 'json' |
|
* @return array json if argument is 'json' |
|
*/ |
|
function csv_table_select_list($outtp = 'json') |
|
{ |
|
$optlist = array(); |
|
$labels = array('f835' => 'Payments', 'f837' => 'Claims', 'batch' => 'Claims', 'f277' => 'Status', 'f276' => 'Status Req', |
|
'f997' => 'Ack','f271' => 'Benefit', 'f270' => 'Benefit Req', 'f278' => 'Auth'); |
|
|
|
$edihist_dir = csv_edih_basedir(); // $GLOBALS['OE_SITE_DIR'].'/documents/edi/history' |
|
$csvdir = $edihist_dir . DS . 'csv'; |
|
$tbllist = scandir($csvdir); |
|
$idx = 0; |
|
foreach ($tbllist as $csvf) { |
|
if ($csvf == "." || $csvf == "..") { |
|
continue; |
|
} |
|
|
|
if (strpos($csvf, 'old') === 0) { |
|
continue; |
|
} |
|
|
|
if (filesize($csvdir . DS . $csvf) < 70) { |
|
continue; |
|
} |
|
|
|
if (substr($csvf, -1) == '~') { |
|
continue; |
|
} |
|
|
|
$finfo = pathinfo($csvdir . DS . $csvf); |
|
$fn = $finfo['filename']; |
|
// e.g. files_f997 |
|
$tp = explode('_', $fn); |
|
//$lblkey = $labels[$tp[1]]; |
|
$optlist[$tp[0]][$tp[1]]['fname'] = $fn; |
|
$optlist[$tp[0]][$tp[1]]['desc'] = $tp[0] . '-' . $labels[$tp[1]]; //$tp[1] .' '.$tp[0]; |
|
$idx++; |
|
} |
|
|
|
if ($outtp == 'json') { |
|
return json_encode($optlist); |
|
} else { |
|
return $optlist; |
|
} |
|
} |
|
|
|
/** |
|
* list existing archive files |
|
* |
|
* @param string default 'json' |
|
* @return array json if argument is 'json' |
|
*/ |
|
function csv_archive_select_list($outtp = 'json') |
|
{ |
|
// |
|
$flist = array(); |
|
$archdir = csv_edih_basedir() . DS . 'archive'; |
|
// |
|
// debug |
|
csv_edihist_log("csv_archive_select_list: using $archdir"); |
|
// |
|
$scan = scandir($archdir); |
|
if (is_array($scan) && count($scan)) { |
|
foreach ($scan as $s) { |
|
if ($s == '.' || $s == '..') { |
|
continue; |
|
} elseif (strpos($s, 'note')) { |
|
continue; |
|
} else { |
|
$flist[] = $s; |
|
} |
|
} |
|
} |
|
|
|
if ($outtp == 'json') { |
|
return json_encode($flist); |
|
} else { |
|
return $flist; |
|
} |
|
} |
|
|
|
/** |
|
* List files in the directory for the given type |
|
* |
|
* Write an entry in the log if a file is in the directory |
|
* that does not match the type |
|
* |
|
* @uses csv_parameters() |
|
* @param string $type a type from our list |
|
* @return array |
|
*/ |
|
function csv_dirfile_list($type) |
|
{ |
|
// return false if location is not appropriate |
|
$tp = csv_file_type($type); |
|
if (!$tp) { |
|
csv_edihist_log("csv_dirfile_list error: incorrect type $type"); |
|
return false; |
|
} |
|
|
|
$params = csv_parameters($tp); |
|
if (empty($params) || csv_singlerecord_test($params) == false) { |
|
csv_edihist_log("csv_dirfile_list() error: incorrect type $type"); |
|
return false; |
|
} |
|
|
|
$search_dir = $params['directory']; |
|
$ext_re = $params['regex']; |
|
$dirfiles = array(); |
|
// |
|
if (is_dir($search_dir)) { |
|
if ($dh = opendir($search_dir)) { |
|
while (($file = readdir($dh)) !== false) { |
|
if ($file == '.' || $file == '..' || $file == "process_bills.log") { |
|
continue; |
|
} elseif ($tp == 'f837' && ($file == 'history' || $file == 'README.txt')) { |
|
continue; |
|
} elseif (is_file($search_dir . DS . $file)) { |
|
$dirfiles[] = $file; |
|
} else { |
|
if ($tp == 'f837' && $file == 'history') { |
|
continue; |
|
} |
|
|
|
csv_edihist_log("csv_dirfile_list $type : not a file $file"); |
|
} |
|
} |
|
} else { |
|
csv_edihist_log("csv_dirfile_list $type : error in scan $search_dir"); |
|
} |
|
} else { |
|
csv_edihist_log("csv_dirfile_list $type : not a directory $search_dir"); |
|
} |
|
|
|
// |
|
return $dirfiles; |
|
} // end function |
|
|
|
|
|
/** |
|
* List files that are in the csv record |
|
* |
|
* @uses csv_parameters() |
|
* @uses csv_table_header() |
|
* |
|
* @param string $type -- one of our types |
|
* @return array |
|
*/ |
|
function csv_processed_files_list($type) |
|
{ |
|
// |
|
$tp = csv_file_type($type); |
|
if (!$tp) { |
|
csv_edihist_log("csv_processed_files_list: incorrect type $type"); |
|
return false; |
|
} |
|
|
|
$processed_files = array(); |
|
$param = csv_parameters($tp); |
|
$hdr_ar = csv_table_header($tp, 'file'); |
|
if (is_array($hdr_ar)) { |
|
foreach ($hdr_ar as $k => $hd) { |
|
if ($hd == 'FileName') { |
|
$csv_col = $k; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
$csv_col = (isset($csv_col)) ? $csv_col : 1; |
|
$csv_file = $param['files_csv']; |
|
//if ($tp == 'dpr') { |
|
//$csv_file = $param['claims_csv']; |
|
//$csv_col = '5'; |
|
//} else { |
|
//$csv_file = $param['files_csv']; |
|
//} |
|
// |
|
//$idx = 0; |
|
if (is_file($csv_file)) { |
|
if (($fh1 = fopen($csv_file, "r")) !== false) { |
|
while (($data = fgetcsv($fh1, 1024, ",")) !== false) { |
|
$processed_files[] = $data[$csv_col]; |
|
// |
|
//if ($idx) { $processed_files[] = $data[$csv_col]; } |
|
// skip the header row |
|
//$idx++; |
|
} |
|
|
|
fclose($fh1); |
|
} else { |
|
csv_edihist_log("csv_list_processed_files: failed to access $csv_file"); |
|
return false; |
|
} |
|
} else { |
|
// first run - no file exists |
|
csv_edihist_log("csv_processed_files_list: csv file does not exist " . basename($csv_file)); |
|
} |
|
|
|
// remove the header row, but avoid NULL or false |
|
$ret_ar = (empty($processed_files)) ? $processed_files : array_slice($processed_files, 1); |
|
return $ret_ar; |
|
} // end function |
|
|
|
|
|
/** |
|
* Give an array of files in the storage directories that are not in the csv record |
|
* |
|
* @param string $type -- one of our types |
|
* @return array |
|
*/ |
|
function csv_newfile_list($type) |
|
{ |
|
// |
|
$ar_new = array(); |
|
$tp = csv_file_type($type); |
|
if (!$tp) { |
|
csv_edihist_log('csv_newfile_list: incorrect type ' . $type); |
|
return false; |
|
} |
|
|
|
// |
|
$dir_files = csv_dirfile_list($tp); |
|
$csv_files = csv_processed_files_list($tp); |
|
// |
|
// $dir_files should come first in array_diff() |
|
if (empty($dir_files)) { |
|
$ar_new = array(); |
|
} elseif (empty($csv_files) || is_null($csv_files)) { |
|
$ar_new = $dir_files; |
|
} else { |
|
$ar_new = array_diff($dir_files, $csv_files); |
|
} |
|
|
|
// |
|
return $ar_new; |
|
} |
|
|
|
/** |
|
* Parse 997 IK3 error segment to identify segment causing rejection |
|
* The error segment string is specially created in edih_997_csv_data() |
|
* Simple analysis, but the idea is just to identify the bad segment |
|
* |
|
* @param string error segment from edih_997_csv_data() |
|
* @param bool true if only the 1st segmentID is wanted |
|
* return array|string |
|
*/ |
|
function edih_errseg_parse($err_seg, $id = false) |
|
{ |
|
// ['err_seg'] = '|IK3*segID*segpos*loop*errcode*bht03syn|CTX-IK3*transID*segID*segpos*elempos |
|
// |IK4*elempos*errcode*elem*|CTX-IK4*segID*segpos*elempos |
|
// |
|
// note: multiple IK3 segments are allowed in 997/999 x12 |
|
// |
|
$ret_ar = array(); |
|
if (!$err_seg || strpos($err_seg, 'IK3') === false) { |
|
csv_edihist_log('edih_errseg_parse: invalid argument'); |
|
return $ret_ar; |
|
} |
|
|
|
//'|IK3*segID*segpos*loop*errcode*bht03syn|CTX-IK3*segID*segPos*loopLS*elemPos:compositePos:repPos |
|
// revised: 123456789004*IK3*segID*segpos[*segID*segpos*segID*segpos] |
|
$ik = explode('*', $err_seg); |
|
foreach ($ik as $i => $k) { |
|
switch ((int)$i) { |
|
case 0: |
|
$ret_ar['trace'] = $k; |
|
break; |
|
case 1: |
|
break; // IK3 |
|
case 2: |
|
$ret_ar['id'][] = $k; |
|
break; // segment ID |
|
case 3: |
|
$ret_ar['err'][] = $k; |
|
break; // segment position |
|
case 4: |
|
$ret_ar['id'][] = $k; |
|
break; |
|
case 5: |
|
$ret_ar['err'][] = $k; |
|
break; |
|
case 6: |
|
$ret_ar['id'][] = $k; |
|
break; |
|
case 7: |
|
$ret_ar['err'][] = $k; |
|
break; |
|
} |
|
} |
|
|
|
// |
|
return $ret_ar; |
|
} |
|
|
|
/** |
|
* Order the csv data array according to the csv table heading row |
|
* so the data to be added to csv table rows are correctly ordered |
|
* the supplied data should be in an array with thie structure |
|
* array['icn'] ['file'][i]['key'] ['claim'][i]['key'] ['type']['type'] |
|
* |
|
* @uses csv_table_header() |
|
* |
|
* @param array data_ar data array from edih_XXX_csv_data() |
|
* @return array|bool ordered array or false on error |
|
*/ |
|
function edih_csv_order($csvdata) |
|
{ |
|
// |
|
$wrcsv = array(); |
|
$order_ar = array(); |
|
// |
|
foreach ($csvdata as $icn => $data) { |
|
// [icn]['type']['file']['claim'] |
|
$ft = $data['type']; |
|
$wrcsv[$icn]['type'] = $ft; |
|
// |
|
foreach ($data as $key => $val) { |
|
if ($key == 'type') { |
|
continue; |
|
} |
|
|
|
$order_ar[$icn][$key] = csv_table_header($ft, $key); |
|
$ct = count($order_ar[$icn][$key]); |
|
foreach ($val as $k => $rcrd) { |
|
// |
|
foreach ($order_ar[$icn][$key] as $ky => $vl) { |
|
$wrcsv[$icn][$key][$k][$ky] = $rcrd[$vl]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return $wrcsv; |
|
} |
|
|
|
/** |
|
* insert dashes in ten-digit telephone numbers |
|
* |
|
* @param string $str_val the telephone number |
|
* @return string the telephone number with dashes |
|
*/ |
|
function edih_format_telephone($str_val) |
|
{ |
|
$strtel = (string)$str_val; |
|
$strtel = preg_replace('/\D/', '', $strtel); |
|
if (strlen($strtel) != 10) { |
|
csv_edihist_log('edih_format_telephone: invalid argument: ' . $str_val); |
|
return $str_val; |
|
} else { |
|
$tel = substr($strtel, 0, 3) . "-" . substr($strtel, 3, 3) . "-" . substr($strtel, 6); |
|
} |
|
|
|
return $tel; |
|
} |
|
|
|
/** |
|
* order MM DD YYYY values and insert slashes in eight-digit dates |
|
* |
|
* US MM/DD/YYYY or general YYYY-MM-DD |
|
* |
|
* @param string $str_val the eight-digit date |
|
* @param string $pref if 'US' (default) anything else means YYYY-MM-DD |
|
* @return string the date with slashes |
|
*/ |
|
function edih_format_date($str_val, $pref = "Y-m-d") |
|
{ |
|
$strdt = (string)$str_val; |
|
$strdt = preg_replace('/\D/', '', $strdt); |
|
$dt = ''; |
|
if (strlen($strdt) == 6) { |
|
$tdy = date('Ymd'); |
|
if ($pref == "US") { |
|
// assume mmddyy |
|
$strdt = substr($tdy, 0, 2) . substr($strdt, -2) . substr($strdt, 0, 4); |
|
} else { |
|
// assume yymmdd |
|
$strdt = substr($tdy, 0, 2) . $strdt; |
|
} |
|
} |
|
|
|
if ($pref == "US") { |
|
$dt = substr($strdt, 4, 2) . "/" . substr($strdt, 6) . "/" . substr($strdt, 0, 4); |
|
} else { |
|
$dt = substr($strdt, 0, 4) . "-" . substr($strdt, 4, 2) . "-" . substr($strdt, 6); |
|
} |
|
|
|
return $dt; |
|
} |
|
|
|
/** |
|
* format monetary amounts with two digits after the decimal place |
|
* |
|
* @todo add other formats |
|
* @param string $str_val the amount string |
|
* @return string the telephone number with dashes |
|
*/ |
|
function edih_format_money($str_val) |
|
{ |
|
// |
|
if ($str_val || $str_val === '0') { |
|
$mny = sprintf("$%01.2f", $str_val); |
|
} else { |
|
$mny = $str_val; |
|
} |
|
|
|
return $mny; |
|
} |
|
|
|
/** |
|
* format percentage amounts with % sign |
|
* typical example ".50" from x12 edi segment element |
|
* |
|
* @param string $str_val the amount string |
|
* @return string the value as a percentage |
|
*/ |
|
function edih_format_percent($str_val) |
|
{ |
|
$val = (float)$str_val; |
|
if (is_float($val)) { |
|
$pct = $val * 100 . '%'; |
|
} else { |
|
$pct = $str_val . '%'; |
|
} |
|
|
|
return $pct; |
|
} |
|
|
|
/** |
|
* HTML string for table thead element |
|
* |
|
* @uses csv_table_header() |
|
* @param string |
|
* @param string |
|
* @return string |
|
*/ |
|
function csv_thead_html($file_type, $csv_type, $tblhd = null) |
|
{ |
|
// |
|
if (is_array($tblhd) && count($tblhd)) { |
|
$hvals = $tblhd; |
|
} else { |
|
$hvals = csv_table_header($file_type, $csv_type); |
|
} |
|
|
|
if (is_array($hvals) && count($hvals)) { |
|
$str_html = ''; |
|
} else { |
|
return false; |
|
} |
|
|
|
$str_html .= "<thead>" . PHP_EOL . "<tr>" . PHP_EOL; |
|
foreach ($hvals as $val) { |
|
$str_html .= "<th>" . text($val) . "</th>"; |
|
} |
|
|
|
$str_html .= PHP_EOL . "</tr>" . PHP_EOL . "</thead>" . PHP_EOL; |
|
// |
|
return $str_html; |
|
} |
|
|
|
|
|
/** |
|
* Give the column headings for the csv files |
|
* |
|
* @uses csv_file_type() |
|
* @param string $file_type one of our edi types |
|
* @param string $csv_type either 'file' or 'claim' |
|
* @return array |
|
*/ |
|
function csv_table_header($file_type, $csv_type) |
|
{ |
|
// |
|
$ft = csv_file_type($file_type); |
|
$ct = strpos('|file', $csv_type) ? 'file' : $csv_type; |
|
$ct = strpos('|claim', $ct) ? 'claim' : $ct; |
|
// |
|
$hdr = array(); |
|
if (!$ft || !$ct) { |
|
csv_edihist_log('csv_table_header error: incorrect file [' . $file_type . ']or csv [' . $csv_type . '] type'); |
|
return $hdr; |
|
} |
|
|
|
// |
|
if ($ct === 'file') { |
|
switch ((string)$ft) { |
|
//case 'ack': $hdr = array('Date', 'FileName', 'isa13', 'ta1ctrl', 'Code'); break; |
|
//case 'ebr': $hdr = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch'); break; |
|
//case 'ibr': $hdr = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch'); break; |
|
// |
|
case 'f837': |
|
$hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'x12Partner'); |
|
break; |
|
case 'ta1': |
|
$hdr = array('Date', 'FileName', 'Control', 'Trace', 'Code'); |
|
break; |
|
case 'f997': |
|
$hdr = array('Date', 'FileName', 'Control', 'Trace', 'RspType', 'RejCt'); |
|
break; |
|
case 'f276': |
|
$hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'x12Partner'); |
|
break; |
|
case 'f277': |
|
$hdr = array('Date', 'FileName', 'Control', 'Accept', 'AccAmt', 'Reject', 'RejAmt'); |
|
break; |
|
case 'f270': |
|
$hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'x12Partner'); |
|
break; |
|
case 'f271': |
|
$hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'Reject', 'Payer'); |
|
break; |
|
case 'f278': |
|
$hdr = array('Date', 'FileName', 'Control', 'TrnCount', 'Auth', 'Payer'); |
|
break; |
|
case 'f835': |
|
$hdr = array('Date', 'FileName', 'Control', 'Trace', 'Claim_ct', 'Denied', 'Payer'); |
|
break; |
|
} |
|
} elseif ($ct === 'claim') { |
|
switch ((string)$ft) { |
|
//case 'ebr': $hdr = array('PtName','SvcDate', 'CLM01', 'Status', 'Batch', 'FileName', 'Payer'); break; |
|
//case 'ibr': $hdr = array('PtName','SvcDate', 'CLM01', 'Status', 'Batch', 'FileName', 'Payer'); break; |
|
//case 'dpr': $hdr = array('PtName','SvcDate', 'CLM01', 'Status', 'Batch', 'FileName', 'Payer'); break; |
|
// |
|
case 'f837': |
|
$hdr = array('PtName', 'SvcDate', 'CLM01', 'InsLevel', 'BHT03', 'FileName', 'Fee', 'PtPaid', 'Provider' ); |
|
break; |
|
case 'f997': |
|
$hdr = array('PtName', 'RspDate', 'Trace', 'Status', 'Control', 'FileName', 'RspType', 'err_seg'); |
|
break; |
|
case 'f276': |
|
$hdr = array('PtName', 'SvcDate', 'CLM01', 'ClaimID', 'BHT03', 'FileName', 'Payer', 'Trace'); |
|
break; |
|
case 'f277': |
|
$hdr = array('PtName', 'SvcDate', 'CLM01', 'Status', 'BHT03', 'FileName', 'Payer', 'Trace'); |
|
break; |
|
case 'f270': |
|
$hdr = array('PtName', 'ReqDate', 'Trace', 'InsBnft', 'BHT03', 'FileName', 'Payer'); |
|
break; |
|
case 'f271': |
|
$hdr = array('PtName', 'RspDate', 'Trace', 'Status', 'BHT03', 'FileName', 'Payer'); |
|
break; |
|
case 'f278': |
|
$hdr = array('PtName', 'FileDate', 'Trace', 'Status', 'BHT03', 'FileName', 'Auth', 'Payer'); |
|
break; |
|
case 'f835': |
|
$hdr = array('PtName', 'SvcDate', 'CLM01', 'Status', 'Trace', 'FileName', 'ClaimID', 'Pmt', 'PtResp', 'Payer'); |
|
break; |
|
} |
|
} else { |
|
// unexpected error |
|
csv_edihist_log('edih_csv_table_header() error: failed to match file type [' . $ft . '] or csv type [' . $ct . ']'); |
|
return false; |
|
} |
|
|
|
if (count($hdr)) { |
|
return $hdr; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
/* |
|
function csv_files_header($file_type, $csv_type) { |
|
// |
|
$tp = csv_file_type($type); |
|
if (!$tp) { |
|
csv_edihist_log('csv_files_header: incorrect type '.$file_type); |
|
return false; |
|
} |
|
if (!strpos('|file|claim', $csv_type) ) { |
|
csv_edihist_log('csv_files_header error: incorrect csv type '.$csv_type); |
|
return false; |
|
} |
|
// |
|
$ft = strpos('|277', $file_type) ? 'f277' : $file_type; |
|
$ft = strpos('|835', $file_type) ? 'era' : $ft; |
|
$ft = strpos('|837', $file_type) ? 'batch' : $ft; |
|
$ft = strpos('|999|997|ack|ta1', $file_type) ? 'f997' : $ft; |
|
// |
|
$csv_hd_ar = array(); |
|
// dataTables: | 'date' | 'file_name (link)' | 'file_text (link fmt)' | 'claim_ct' | 'reject_ct' | |
|
$csv_hd_ar['ack']['file'] = array('Date', 'FileName', 'isa13', 'ta1ctrl', 'Code'); |
|
$csv_hd_ar['ebr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch'); |
|
$csv_hd_ar['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch'); |
|
// |
|
// dataTables: | 'date' | 'file_name (link)' | 'file_text (link fmt)' | 'claim_ct' | 'partner' | |
|
$csv_hd_ar['batch']['file'] = array('Date', 'FileName', 'Ctn_837', 'claim_ct', 'x12_partner'); |
|
$csv_hd_ar['ta1']['file'] = array('Date', 'FileName', 'Ctn_ta1', 'ta1ctrl', 'Code'); |
|
$csv_hd_ar['f997']['file'] = array('Date', 'FileName', 'Ctn_999', 'ta1ctrl', 'RejCt'); |
|
$csv_hd_ar['f277']['file'] = array('Date', 'FileName', 'Ctn_277', 'Accept', 'AccAmt', 'Reject', 'RejAmt'); |
|
$csv_hd_ar['f270']['file'] = array('Date', 'FileName', 'Ctn_270', 'claim_ct', 'x12_partner'); |
|
$csv_hd_ar['f271']['file'] = array('Date', 'FileName', 'Ctn_271', 'claim_ct', 'Denied', 'Payer'); |
|
$csv_hd_ar['era']['file'] = array('Date', 'FileName', 'Trace', 'claim_ct', 'Denied', 'Payer'); |
|
// |
|
// dataTables: | 'pt_name' | 'svc_date' | 'clm01 (link clm)' | 'status (mouseover)' | b f t (links to files) | message (mouseover) | |
|
$csv_hd_ar['ebr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer'); |
|
$csv_hd_ar['ibr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer'); |
|
$csv_hd_ar['dpr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer'); |
|
// |
|
// dataTables: | 'pt_name' | 'svc_date' | 'clm01 (link clm)' | 'status (mouseover)' | 'bht03_837 (link rsp)' | message (mouseover) | |
|
$csv_hd_ar['batch']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' ); |
|
$csv_hd_ar['f997']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'File_997', 'Ctn_837', 'err_seg'); |
|
$csv_hd_ar['f277']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_277', 'File_277', 'payer_name', 'claim_id', 'bht03_837'); |
|
$csv_hd_ar['f270']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'st_270', 'File_270', 'payer_name', 'bht03_270'); |
|
$csv_hd_ar['f271']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_271', 'File_271', 'payer_name', 'bht03_270'); |
|
$csv_hd_ar['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer'); |
|
// |
|
return $csv_hd_ar[$ft][$csv_type]; |
|
} |
|
*/ |
|
|
|
/** |
|
* adapted from http://scratch99.com/web-development/javascript/convert-bytes-to-mb-kb/ |
|
* |
|
* @param int |
|
* |
|
* @return string |
|
*/ |
|
function csv_convert_bytes($bytes) |
|
{ |
|
$sizes = array('Bytes', 'KB', 'MB', 'GB', 'TB'); |
|
if ($bytes == 0) { |
|
return 'n/a'; |
|
} |
|
|
|
$i = floor(log($bytes) / log(1024)); |
|
//$i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); |
|
if ($i == 0) { |
|
return $bytes . ' ' . $sizes[$i]; |
|
} else { |
|
return round($bytes / pow(1024, $i), 1) . ' ' . $sizes[$i]; |
|
} |
|
} |
|
|
|
/** |
|
* Determine whether an array is multidimensional |
|
* |
|
* @param array |
|
* @return bool false if arrayis multidimensional |
|
*/ |
|
function csv_singlerecord_test($array) |
|
{ |
|
// the two versions of count() are compared |
|
// if the array has a sub-array, count recursive is greater |
|
if (is_array($array)) { |
|
$is_sngl = count($array, COUNT_RECURSIVE) == count($array, COUNT_NORMAL); |
|
} else { |
|
$is_sngl = false; |
|
} |
|
|
|
// |
|
return $is_sngl; |
|
} |
|
|
|
/* |
|
* give first and last index keys for an array |
|
* |
|
* @param array |
|
* @return array |
|
*/ |
|
function csv_array_bounds($array) |
|
{ |
|
// get the segment array bounds |
|
$ret_ar = array(); |
|
if (is_array($array) && count($array)) { |
|
if (reset($array) !== false) { |
|
$ret_ar[0] = key($array); |
|
} |
|
|
|
if (end($array) !== false) { |
|
$ret_ar[1] = key($array); |
|
} |
|
} |
|
|
|
return $ret_ar; |
|
} |
|
|
|
/* |
|
* return a csv file as an associative array |
|
* the first row is the header or array keys for the row |
|
* array structure: |
|
* array[i]=>array(hdr0=>csvrow[0], hdr1=>csvrow[1], hdr2=>csvrow[2], ...) |
|
* |
|
* @param string file type e.g. f837 |
|
* @param string csv type claim or file |
|
* @return array |
|
*/ |
|
function csv_assoc_array($file_type, $csv_type) |
|
{ |
|
// |
|
if (!$file_type || !$csv_type) { |
|
csv_edihist_log('csv_assoc_array; invalid arguments ft: ' . $file_type . ' csvt: ' . $csv_type); |
|
return false; |
|
} |
|
|
|
$csv_ar = array(); |
|
$h = array(); |
|
$fp = ''; |
|
// |
|
$param = csv_parameters($file_type); |
|
$fcsv = (strpos($csv_type, 'aim')) ? 'claims_csv' : 'files_csv'; |
|
// |
|
$fp = (isset($param[$fcsv])) ? $param[$fcsv] : ''; |
|
if (!is_file($fp)) { |
|
csv_edihist_log('csv_assoc_array; invalid csv file ' . basename($fp)); |
|
return $csv_ar; |
|
} |
|
|
|
$ct = 0; |
|
$row = 0; |
|
$ky = -1; |
|
if (($fh = fopen($fp, "rb")) !== false) { |
|
while (($data = fgetcsv($fh, 2048, ",")) !== false) { |
|
if (is_null($data)) { |
|
continue; |
|
} |
|
|
|
if ($row) { |
|
for ($i = 0; $i < $ct; $i++) { |
|
$csv_ar[$ky][$h[$i]] = $data[$i]; |
|
} |
|
} else { |
|
$ct = count($data); |
|
$h = $data; |
|
} |
|
|
|
$row++; |
|
$ky++; |
|
} |
|
|
|
fclose($fh); |
|
} else { |
|
// invalid file path |
|
csv_edihist_log('csv_assoc_array; invalid file path ' . $fp); |
|
return false; |
|
} |
|
|
|
// |
|
return $csv_ar; |
|
} |
|
|
|
|
|
/** |
|
* A multidimensional array will be flattened to a single row. |
|
* |
|
* @param array $array array to be flattened |
|
* @return array |
|
*/ |
|
function csv_array_flatten($array) |
|
{ |
|
// |
|
if (!is_array($array)) { |
|
return false; |
|
} |
|
|
|
$result = array(); |
|
foreach ($array as $key => $value) { |
|
if (is_array($value)) { |
|
$result = array_merge($result, csv_array_flatten($value)); |
|
} else { |
|
$result[$key] = $value; |
|
} |
|
} |
|
|
|
return $result; |
|
} |
|
|
|
|
|
/** |
|
* Write parsed data from edi x12 files to csv file |
|
* |
|
* @uses csv_parameters() |
|
* @usescsv_table_header() |
|
* |
|
* @param array data array from parse functions |
|
* @return bool true if no error |
|
*/ |
|
function edih_csv_write($csv_data) |
|
{ |
|
// |
|
if (! (is_array($csv_data) && count($csv_data))) { |
|
csv_edihist_log('edih_csv_write(): invalid data array'); |
|
return false; |
|
} |
|
|
|
// |
|
foreach ($csv_data as $icn => $isa) { |
|
// should be array[icn] => [file][j][key] [claim][j][key] [type] |
|
$ft = ( isset($isa['type']) ) ? $isa['type'] : ''; |
|
if (!$ft) { |
|
csv_edihist_log('edih_csv_write(): invalid file type'); |
|
continue; |
|
} |
|
|
|
// |
|
$param = csv_parameters($ft); |
|
$f_hdr = csv_table_header($ft, 'file'); |
|
$c_hdr = csv_table_header($ft, 'claim'); |
|
if (is_array($param)) { |
|
// if either csv files does not exist, create them both |
|
// all unlisted files in type directory will be processed on next process round |
|
if (is_file($param['files_csv']) && (filesize($param['files_csv']) > 20)) { |
|
csv_edihist_log('edih_csv_write: csv check for files csv ' . $ft); |
|
} else { |
|
$nfcsv = $param['files_csv']; |
|
$fh = fopen($nfcsv, 'wb'); |
|
if ($fh !== false) { |
|
fputcsv($fh, $f_hdr); |
|
fclose($fh); |
|
chmod($nfcsv, 0600); |
|
} |
|
|
|
csv_edihist_log('edih_csv_write: created files_csv file for ' . $ft); |
|
} |
|
|
|
if (is_file($param['claims_csv']) && filesize($param['claims_csv'])) { |
|
csv_edihist_log('edih_csv_write: csv check for claims csv ' . $ft); |
|
} else { |
|
$nfcsv = $param['claims_csv']; |
|
$fh = fopen($nfcsv, 'wb'); |
|
if ($fh !== false) { |
|
fputcsv($fh, $c_hdr); |
|
fclose($fh); |
|
chmod($nfcsv, 0600); |
|
} |
|
|
|
csv_edihist_log('edih_csv_write: created claims_csv file for ' . $ft); |
|
} |
|
} else { |
|
csv_edihist_log('edih_csv_write: parameters error for type ' . $ft); |
|
return false; |
|
} |
|
|
|
// |
|
foreach ($isa as $key => $data) { |
|
if ($key == 'type') { |
|
continue; |
|
} |
|
|
|
// get the csv file path from parameters |
|
$fp = ($key == 'file') ? $param['files_csv'] : $param['claims_csv']; |
|
// get the csv row header |
|
$order_ar = ($key == 'file') ? $f_hdr : $c_hdr; |
|
$ct = count($order_ar); |
|
$chrs = 0; |
|
$rws = 0; |
|
// |
|
$fh = fopen($fp, 'ab'); |
|
if (is_resource($fh)) { |
|
// to assure proper order of data in each row, the |
|
// csv row is assembled by matching keys to the header row |
|
foreach ($data as $ky => $row) { |
|
$csvrow = array(); |
|
for ($i = 0; $i < $ct; $i++) { |
|
$csvrow[$i] = $row[$order_ar[$i]]; |
|
} |
|
|
|
$chrs += fputcsv($fh, $csvrow); |
|
$rws++; |
|
} |
|
} else { |
|
csv_edihist_log('edih_csv_write(): failed to open ' . $fp); |
|
return false; |
|
} |
|
|
|
// |
|
csv_edihist_log('edih_csv_write() wrote ' . $rws . ' rows to ' . basename($fp)); |
|
} |
|
} |
|
|
|
// |
|
return $rws; |
|
} |
|
|
|
|
|
/** |
|
* Search a csv record file and return the row or values from selected columns |
|
* |
|
* This function requires that the $search_ar parameter be an array |
|
* with keys ['s_val']['s_col']['r_cols'], and 'r_cols' is an array |
|
* 's_val' is the search value, s_col is the column to check, r_cols is an array |
|
* of column numbers from which values are returned. If r_cols is not an array, |
|
* then the entire row will be returned. If the 'expect' parameter is 1, then |
|
* the search will stop after the first success and return the result. Otherwise, the |
|
* entire file will be searched. |
|
* ex: csv_search_record('batch', 'claim', array('s_val'=>'0024', 's_col'=>9, 'r_cols'=>array(1, 2, 7)), "1" ) |
|
* |
|
* @uses csv_parameters() |
|
* @param string $file_type |
|
* @param string $csv_type |
|
* @param array $search_ar |
|
* @param mixed $expect |
|
* @return array |
|
*/ |
|
function csv_search_record($file_type, $csv_type, $search_ar, $expect = '1') |
|
{ |
|
// |
|
csv_edihist_log("csv_search_record: " . strval($file_type) . " " . strval($csv_type) . " " . strval($search_ar['s_val'])); |
|
// |
|
$tp = csv_file_type($file_type); |
|
if (!$tp) { |
|
csv_edihist_log("csv_search_record: incorrect type $file_type"); |
|
return false; |
|
} |
|
|
|
// |
|
$params = csv_parameters($tp); |
|
// |
|
if ($csv_type == 'claim') { |
|
$fp = $params['claims_csv']; |
|
} elseif ($csv_type == 'file') { |
|
$fp = $params['files_csv']; |
|
} else { |
|
csv_edihist_log('csv_search_record: incorrect csv type ' . $csv_type); |
|
return false; |
|
} |
|
|
|
// |
|
if (!is_array($search_ar) || array_keys($search_ar) != array('s_val', 's_col', 'r_cols')) { |
|
csv_edihist_log('csv_search_record: invalid search criteria'); |
|
return false; |
|
} |
|
|
|
$sv = $search_ar['s_val']; |
|
$sc = $search_ar['s_col']; |
|
$rv = (is_array($search_ar['r_cols']) && count($search_ar['r_cols'])) ? $search_ar['r_cols'] : 'all'; |
|
$ret_ar = array(); |
|
$idx = 0; |
|
if (($fh1 = fopen($fp, "r")) !== false) { |
|
while (($data = fgetcsv($fh1)) !== false) { |
|
// check for a match |
|
if ($data[$sc] == $sv) { |
|
if ($rv == 'all') { |
|
$ret_ar[$idx] = $data; |
|
} else { |
|
// now loop through the 'r_cols' array for data index |
|
$dct = count($data); |
|
foreach ($rv as $c) { |
|
// make sure we don't access a non-existing index |
|
if ($c >= $dct) { |
|
continue; |
|
} |
|
|
|
// |
|
$ret_ar[$idx][] = $data[$c]; |
|
} |
|
} |
|
|
|
$idx++; |
|
if ($expect == '1') { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
fclose($fh1); |
|
} else { |
|
csv_edihist_log('csv_search_record: failed to open ' . $fp); |
|
return false; |
|
} |
|
|
|
if (empty($ret_ar)) { |
|
return false; |
|
} else { |
|
return $ret_ar; |
|
} |
|
} |
|
|
|
/** |
|
* Search the 'claims' csv table for the patient control and find the associated file name |
|
* |
|
* Searchtype |
|
* In 'claims' csv tables, clm01 is position 2, ISA13 number is pos 4, and filename is pos 5; |
|
* Since we are interested usually in the filename, ISA13 is irrelevant usually. |
|
* |
|
* @uses csv_parameters() |
|
* @uses csv_pid_enctr_parse() |
|
* @param string patient control-- pid-encounter, encounter, or pid |
|
* @param string filetype -- x12 type or f837, f277, etc |
|
* @param string search type encounter, pid, or clm01 |
|
* @return array|bool [i] data row array or empty on error |
|
*/ |
|
function csv_file_by_enctr($clm01, $filetype = 'f837') |
|
{ |
|
// |
|
// return array of [i](pid_encounter, filename), there may be more than one file |
|
// |
|
if (!$clm01) { |
|
return 'invalid encounter data<br />' . PHP_EOL; |
|
} |
|
|
|
// |
|
$ret_ar = array(); |
|
$ft = csv_file_type($filetype); |
|
// |
|
if (!$ft) { |
|
csv_edihist_log('csv_file_by_enctr: incorrect file type ' . $filetype); |
|
return $ret_ar; |
|
} else { |
|
$params = csv_parameters($ft); |
|
//$fp = isset($params['claims_csv']) ? dirname(__FILE__).$params['claims_csv'] : false; |
|
$fp = isset($params['claims_csv']) ? $params['claims_csv'] : false; |
|
$h_ar = csv_table_header($ft, 'claim'); |
|
$hct = count($h_ar); |
|
if (!$fp) { |
|
csv_edihist_log('csv_file_by_enctr: incorrect file type ' . $filetype); |
|
return $ret_ar; |
|
} |
|
} |
|
|
|
// |
|
$enct = csv_pid_enctr_parse(strval($clm01)); |
|
$p = (isset($enct['pid'])) ? $enct['pid'] : ''; |
|
$e = (isset($enct['enctr'])) ? $enct['enctr'] : ''; |
|
if ($p && $e) { |
|
$pe = $p . '-' . $e; |
|
$srchtype = ''; |
|
} elseif ($e) { |
|
$srchtype = 'encounter'; |
|
} elseif ($p) { |
|
$srchtype = 'pid'; |
|
} else { |
|
csv_edihist_log('csv_file_by_enctr: unable to determine encounter value ' . $clm01); |
|
return 'unable to determine encounter value ' . text($clm01) . '<br />' . PHP_EOL; |
|
} |
|
|
|
// OpenEMR creates CLM01 as nnn-nnn in genX12 batch |
|
//$pm = preg_match('/\D/', $enctr, $match2, PREG_OFFSET_CAPTURE); |
|
$val = array(); |
|
//array_combine ( array $keys , array $values ) |
|
// in 'claims' csv tables, clm01 is position 2 and filename is position 5 |
|
if (($fh1 = fopen($fp, "r")) !== false) { |
|
if ($srchtype == 'encounter') { |
|
while (($data = fgetcsv($fh1, 1024, ",")) !== false) { |
|
// check for a match |
|
if (strpos($data[2], $e)) { |
|
$te = substr($data[2], strpos($data[2], '-') + 1); |
|
if (strcmp($te, $e) === 0) { |
|
for ($i = 0; $i < $hct; $i++) { |
|
$val[$h_ar[$i]] = $data[$i]; |
|
} |
|
|
|
$ret_ar[] = $val; // array_combine($h_ar, $data); |
|
} |
|
} |
|
} |
|
} elseif ($srchtype == 'pid') { |
|
while (($data = fgetcsv($fh1, 1024, ',')) !== false) { |
|
if (strpos($data[2], $p) !== false) { |
|
$te = (strpos($data[2], '-')) ? substr($data[2], 0, strpos($data[2], '-')) : ''; |
|
if (strcmp($te, $p) === 0) { |
|
for ($i = 0; $i < $hct; $i++) { |
|
$val[$h_ar[$i]] = $data[$i]; |
|
} |
|
|
|
$ret_ar[] = $val; // $ret_ar[] = array_combine($h_ar, $data); |
|
} |
|
} |
|
} |
|
} else { |
|
while (($data = fgetcsv($fh1, 1024, ",")) !== false) { |
|
// check for a match |
|
if (strcmp($data[2], $pe) === 0) { |
|
for ($i = 0; $i < $hct; $i++) { |
|
$val[$h_ar[$i]] = $data[$i]; |
|
} |
|
|
|
$ret_ar[] = $val; // $ret_ar[] = array_combine($h_ar, $data); |
|
} |
|
} |
|
} |
|
|
|
fclose($fh1); |
|
} else { |
|
csv_edihist_log('csv_file_by_enctr: failed to open csv file ' . basename($fp)); |
|
return false; |
|
} |
|
|
|
return $ret_ar; |
|
} |
|
|
|
|
|
/** |
|
* get the x12 file containing the control_num ISA13 |
|
* |
|
* @todo the csv for x12 files 999, 277, 835, 837 must have the control number |
|
* |
|
* @uses csv_search_record() |
|
* @param string $control_num the interchange control number, isa13 |
|
* @return string the file name |
|
*/ |
|
function csv_file_by_controlnum($type, $control_num) |
|
{ |
|
// get the batch file containing the control_num |
|
// |
|
$tp = csv_file_type($type); |
|
// |
|
$hdr = csv_table_header($tp, 'file'); |
|
$scol = array_search('Control', $hdr); |
|
$rcol = array_search('FileName', $hdr); |
|
// |
|
// $search_ar should have keys ['s_val']['s_col'] array(['r_cols'][]) |
|
// like "batch', 'claim, array(9, '0024', array(1, 2, 7)) |
|
//$csv_hd_ar['batch']['file'] = array('time', 'file_name', 'control_num', 'claims', 'x12_partner', 'x12_version'); |
|
// |
|
$fn = ''; |
|
$ctln = (strlen($control_num) >= 9) ? substr($control_num, 0, 9) : $control_num; |
|
$search = array('s_val' => $ctln, 's_col' => $scol, 'r_cols' => array($rcol)); |
|
$result = csv_search_record($tp, 'file', $search, "1"); |
|
if (is_array($result) && count($result[0]) == 1) { |
|
$fn = $result[0][0]; |
|
} |
|
|
|
return $fn; |
|
} |
|
|
|
|
|
/** |
|
* Search the csv table to obtain the file name for a given |
|
* trace value (835 / 997 999 type only) |
|
* |
|
* Note: the 997/999 trace is the ISA13 of a batch file |
|
* |
|
* |
|
* @param string trace value (TRN02, TA101, or BHT03) |
|
* @param string from type (default is f835) |
|
* @param string to type (default is f835) |
|
* @return string file name or empty string |
|
*/ |
|
function csv_file_by_trace($trace, $from_type = 'f835', $to_type = 'f837') |
|
{ |
|
// get the file referenced by the trace value |
|
// |
|
$ft = ($from_type) ? csv_file_type($from_type) : ''; |
|
$tt = ($to_type) ? csv_file_type($to_type) : ''; |
|
$fn = ''; |
|
$csv_type = ''; |
|
$type = ''; |
|
$search = array(); |
|
// |
|
csv_edihist_log("csv_file_by_trace: $trace from $ft to $tt"); |
|
// |
|
// $search_ar should have keys ['s_val']['s_col'] array(['r_cols']) |
|
// like "f837', 'claim, array(9, '0024', array(1, 2, 7)) |
|
// |
|
if ($ft == 'f835') { |
|
// trace payment to status or claim |
|
$search = array('s_val' => $trace, 's_col' => 3, 'r_cols' => 'All'); |
|
$type = $tt; |
|
$csv_type = 'file'; |
|
} elseif ($ft == 'f997') { |
|
// trace ACK to batch file |
|
$icn = (is_numeric($trace) && strlen($trace) >= 9) ? substr($trace, 0, 9) : $trace; |
|
$search = array('s_val' => $icn, 's_col' => 2, 'r_cols' => 'All'); |
|
$type = $tt; |
|
$csv_type = 'file'; |
|
} elseif ($ft == 'f277') { |
|
// trace status to status req or claim |
|
if ($tt == 'f276') { |
|
$search = array('s_val' => $trace, 's_col' => 7, 'r_cols' => 'All'); |
|
$type = $tt; |
|
$csv_type = 'claim'; |
|
} elseif ($tt == 'f837') { |
|
// expect CLM01 for trace value |
|
$search = array('s_val' => $trace, 's_col' => 2, 'r_cols' => 'All'); |
|
$type = $tt; |
|
$csv_type = 'claim'; |
|
} |
|
} elseif ($ft == 'f271') { |
|
// trace benefit to benefit req |
|
if ($tt == 'f270') { |
|
$search = array('s_val' => $trace, 's_col' => 2, 'r_cols' => 'All'); |
|
$type = $tt; |
|
$csv_type = 'claim'; |
|
} |
|
} elseif ($ft == 'f278') { |
|
// trace auth to auth req |
|
$search = array('s_val' => $trace, 's_col' => 2, 'r_cols' => 'All'); |
|
$type = 'f278'; |
|
$csv_type = 'claim'; |
|
} else { |
|
csv_edihist_log('csv_file_by_trace: incorrect file type ' . $file_type); |
|
return $fn; |
|
} |
|
|
|
// |
|
if ($type && $csv_type && $search) { |
|
$result = csv_search_record($type, $csv_type, $search, false); |
|
if (is_array($result) && count($result)) { |
|
if ($ft == 'f278') { |
|
foreach ($result as $r) { |
|
if ($r[6] == 'Rsp' || $r[6] == 'Reply') { |
|
$fn = $result[0][5]; |
|
break; |
|
} |
|
} |
|
} elseif ($csv_type == 'claim') { |
|
$fn = $result[0][5]; |
|
} else { |
|
$fn = $result[0][1]; |
|
} |
|
} else { |
|
csv_edihist_log("csv_file_by_trace: search failed $type csv $csv_type for trace $trace $from_type $to_type"); |
|
} |
|
} else { |
|
csv_edihist_log("csv_file_by_trace: error type $type csv $csv_type for trace $trace $from_type $to_type"); |
|
} |
|
|
|
return $fn; |
|
} |
|
|
|
/** |
|
* list claim records with Denied or Reject status in given file |
|
* |
|
* @param string |
|
* @param string |
|
* |
|
* @return array |
|
*/ |
|
function csv_denied_by_file($filetype, $filename, $trace = '') |
|
{ |
|
// |
|
$ret_ar = array(); |
|
$ft = csv_file_type($filetype); |
|
if (strpos('|f997|f271|f277|f835', $ft)) { |
|
$param = csv_parameters($ft); |
|
$csv_file = $param['claims_csv']; |
|
} else { |
|
csv_edihist_log("csv_errors_by_file: incorrect file type $filetype"); |
|
return $ret_ar; |
|
} |
|
|
|
// |
|
csv_edihist_log("csv_errors_by_file: $ft searching $filename with trace $trace"); |
|
// |
|
if (($fh1 = fopen($csv_file, "r")) !== false) { |
|
if ($ft == 'f835') { |
|
while (($data = fgetcsv($fh1, 1024, ",")) !== false) { |
|
// check filename, then status |
|
if ($trace) { |
|
if ($data[4] == $trace) { |
|
if (!in_array($data[3], array('1', '2', '3', '19', '20', '21'))) { |
|
$ret_ar[] = $data; |
|
} |
|
} |
|
} elseif ($data[5] == $filename) { |
|
if (!in_array($data[3], array('1', '2', '3', '19', '20', '21'))) { |
|
$ret_ar[] = $data; |
|
} |
|
} |
|
} |
|
} elseif ($ft == 'f277') { |
|
while (($data = fgetcsv($fh1, 1024, ",")) !== false) { |
|
if ($data[5] == $filename) { |
|
if (!strpos('|A1|A2|A5', substr($data[3], 0, 2))) { |
|
$ret_ar[] = $data; |
|
} |
|
} |
|
} |
|
} elseif (strpos('|f997|f999|f271', $ft)) { |
|
while (($data = fgetcsv($fh1, 1024, ",")) !== false) { |
|
if ($data[5] == $filename) { |
|
if ($data[3] !== 'A') { |
|
$ret_ar[] = $data; |
|
} |
|
} |
|
} |
|
} else { |
|
csv_edihist_log("csv_errors_by_file: file type did not match $filetype"); |
|
} |
|
|
|
fclose($fh1); |
|
} |
|
|
|
// |
|
return $ret_ar; |
|
} |
|
|
|
|
|
/** |
|
* A function to try and assure the pid-encounter is correctly parsed |
|
* |
|
* assume a format of pid-encounter, since that is sent in the OpenEMR x12 837 |
|
* |
|
* @param string $pid_enctr the value from element CLM01 |
|
* return array array('pid' => $pid, 'enctr' => $enc) |
|
*/ |
|
function csv_pid_enctr_parse($pid_enctr) |
|
{ |
|
// evaluate the patient account field |
|
// |
|
if (!$pid_enctr || !is_string($pid_enctr)) { |
|
csv_edihist_log("csv_pid_enctr_parse: invalid argument"); |
|
return false; |
|
} |
|
|
|
$pval = trim($pid_enctr); |
|
if (strpos($pval, '-')) { |
|
$pid = substr($pval, 0, strpos($pval, '-')); |
|
$enc = substr($pval, strpos($pval, '-') + 1); |
|
} elseif (ctype_digit($pval)) { |
|
if (preg_match('/(19|20)\d{2}[01]\d{1}[0-3]\d{1}/', $pval)) { |
|
$enc = $pval; |
|
} else { |
|
$enc = ( strlen($pval) ) >= ENCOUNTER_MIN_DIGIT_LENGTH ? $pval : ''; |
|
$pid = ''; |
|
} |
|
} elseif (preg_match('/\D/', $pval, $match2, PREG_OFFSET_CAPTURE)) { |
|
$inv_split = (count($match2)) ? preg_split('/\D/', $pval, 2, PREG_SPLIT_NO_EMPTY) : false; |
|
if ($inv_split) { |
|
$pid = $inv_split[0]; |
|
$enc = $inv_split[1]; |
|
} |
|
} else { |
|
$enc = ( strlen($pval) ) >= ENCOUNTER_MIN_DIGIT_LENGTH ? $pval : ''; |
|
$pid = ''; |
|
} |
|
|
|
return array('pid' => $pid, 'enctr' => $enc); |
|
}
|
|
|