5 * Copyright 2012 Kevin McCormick
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; version 3 or later. You should have
15 * received a copy of the GNU General Public License along with this program;
16 * if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * <http://opensource.org/licenses/gpl-license.php>
20 * @author Kevin McCormick
21 * @link: http://www.open-emr.org
23 * @subpackage ediHistory
27 * The purpose of this file is to hold functions of general utility for
28 * my edi_claim_history project. It began as a php "class" but I am now
29 * thinking that instantiating the class is too much bother and probably
30 * a waste of memory, since the contents of the file have to be read into
34 * ******* important *********
35 * function csv_parameters($type="ALL")
36 * This function must have the correct values or nothing will work
37 * function csv_verify_file( $file_path, $type, $val_array=FALSE )
38 * critical for file verification and x12 parsing
39 * function (in ibr_uploads.php) ibr_upload_match_file($param_ar, $fidx, &$html_str)
40 * contains a regular expression that must be correct
42 * Also, the constant IBR_HISTORY_DIR must be correct
43 * **************************
46 * The claim_history x12 files are claim (837) acknowledgement (997/999) claim status (277) and claim payment (835)
47 * Also eligibility request (270) and eligibility response (271)
51 * Each file type has a row in the array from csv_paramaters()
52 * type directory files_csv claims_csv column regex
54 * 1. open submitted file in edih_x12_class to verify and produce properties
55 * 2. Read the parameters array and choose the parameters using 'type'
56 * 2. Search the matched type 'directory' for the filename files matching the 'regex' regular expressions and
57 * compare the results to the files listed in the 'files_csv' files.csv record -- unmatched files are "new"
58 * 3. Each "new" x12 file should be read by csv_x12_segments -- returns array('path', 'delimiters', 'segments')
59 * ibr, ebr, ack -- basically Availity formats have their own read functions
60 * 4. Pass the array to various functions which parse for claims information
61 * 5. Write the results to files.csv or claims.csv and create html output for display
63 * 6. Other outputs as called for in ibr_history.php -- from user input from claim_history.html
66 * Key usability issue is the "new" files are in the users home directory -- downloaded there
67 * while the OpenEMR is on the server -- so there is a basic issue of access to the files
69 * The ibr_uploads.php script handles uploads of zip archives or multiple file uploads
71 * The csv data files are just php written .csv files, so anything different may cause errors
72 * You can open and edit them in OpenOffice, but you must save them in "original format"
74 * TO_DO Some type of "find in files" search would be helpful for locating all references to a claim, patient, etc.
75 * [ grep -nHIrF 'findtext']
77 * TO_DO functions to zip old files, put them aside, and remove them from csv tables
81 // * a security measure to prevent direct web access to this file
83 // if (!defined('SITE_IN')) die('Direct access not allowed!');
84 // $GLOBALS['OE_EDIH_DIR'] $GLOBALS['OE_SITE_DIR']
86 /* *********** GLOBALS used for testing only **********
88 // //$GLOBALS['OE_SITE_DIR'].'/edi/history';
89 //$OE_SITES_BASE = $GLOBALS['OE_SITE_DIR'];
90 //$OE_SITE_DIR = $OE_SITES_BASE.'/testing';
91 //$OE_EDIH_DIR = $OE_SITE_DIR.'/edi/history';
96 * Constant that is checked in included files to prevent direct access.
97 * concept taken from Joomla
100 //DIRECTORY_SEPARATOR;
101 if (!defined('DS')) define('DS', DIRECTORY_SEPARATOR
);
104 * Log messages to the log file
106 * @param string $msg_str the log message
107 * @return int number of characters written
109 function csv_edihist_log ( $msg_str ) {
111 //$dir = dirname(__FILE__).DS.'log';
112 //$dir = $GLOBALS['OE_EDIH_DIR'].DS.'log';
113 //$logfile = $GLOBALS['OE_EDIH_DIR'] . "/log/edi_history_log.txt";
114 $logfile = 'edih_log_'.date('Y-m-d').'.txt';
115 $dir = csv_edih_basedir().DS
.'log';
117 if ( is_string($msg_str) && strlen($msg_str) ) {
118 $tm = date('Ymd:Hms') . ' ' . $msg_str . PHP_EOL
;
120 $rslt = file_put_contents($dir.DS
.$logfile, $tm, FILE_APPEND
);
123 $fnctn = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS
, 2)[1]['function'];
124 csv_edihist_log ('invalid message string '.$fnctn);
127 return $rslt; // number of characters written
131 * read the edi_history_log.txt file into an
132 * html formatted ordered list
136 function csv_log_html($logname='') {
137 $html_str = "<div class='filetext'>".PHP_EOL
."<ol class='logview'>".PHP_EOL
;
138 $fp = csv_edih_basedir().DS
.'log'.DS
.$logname;
139 if ( is_file($fp) ) {
140 $fh = fopen( $fp, 'r');
142 while (($buffer = fgets($fh)) !== false) {
143 $html_str .= "<li>".$buffer."</li>".PHP_EOL
;
145 $html_str .= "</ol>".PHP_EOL
."</div>".PHP_EOL
;
147 $html_str .= "<p>Error in logfile: unexpected file ending</p>".PHP_EOL
;
151 $html_str = "<p>Error: unable to open log file</p>".PHP_EOL
;
159 * list log files and store old logs in an archive
162 * @return array (json)
164 function csv_log_manage($list=true) {
166 //$dir = dirname(__FILE__).DS.'log';
167 $dir = csv_edih_basedir().DS
.'log';
170 $lognames = scandir($dir);
172 foreach($lognames as $log) {
173 if (!strpos($log, '_log_')) { continue; }
176 $s = (count($list_ar)) ?
rsort($list_ar) : false;
178 return json_encode($list_ar);
181 // list is false, must be archive
182 $datetime1 = date_create(date('Y-m-d'));
184 foreach($lognames as $log) {
185 if ($log == '.' ||
$log == '..') { continue; }
187 $pos1 = strrpos($log, '_');
189 $ldate = substr($log, $pos1+
1, 10);
190 $datetime2 = date_create($ldate);
191 $interval = date_diff($datetime1, $datetime2);
192 //echo '== date difference '.$ldate.' '.$interval->format('%R%a days').PHP_EOL;
193 if ($interval->format('%R%a') < -7) {
194 // older log files are put in zip archive
195 if ( is_file($dir.DS
.$log) ) { $old_ar[] = $log; }
202 $archname = $dir.DS
.'edih-log-archive.zip';
205 if (count($old_ar)) {
206 $zip = new ZipArchive
;
207 if (is_file($archname)) {
208 $ok = $zip->open($archname, ZipArchive
::CHECKCONS
);
210 $ok = $zip->open($archname, ZipArchive
::CREATE
);
214 if ($zip->numFiles
>= $filelimit) {
216 $dte = $datetime1->format('Ymd');
217 $ok = rename($dir.DS
.$archname, $dir.DS
.$dte.'_'.$archname);
218 csv_edihist_log('csv_log_archive: rename full archive '.$dte.'_'.$archname );
220 $ok = $zip->open($archname, ZipArchive
::CREATE
);
222 csv_edihist_log('csv_log_archive: cannot create '.$archname);
225 csv_edihist_log('csv_log_archive: cannot rename '.$archname);
230 foreach($old_ar as $lg) {
231 if (is_file($dir.DS
.$lg)) {
232 $a = $zip->addFile($dir.DS
.$lg, $lg);
234 csv_edihist_log('csv_log_archive: add to archive '.$lg );
236 csv_edihist_log('csv_log_archive: error archiving '.$lg );
242 foreach($old_ar as $lg) {
243 $u = unlink($dir.DS
.$lg);
247 csv_edihist_log('csv_log_archive: error removing '.$dir.DS
.$lg);
251 csv_edihist_log('csv_log_archive: error closing log file archive');
254 csv_edihist_log('csv_log_manage: error failed to open '.$archname);
259 return json_encode($old_ar);
264 * open or save a user notes file
270 function csv_notes_file($content='', $open=true) {
273 //$fp = $GLOBALS['OE_EDIH_DIR'].'/edi_notes.txt';
274 $fp = csv_edih_basedir().DS
.'archive'.DS
.'edi_notes.txt';
275 if (! is_writable($fp) ) {
276 $fh = fopen( $fp, 'a+b');
279 // for retrieving notes
281 // if contents were previously deleted by user and file is empty,
282 // the text 'empty' is put in content in save operation
283 $ftxt = file_get_contents($fp);
284 if ($ftxt === false) {
285 $str_html .= 'csv_notes_file: file error <br>'.PHP_EOL
;
286 csv_edihist_log('csv_notes_file: file error');
288 if (substr($ftxt, 0, 5) == 'empty' && strlen($ftxt) == 5) {
289 $ftxt = '## '. date("F j, Y, g:i a");
291 $ftxt = '## '. date("F j, Y, g:i a");
293 $str_html .= PHP_EOL
.$ftxt.PHP_EOL
;
294 // next stanza for saving content
295 } elseif (strlen($content)) {
296 //echo "csv_notes_file: we have content<br>".PHP_EOL;
297 // use finfo php class
298 if ( class_exists('finfo') ) {
299 $finfo = new finfo(FILEINFO_MIME
);
300 $mimeinfo = $finfo->buffer($content);
301 if ( strncmp($mimeinfo, 'text/plain; charset=us-ascii', 28) !== 0 ) {
302 csv_edihist_log('csv_notes_file: invalid mime-type '.$mimeinfo);
303 $str_html = 'csv_notes_file: invalid mime-type <br>'.$mimeinfo;
307 } elseif (preg_match('/[^\x20-\x7E\x0A\x0D]|(<\?)|(<%)|(<asp)|(<ASP)|(#!)|(\$\{)|(<scr)|(<SCR)/', $content, $matches, PREG_OFFSET_CAPTURE
)) {
308 csv_edihist_log('csv_notes_file: Filtered character in file content -- character: '.$matches[0][0].' position: '.$matches[0][1]);
309 $str_html .= 'Filtered character in file content not accepted <br>'. PHP_EOL
;
310 $str_html .= ' character: ' . $matches[0][0] . ' position: ' . $matches[0][1] . '<br>' . PHP_EOL
;
315 $ftxt = ($content) ?
$content : 'empty';
316 $saved = file_put_contents($fp, $ftxt);
317 $str_html .= ($saved) ?
'<p>Save Error with notes file</p>' : '<p>Notes content saved</p>';
324 * generates path to edi history files
326 * @return string|bool directory path
328 function csv_edih_basedir() {
329 // should be something like /var/www/htdocs/openemr/sites/default
330 if (isset($GLOBALS['OE_SITE_DIR'])) {
332 //echo 'csv_edih_basedir OE_SITE_DIR '.$GLOBALS['OE_SITE_DIR'].'<br>'.PHP_EOL;
333 return $GLOBALS['OE_SITE_DIR'].DS
.'edi'.DS
.'history';
335 csv_edihist_log('csv_edih_basedir: failed to obtain OpenEMR Site directory');
341 * generates path to edi_history tmp dir for file upload operations
343 * @uses csv_edih_basedir()
344 * @return string directory path
346 function csv_edih_tmpdir() {
348 $bdir = csv_edih_basedir();
349 $tdir = ($bdir) ?
$bdir.DS
.'tmp' : false;
350 //$systmp = sys_get_temp_dir();
351 //$systmp = stripcslashes($systmp);
352 //$systdir = $systmp.DS.'edihist';
353 //if ( $tdir && (is_dir($tdir) || mkdir($tdir, 0755) ) ) {
364 * Initial setup function
366 * Create the directory tree and write the column headers into the csv files
367 * This function will accept a directory argument and it appends the value
368 * from IBR_HISTORY_DIR to the path. Then a directory for each type of file
369 * and the csv files are created under that.
371 * @uses csv_parameters()
372 * @uses csv_table_header()
373 * @uses csv_edih_basedir()
375 * @param string &$out_str referenced, should be created in calling function
378 function csv_setup() {
383 // $GLOBALS['OE_SITE_DIR'] should be like /var/www/htdocs/openemr/sites/default
384 $sitedir = $GLOBALS['OE_SITE_DIR'];
385 //$sitedir = csv_edih_basedir();
387 if (is_readable($sitedir)) {
388 $basedir = $sitedir.DS
.'edi';
389 $edihist_dir = $basedir.DS
.'history';
390 $csv_dir = $edihist_dir.DS
.'csv';
391 $archive_dir = $edihist_dir.DS
.'archive';
392 $log_dir = $edihist_dir.DS
.'log';
393 $tmp_dir = $edihist_dir.DS
.'tmp';
395 //csv_edihist_log('setup: failed to obtain OpenEMR Site directory');
396 echo 'setup: failed to obtain OpenEMR Site directory<br>'.PHP_EOL
;
400 if (is_writable($basedir) ) {
402 //csv_edihist_log('setup: directory '.$basedir);
403 $out_str .= 'EDI_History Setup should not overwrite existing data.<br>'.PHP_EOL
;
404 $out_str .= 'Setup: directory '.$basedir.'<br>'.PHP_EOL
;
406 if (is_dir($edihist_dir) ||
mkdir($edihist_dir, 0755)) {
407 $out_str .= 'created folder '.$edihist_dir.'<br>'.PHP_EOL
;
409 if (is_dir($csv_dir) ||
mkdir($csv_dir, 0755) ) {
410 $out_str .= 'created folder '.$csv_dir.'<br>'.PHP_EOL
;
414 $out_str .= 'Setup: Failed to create csv folder... '.'<br>'.PHP_EOL
;
415 die('Failed to create csv folder... '.$archive_dir);
417 if (is_dir($archive_dir) ||
mkdir($archive_dir, 0755) ) {
418 $out_str .= 'created folder '.$archive_dir.'<br>'.PHP_EOL
;
422 $out_str .= 'Setup: Failed to create archive folder... '.'<br>'.PHP_EOL
;
423 die('Failed to create archive folder... ');
425 if (is_dir($log_dir) ||
mkdir($log_dir, 0755) ) {
426 $out_str .= 'created folder '.$log_dir.'<br>'.PHP_EOL
;
430 $out_str .= 'Setup: Failed to create log folder... '.'<br>'.PHP_EOL
;
431 die('Failed to create log folder... ');
433 if (is_dir($tmp_dir) ||
mkdir($tmp_dir, 0755) ) {
434 $out_str .= 'created folder '.$tmp_dir.PHP_EOL
;
438 $out_str .= 'Setup: Failed to create tmp folder... '.'<br>'.PHP_EOL
;
439 die('Failed to create tmp folder... ');
443 $out_str .= 'Setup failed: cannot write to folder '.$basedir.'<br>'.PHP_EOL
;
444 die('Setup failed: cannot write to '.$basedir);
448 $out_str .= 'Setup: Failed to create history folder... '.'<br>'.PHP_EOL
;
449 die('Failed to create history folder... '.$edihist_dir);
452 $p_ar = csv_parameters('ALL');
453 $old_csv = array('f837'=>'batch', 'f835'=>'era');
454 foreach ($p_ar as $key=>$val) {
455 // rename existing csv files to old_filename
456 if (is_dir($csv_dir)) {
457 if ($dh = opendir($csv_dir)) {
458 while (($file = readdir($dh)) !== false) {
459 if (is_file($csv_dir.DS
.$file) && strpos($file, 'csv')) {
460 $rn = rename($csv_dir.DS
.$file, $csv_dir.DS
.'old_'.$file);
462 $out_str .= 'renamed csv/'.$file.' to old_'.$file.'<br />'.PHP_EOL
;
464 $out_str .= 'attempt to rename csv/'.$file.' failed<br />'.PHP_EOL
;
471 // make the edi files storage subdirs
472 $tp = $p_ar[$key]['type'];
473 $type_dir = $p_ar[$key]['directory'];
475 if (is_dir($type_dir)) {
476 $out_str .= 'folder for '.$tp.' exists '.$type_dir.'<br>'.PHP_EOL
;
477 } elseif (mkdir($type_dir, 0755)) {
479 // in upgrade case the f835 directory should not exist
480 // move 'era' files from /era to /f835
481 if (is_dir($edihist_dir.DS
.'era')) {
483 if ($dh = opendir($edihist_dir.DS
.'era')) {
484 while (($file = readdir($dh)) !== false) {
485 if (is_file($edihist_dir.DS
.'era'.DS
.$file)) {
487 $rn = rename($edihist_dir.DS
.'era'.DS
.$file, $type_dir.DS
.$file);
488 $fct = ($rn) ?
$fct +
1 : $fct;
492 $out_str .= 'created type folder '.$type_dir.' and moved '.$fct.' of '.$rct.' files from /era<br>'.PHP_EOL
;
495 $out_str .= 'created type folder '.$type_dir.'<br>'.PHP_EOL
;
499 $out_str .= 'Setup failed to create directory for '.$tp.'<br>'.PHP_EOL
;
503 $out_str .= 'Setup failed: Can not create directories <br>'.PHP_EOL
;
507 csv_edihist_log($out_str);
516 * Empty all contents of tmp dir /edi/history/tmp
518 * @uses csv_edih_tmpdir()
522 function csv_clear_tmpdir() {
524 $tmpdir = csv_edih_tmpdir();
525 if ( basename($tmpdir) != 'tmp' ) {
526 csv_edihist_log ( 'tmp dir not /edi/history/tmp');
529 $tmp_files = scandir($tmpdir);
530 if (count($tmp_files) > 2) {
531 foreach($tmp_files as $idx=>$tmpf) {
532 if ($tmpf == "." ||
$tmpf == "..") {
533 // can't delete . and ..
535 } elseif (is_file($tmpdir.DS
.$tmpf) ) {
536 unlink($tmpdir.DS
.$tmpf);
537 } elseif(is_dir($tmpdir.DS
.$tmpf)) {
538 $tdir_ar = scandir($tmpdir.DS
.$tmpf);
539 foreach($tdir_ar as $tfn) {
540 if ($tfn == "." ||
$tfn == "..") {
542 } elseif (is_file($tmpdir.DS
.$tmpf.DS
.$tfn)) {
543 unlink($tmpdir.DS
.$tmpf.DS
.$tfn);
546 rmdir($tmpdir.DS
.$tmpf);
550 $tmp_files = scandir($tmpdir);
551 if (count($tmp_files) > 2) {
552 csv_edihist_log ('tmp dir contents remain in ... /edi/history/tmp');
560 * open and verify a default edih_x12_file object
562 * @uses csv_check_filepath()
564 * @param string filepath or filename
565 * @parm string file x12 type
566 * @return object edih_x12_file class
568 function csv_check_x12_obj($filepath, $type='') {
573 $fp = csv_check_filepath($filepath, $type);
576 $x12obj = new edih_x12_file($fp);
577 if ( 'edih_x12_file' == get_class($x12obj) ) {
578 if ($x12obj->edih_valid() == 'ovigs') {
579 $ok = count( $x12obj->edih_segments() );
580 $ok = ($ok) ?
count( $x12obj->edih_envelopes() ) : false;
581 $ok = ($ok) ?
count( $x12obj->edih_delimiters() ) : false;
583 csv_edihist_log("csv_check_x12_obj: object missing properties [$filepath]");
584 csv_edihist_log( $x12obj->edih_message() );
588 csv_edihist_log("csv_check_x12_obj: invalid object $filepath");
592 csv_edihist_log("csv_check_x12_obj: object not edih_x12_file $filepath");
596 csv_edihist_log("csv_check_x12_obj: invalid file path $filepath");
604 * Check that the file path we are working with is a readable file.
606 * If it is a file we have uploaded and we have only the file name
607 * this function will type the file and find it in the uploaded files directories
608 * and return the complete path.
610 * @uses csv_parameters()
611 * @param string $filename name of a file that is one of our types
612 * @param string $type optional; one of our file types
613 * @return string either an empty string or a readable filepath
615 function csv_check_filepath($filename, $type='ALL') {
617 // if file is readable, just return it
618 if ( is_file($filename) && is_readable($filename) ) {
624 $fn = basename($filename);
626 if ($type && $type != 'ALL') {
627 $p = csv_parameters($type);
628 if (is_array($p) && array_key_exists('type', $p) ) {
629 $fp = $p['directory'].DS
.$fn;
632 $p_ar = csv_parameters("ALL");
633 foreach ($p_ar as $tp=>$par) {
634 if ( !$p_ar[$tp]['regex'] ||
!preg_match($p_ar[$tp]['regex'], $fn) ) {
637 $fp = $p_ar[$tp]['directory'].DS
.$fn;
642 if ( is_file($fp) && is_readable($fp) ) { $goodpath = realpath($fp); }
648 * verify file type parameter
650 * @param string file type
651 * @param bool return GS02 code or fXXX
652 * @return string file type or empty
654 function csv_file_type($type, $gs_code=false) {
657 csv_edihist_log('csv_file_type: invalid or missing type argument '.$type);
660 $tp_type = (string)$type;
663 if ( strpos('|f837|batch|HC', $tp_type) ) {
664 $tp = ($gs_code) ?
'HC' : 'f837';
665 } elseif ( strpos('|f835|era|HP', $tp_type) ) {
666 $tp = ($gs_code) ?
'HP' : 'f835';
667 } elseif ( strpos('|f999|f997|ack|ta1|FA', $tp_type) ) {
668 $tp = ($gs_code) ?
'FA' : 'f997';
669 } elseif ( strpos('|f277|HN', $tp_type) ) {
670 $tp = ($gs_code) ?
'HN' : 'f277';
671 } elseif ( strpos('|f276|HR', $tp_type) ) {
672 $tp = ($gs_code) ?
'HR' : 'f276';
673 } elseif ( strpos('|f271|HB', $tp_type) ) {
674 $tp = ($gs_code) ?
'HB' : 'f271';
675 } elseif ( strpos('|f270|HS', $tp_type) ) {
676 $tp = ($gs_code) ?
'HS' : 'f270';
677 } elseif ( strpos('|f278|HI', $tp_type) ) {
678 $tp = ($gs_code) ?
'HI' : 'f278';
684 csv_edihist_log('csv_file_type error: incorrect type '.$tp_type);
691 * The array that holds the various parameters used in dealing with files
693 * A key function since it holds the paths, columns, etc.
694 * Unfortunately, there is an issue with matching the type in * the case of the
695 * values '997', '277', '999', etc, becasue these strings may be recast
696 * from strings to integers, so the 'type' originally supplied is lost.
697 * This introduces an inconsistency when the 'type' is used in comparison tests.
698 * We call the csv_file_type() function to return a usable file type identifier.
699 * The 'datecolumn' and 'fncolumn' entries are used in csv_to_html() to filter by date
700 * or place links to files.
702 * @param string $type -- default = ALL or one of batch, ibr, ebr, dpr, f997, f277, era, ack, text
705 function csv_parameters($type='ALL') {
707 // This will need the OpenEMR 'oe_site_dir' to replace global
711 $tp = ($type === 'ALL') ?
$type : csv_file_type($type);
713 csv_edihist_log('csv_parameters() error: incorrect type '.$type);
716 //$edihist_dir = $GLOBALS['OE_SITE_DIR'].'/edi/history';
717 $edihist_dir = csv_edih_basedir();
719 // the batch file directory is a special case - decide whether to use OpenEMR batch files or make our own copies
720 // OpenEMR copies each batch file to sites/default/edi and this project never writes to that directory
721 // 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$/'
723 $p_ar['f837'] = array('type'=>'f837', 'directory'=>$GLOBALS['OE_SITE_DIR'].DS
.'edi', 'claims_csv'=>$edihist_dir.DS
.'csv'.DS
.'claims_f837.csv',
724 'files_csv'=>$edihist_dir.DS
.'csv'.DS
.'files_f837.csv', 'filedate'=>'Date', 'claimdate'=>'SvcDate', 'regex'=>'/\-batch(.*)\.txt$/');
726 //$p_ar['csv'] = array("type"=>'csv', "directory"=>$edihist_dir.'/csv', "claims_csv"=>'ibr_parameters.csv',
727 // "files_csv"=>'', "column"=>'', "regex"=>'/\.csv$/');
728 $p_ar['f997'] = array('type'=>'f997', 'directory'=>$edihist_dir.DS
.'f997', 'claims_csv'=>$edihist_dir.DS
.'csv'.DS
.'claims_f997.csv',
729 'files_csv'=>$edihist_dir.DS
.'csv'.DS
.'files_f997.csv', 'filedate'=>'Date', 'claimdate'=>'RspDate', 'regex'=>'/\.(99[79]|ta1|ack)$/i');
730 $p_ar['f276'] = array('type'=>'f276', 'directory'=>$edihist_dir.DS
.'f276', 'claims_csv'=>$edihist_dir.DS
.'csv'.DS
.'claims_f276.csv',
731 'files_csv'=>$edihist_dir.DS
.'csv'.DS
.'files_f276.csv', 'filedate'=>'Date', 'claimdate'=>'ReqDate', 'regex'=>'/\.276([ei]br)?$/');
732 $p_ar['f277'] = array('type'=>'f277', 'directory'=>$edihist_dir.DS
.'f277', 'claims_csv'=>$edihist_dir.DS
.'csv'.DS
.'claims_f277.csv',
733 'files_csv'=>$edihist_dir.DS
.'csv'.DS
.'files_f277.csv', 'filedate'=>'Date', 'claimdate'=>'SvcDate', 'regex'=>'/\.277([ei]br)?$/i');
734 $p_ar['f270'] = array('type'=>'f270', 'directory'=>$edihist_dir.DS
.'f270', 'claims_csv'=>$edihist_dir.DS
.'csv'.DS
.'claims_f270.csv',
735 'files_csv'=>$edihist_dir.DS
.'csv'.DS
.'files_f270.csv', 'filedate'=>'Date', 'claimdate'=>'ReqDate', 'regex'=>'/\.270([ei]br)?$/i');
736 $p_ar['f271'] = array('type'=>'f271', 'directory'=>$edihist_dir.DS
.'f271', 'claims_csv'=>$edihist_dir.DS
.'csv'.DS
.'claims_f271.csv',
737 'files_csv'=>$edihist_dir.DS
.'csv'.DS
.'files_f271.csv', 'filedate'=>'Date', 'claimdate'=>'RspDate', 'regex'=>'/\.271([ei]br)?$/i');
738 $p_ar['f278'] = array('type'=>'f278', 'directory'=>$edihist_dir.DS
.'f278', 'claims_csv'=>$edihist_dir.DS
.'csv'.DS
.'claims_f278.csv',
739 'files_csv'=>$edihist_dir.DS
.'csv'.DS
.'files_f278.csv', 'filedate'=>'Date', 'claimdate'=>'FileDate', 'regex'=>'/\.278/');
740 // OpenEMR stores era files, but the naming scheme is confusing, so we will just use our own directory for them
741 $p_ar['f835'] = array('type'=>'f835', 'directory'=>$edihist_dir.DS
.'f835', 'claims_csv'=>$edihist_dir.DS
.'csv'.DS
.'claims_f835.csv',
742 'files_csv'=>$edihist_dir.DS
.'csv'.DS
.'files_f835.csv', 'filedate'=>'Date', 'claimdate'=>'SvcDate', 'regex'=>'/835[0-9]{5}\.835*|\.(era|ERA|835)$/i');
744 if ( array_key_exists($tp, $p_ar) ) {
752 * determine if a csv table has data for select dropdown
754 * @param string default 'json'
755 * @return array json if argument is 'json'
757 function csv_table_select_list($outtp='json') {
759 $labels = array('f835'=>'Payments', 'f837'=>'Claims', 'batch'=>'Claims', 'f277'=>'Status', 'f276'=>'Status Req',
760 'f997'=>'Ack','f271'=>'Benefit', 'f270'=>'Benefit Req', 'f278'=>'Auth');
762 $edihist_dir = csv_edih_basedir(); // $GLOBALS['OE_SITE_DIR'].'/edi/history'
763 $csvdir = $edihist_dir.DS
.'csv';
764 $tbllist = scandir($csvdir);
766 foreach($tbllist as $csvf) {
767 if ($csvf == "." ||
$csvf == ".." ) { continue; }
768 if (strpos($csvf, 'old') === 0) { continue; }
769 if (filesize($csvdir.DS
.$csvf) < 70) { continue; }
770 if (substr($csvf, -1) == '~') { continue; }
771 $finfo = pathinfo($csvdir.DS
.$csvf);
772 $fn = $finfo['filename'];
774 $tp = explode('_', $fn);
775 //$lblkey = $labels[$tp[1]];
776 $optlist[$tp[0]][$tp[1]]['fname'] = $fn;
777 $optlist[$tp[0]][$tp[1]]['desc'] = $tp[0].'-'.$labels[$tp[1]]; //$tp[1] .' '.$tp[0];
780 if ($outtp == 'json') {
781 return json_encode($optlist);
788 * list existing archive files
790 * @param string default 'json'
791 * @return array json if argument is 'json'
793 function csv_archive_select_list($outtp='json') {
796 $archdir = csv_edih_basedir().DS
.'archive';
799 csv_edihist_log("csv_archive_select_list: using $archdir");
801 $scan = scandir($archdir);
802 if (is_array($scan) && count($scan)) {
803 foreach($scan as $s) {
804 if ($s == '.' ||
$s == '..') {
806 } elseif (strpos($s, 'note')) {
813 if ($outtp == 'json') {
814 return json_encode($flist);
821 * List files in the directory for the given type
823 * Write an entry in the log if an file is in the directory
824 * that does not match the type
826 * @uses csv_parameters()
827 * @param string $type a type from our list
830 function csv_dirfile_list($type) {
831 // return false if location is not appropriate
832 $tp = csv_file_type($type);
834 csv_edihist_log("csv_dirfile_list error: incorrect type $type");
837 $params = csv_parameters($tp);
838 if (empty($params) ||
csv_singlerecord_test($params) == false ) {
839 csv_edihist_log("csv_dirfile_list() error: incorrect type $type");
842 $search_dir = $params['directory'];
843 $ext_re = $params['regex'];
846 if (is_dir($search_dir)) {
847 if ($dh = opendir($search_dir)) {
848 while (($file = readdir($dh)) !== false) {
849 if ($file == '.' ||
$file == '..') {
851 } elseif ($tp == 'f837' && ($file == 'history' ||
$file == 'README.txt')) {
853 } elseif (is_file($search_dir.DS
.$file) ) {
856 if ($tp == 'f837' && $file == 'history') { continue; }
857 csv_edihist_log("csv_dirfile_list $type : not a file $file");
861 csv_edihist_log("csv_dirfile_list $type : error in scan $search_dir");
864 csv_edihist_log("csv_dirfile_list $type : not a directory $search_dir");
872 * List files that are in the csv record
874 * @uses csv_parameters()
875 * @uses csv_table_header()
877 * @param string $type -- one of our types
880 function csv_processed_files_list($type) {
882 $tp = csv_file_type($type);
884 csv_edihist_log("csv_processed_files_list: incorrect type $type");
887 $processed_files = array();
888 $param = csv_parameters($tp);
889 $hdr_ar = csv_table_header($tp, 'file');
890 if ( is_array($hdr_ar) ) {
891 foreach($hdr_ar as $k=>$hd) {
892 if ($hd == 'FileName') { $csv_col = $k; break; }
895 $csv_col = (isset($csv_col)) ?
$csv_col : 1;
896 $csv_file = $param['files_csv'];
897 //if ($tp == 'dpr') {
898 //$csv_file = $param['claims_csv'];
901 //$csv_file = $param['files_csv'];
905 if (is_file($csv_file)) {
906 if (($fh1 = fopen( $csv_file, "r" )) !== FALSE) {
907 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
908 $processed_files[] = $data[$csv_col];
910 //if ($idx) { $processed_files[] = $data[$csv_col]; }
911 // skip the header row
916 csv_edihist_log ("csv_list_processed_files: failed to access $csv_file" );
920 // first run - no file exists
921 csv_edihist_log("csv_processed_files_list: csv file does not exist ".basename($csv_file));
923 // remove the header row, but avoid NULL or false
924 $ret_ar = (empty($processed_files)) ?
$processed_files : array_slice($processed_files, 1);
930 * Give an array of files in the storage directories that are not in the csv record
932 * @param string $type -- one of our types
935 function csv_newfile_list($type) {
938 $tp = csv_file_type($type);
940 csv_edihist_log('csv_newfile_list: incorrect type '.$type);
944 $dir_files = csv_dirfile_list($tp);
945 $csv_files = csv_processed_files_list($tp);
947 // $dir_files should come first in array_diff()
948 if (empty($dir_files)) {
950 } elseif (empty($csv_files) ||
is_null($csv_files)) {
951 $ar_new = $dir_files;
953 $ar_new = array_diff($dir_files, $csv_files);
960 * Parse 997 IK3 error segment to identify segment causing rejection
961 * The error segment string is specially created in edih_997_csv_data()
962 * Simple analysis, but the idea is just to identify the bad segment
964 * @param string error segment from edih_997_csv_data()
965 * @param bool true if only the 1st segmentID is wanted
966 * return array|string
968 function edih_errseg_parse($err_seg, $id=false) {
969 // ['err_seg'] = '|IK3*segID*segpos*loop*errcode*bht03syn|CTX-IK3*transID*segID*segpos*elempos
970 // |IK4*elempos*errcode*elem*|CTX-IK4*segID*segpos*elempos
972 // note: multiple IK3 segments are allowed in 997/999 x12
975 if ( !$err_seg ||
strpos($err_seg, 'IK3') === false) {
976 csv_edihist_log('edih_errseg_parse: invalid argument');
979 //'|IK3*segID*segpos*loop*errcode*bht03syn|CTX-IK3*segID*segPos*loopLS*elemPos:compositePos:repPos
980 // revised: 123456789004*IK3*segID*segpos[*segID*segpos*segID*segpos]
981 $ik = explode('*', $err_seg);
982 foreach($ik as $i=>$k) {
984 case 0:$ret_ar['trace'] = $k; break;
985 case 1: break; // IK3
986 case 2: $ret_ar['id'][] = $k; break; // segment ID
987 case 3: $ret_ar['err'][] = $k; break; // segment position
988 case 4: $ret_ar['id'][] = $k; break;
989 case 5: $ret_ar['err'][] = $k; break;
990 case 6: $ret_ar['id'][] = $k; break;
991 case 7: $ret_ar['err'][] = $k; break;
999 * Order the csv data array according to the csv table heading row
1000 * so the data to be added to csv table rows are correctly ordered
1001 * the supplied data should be in an array with thie structure
1002 * array['icn'] ['file'][i]['key'] ['claim'][i]['key'] ['type']['type']
1004 * @uses csv_table_header()
1006 * @param array data_ar data array from edih_XXX_csv_data()
1007 * @return array|bool ordered array or false on error
1009 function edih_csv_order($csvdata) {
1012 $order_ar = array();
1014 foreach($csvdata as $icn=>$data) {
1015 // [icn]['type']['file']['claim']
1016 $ft = $data['type'];
1017 $wrcsv[$icn]['type'] = $ft;
1019 foreach($data as $key=>$val) {
1020 if ($key == 'type') { continue; }
1021 $order_ar[$icn][$key] = csv_table_header($ft, $key);
1022 $ct = count($order_ar[$icn][$key]);
1023 foreach($val as $k=>$rcrd) {
1025 foreach($order_ar[$icn][$key] as $ky=>$vl) {
1026 $wrcsv[$icn][$key][$k][$ky] = $rcrd[$vl];
1035 * insert dashes in ten-digit telephone numbers
1037 * @param string $str_val the telephone number
1038 * @return string the telephone number with dashes
1040 function edih_format_telephone ($str_val) {
1041 $strtel = (string)$str_val;
1042 $strtel = preg_replace('/\D/', '', $strtel);
1043 if ( strlen($strtel) != 10 ) {
1044 csv_edihist_log('edih_format_telephone: invalid argument: '.$str_val);
1047 $tel = substr($strtel,0,3) . "-" . substr($strtel,3,3) . "-" . substr($strtel,6);
1053 * order MM DD YYYY values and insert slashes in eight-digit dates
1055 * US MM/DD/YYYY or general YYYY-MM-DD
1057 * @param string $str_val the eight-digit date
1058 * @param string $pref if 'US' (default) anything else means YYYY-MM-DD
1059 * @return string the date with slashes
1061 function edih_format_date ($str_val, $pref = "Y-m-d") {
1062 $strdt = (string)$str_val;
1063 $strdt = preg_replace('/\D/', '', $strdt);
1065 if (strlen($strdt) == 6) {
1067 if ($pref == "US") {
1069 $strdt = substr($tdy,0,2).substr($strdt,-2).substr($strdt,0,4);
1072 $strdt = substr($tdy,0,2).$strdt;
1075 if ($pref == "US") {
1076 $dt = substr($strdt,4,2) . "/" . substr($strdt,6) . "/" . substr($strdt,0,4);
1078 $dt = substr($strdt,0,4) . "-" . substr($strdt,4,2) . "-" . substr($strdt,6);
1084 * format monetary amounts with two digits after the decimal place
1086 * @todo add other formats
1087 * @param string $str_val the amount string
1088 * @return string the telephone number with dashes
1090 function edih_format_money ($str_val) {
1092 if ($str_val ||
$str_val === '0') {
1093 $mny = sprintf("$%01.2f", $str_val);
1101 * format percentage amounts with % sign
1102 * typical example ".50" from x12 edi segment element
1104 * @param string $str_val the amount string
1105 * @return string the value as a percentage
1107 function edih_format_percent ($str_val) {
1108 $val = (float)$str_val;
1109 if (is_float($val)) {
1110 $pct = $val*100 . '%';
1112 $pct = $str_val.'%';
1118 * HTML string for table thead element
1120 * @uses csv_table_header()
1125 function csv_thead_html($file_type, $csv_type, $tblhd=null) {
1127 if (is_array($tblhd) & count($tblhd) ) {
1130 $hvals = csv_table_header($file_type, $csv_type);
1132 if ( is_array($hvals) && count($hvals) ) {
1137 $str_html .= "<thead>".PHP_EOL
."<tr>".PHP_EOL
;
1138 foreach($hvals as $val) {
1139 $str_html .="<th>$val</th>";
1141 $str_html .= PHP_EOL
."</tr>".PHP_EOL
."</thead>".PHP_EOL
;
1148 * Give the column headings for the csv files
1150 * @uses csv_file_type()
1151 * @param string $file_type one of our edi types
1152 * @param string $csv_type either 'file' or 'claim'
1155 function csv_table_header($file_type, $csv_type) {
1157 $ft = csv_file_type($file_type);
1158 $ct = strpos('|file', $csv_type) ?
'file' : $csv_type;
1159 $ct = strpos('|claim', $ct) ?
'claim' : $ct;
1162 if (!$ft ||
!$ct ) {
1163 csv_edihist_log ('csv_table_header error: incorrect file ['.$file_type.']or csv ['.$csv_type.'] type');
1167 if ($ct === 'file') {
1168 switch((string)$ft) {
1169 //case 'ack': $hdr = array('Date', 'FileName', 'isa13', 'ta1ctrl', 'Code'); break;
1170 //case 'ebr': $hdr = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch'); break;
1171 //case 'ibr': $hdr = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch'); break;
1173 case 'f837': $hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'x12Partner'); break;
1174 case 'ta1': $hdr = array('Date', 'FileName', 'Control', 'Trace', 'Code'); break;
1175 case 'f997': $hdr = array('Date', 'FileName', 'Control', 'Trace', 'RspType', 'RejCt'); break;
1176 case 'f276': $hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'x12Partner'); break;
1177 case 'f277': $hdr = array('Date', 'FileName', 'Control', 'Accept', 'AccAmt', 'Reject', 'RejAmt'); break;
1178 case 'f270': $hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'x12Partner'); break;
1179 case 'f271': $hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'Reject', 'Payer'); break;
1180 case 'f278': $hdr = array('Date', 'FileName', 'Control', 'TrnCount', 'Auth', 'Payer'); break;
1181 case 'f835': $hdr = array('Date', 'FileName', 'Control', 'Trace', 'Claim_ct', 'Denied', 'Payer'); break;
1183 } elseif ($ct === 'claim') {
1184 switch((string)$ft) {
1185 //case 'ebr': $hdr = array('PtName','SvcDate', 'CLM01', 'Status', 'Batch', 'FileName', 'Payer'); break;
1186 //case 'ibr': $hdr = array('PtName','SvcDate', 'CLM01', 'Status', 'Batch', 'FileName', 'Payer'); break;
1187 //case 'dpr': $hdr = array('PtName','SvcDate', 'CLM01', 'Status', 'Batch', 'FileName', 'Payer'); break;
1189 case 'f837': $hdr = array('PtName', 'SvcDate', 'CLM01', 'InsLevel', 'BHT03', 'FileName', 'Fee', 'PtPaid', 'Provider' ); break;
1190 case 'f997': $hdr = array('PtName', 'RspDate', 'Trace', 'Status', 'Control', 'FileName', 'RspType', 'err_seg'); break;
1191 case 'f276': $hdr = array('PtName', 'SvcDate', 'CLM01', 'ClaimID', 'BHT03', 'FileName', 'Payer', 'Trace'); break;
1192 case 'f277': $hdr = array('PtName', 'SvcDate', 'CLM01', 'Status', 'BHT03', 'FileName', 'Payer', 'Trace'); break;
1193 case 'f270': $hdr = array('PtName', 'ReqDate', 'Trace', 'InsBnft', 'BHT03', 'FileName', 'Payer'); break;
1194 case 'f271': $hdr = array('PtName', 'RspDate', 'Trace', 'Status', 'BHT03', 'FileName', 'Payer'); break;
1195 case 'f278': $hdr = array('PtName', 'FileDate', 'Trace', 'Status', 'BHT03', 'FileName', 'Auth', 'Payer'); break;
1196 case 'f835': $hdr = array('PtName', 'SvcDate', 'CLM01', 'Status', 'Trace', 'FileName', 'ClaimID', 'Pmt', 'PtResp', 'Payer'); break;
1200 csv_edihist_log ('edih_csv_table_header() error: failed to match file type ['.$ft.'] or csv type ['.$ct.']');
1211 function csv_files_header($file_type, $csv_type) {
1213 $tp = csv_file_type($type);
1215 csv_edihist_log('csv_files_header: incorrect type '.$file_type);
1218 if (!strpos('|file|claim', $csv_type) ) {
1219 csv_edihist_log('csv_files_header error: incorrect csv type '.$csv_type);
1223 $ft = strpos('|277', $file_type) ? 'f277' : $file_type;
1224 $ft = strpos('|835', $file_type) ? 'era' : $ft;
1225 $ft = strpos('|837', $file_type) ? 'batch' : $ft;
1226 $ft = strpos('|999|997|ack|ta1', $file_type) ? 'f997' : $ft;
1228 $csv_hd_ar = array();
1229 // dataTables: | 'date' | 'file_name (link)' | 'file_text (link fmt)' | 'claim_ct' | 'reject_ct' |
1230 $csv_hd_ar['ack']['file'] = array('Date', 'FileName', 'isa13', 'ta1ctrl', 'Code');
1231 $csv_hd_ar['ebr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
1232 $csv_hd_ar['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
1234 // dataTables: | 'date' | 'file_name (link)' | 'file_text (link fmt)' | 'claim_ct' | 'partner' |
1235 $csv_hd_ar['batch']['file'] = array('Date', 'FileName', 'Ctn_837', 'claim_ct', 'x12_partner');
1236 $csv_hd_ar['ta1']['file'] = array('Date', 'FileName', 'Ctn_ta1', 'ta1ctrl', 'Code');
1237 $csv_hd_ar['f997']['file'] = array('Date', 'FileName', 'Ctn_999', 'ta1ctrl', 'RejCt');
1238 $csv_hd_ar['f277']['file'] = array('Date', 'FileName', 'Ctn_277', 'Accept', 'AccAmt', 'Reject', 'RejAmt');
1239 $csv_hd_ar['f270']['file'] = array('Date', 'FileName', 'Ctn_270', 'claim_ct', 'x12_partner');
1240 $csv_hd_ar['f271']['file'] = array('Date', 'FileName', 'Ctn_271', 'claim_ct', 'Denied', 'Payer');
1241 $csv_hd_ar['era']['file'] = array('Date', 'FileName', 'Trace', 'claim_ct', 'Denied', 'Payer');
1243 // dataTables: | 'pt_name' | 'svc_date' | 'clm01 (link clm)' | 'status (mouseover)' | b f t (links to files) | message (mouseover) |
1244 $csv_hd_ar['ebr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1245 $csv_hd_ar['ibr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1246 $csv_hd_ar['dpr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1248 // dataTables: | 'pt_name' | 'svc_date' | 'clm01 (link clm)' | 'status (mouseover)' | 'bht03_837 (link rsp)' | message (mouseover) |
1249 $csv_hd_ar['batch']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' );
1250 $csv_hd_ar['f997']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'File_997', 'Ctn_837', 'err_seg');
1251 $csv_hd_ar['f277']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_277', 'File_277', 'payer_name', 'claim_id', 'bht03_837');
1252 $csv_hd_ar['f270']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'st_270', 'File_270', 'payer_name', 'bht03_270');
1253 $csv_hd_ar['f271']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_271', 'File_271', 'payer_name', 'bht03_270');
1254 $csv_hd_ar['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
1256 return $csv_hd_ar[$ft][$csv_type];
1261 * adapted from http://scratch99.com/web-development/javascript/convert-bytes-to-mb-kb/
1267 function csv_convert_bytes($bytes) {
1268 $sizes = array('Bytes', 'KB', 'MB', 'GB', 'TB');
1269 if ($bytes == 0) { return 'n/a'; }
1270 $i = floor( log($bytes) / log(1024) );
1271 //$i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
1273 return $bytes.' '.$sizes[$i];
1275 return round($bytes / pow(1024, $i), 1).' '.$sizes[$i];
1280 * Determine whether an array is multidimensional
1283 * @return bool false if arrayis multidimensional
1285 function csv_singlerecord_test ( $array ) {
1286 // the two versions of count() are compared
1287 // if the array has a sub-array, count recursive is greater
1288 if ( is_array($array) ) {
1289 $is_sngl = count($array, COUNT_RECURSIVE
) == count( $array, COUNT_NORMAL
);
1298 * give first and last index keys for an array
1303 function csv_array_bounds($array) {
1304 // get the segment array bounds
1306 if (is_array($array) && count($array)) {
1307 if (reset($array) !== false) { $ret_ar[0] = key($array); }
1308 if (end($array) !== false) { $ret_ar[1] = key($array); }
1314 * return a csv file as an associative array
1315 * the first row is the header or array keys for the row
1317 * array[i]=>array(hdr0=>csvrow[0], hdr1=>csvrow[1], hdr2=>csvrow[2], ...)
1319 * @param string file type e.g. f837
1320 * @param string csv type claim or file
1323 function csv_assoc_array($file_type, $csv_type) {
1325 if (!$file_type ||
!$csv_type) {
1326 csv_edihist_log('csv_assoc_array; invalid arguments ft: '.$file_type.' csvt: '.$csv_type);
1333 $param = csv_parameters($file_type);
1334 $fcsv = (strpos($csv_type, 'aim')) ?
'claims_csv' : 'files_csv';
1336 $fp = (isset($param[$fcsv])) ?
$param[$fcsv] : '';
1337 if (!is_file($fp)) {
1338 csv_edihist_log('csv_assoc_array; invalid csv file '.basename($fp));
1344 if (($fh = fopen($fp, "rb")) !== false) {
1345 while (($data = fgetcsv($fh, 2048, ",")) !== false) {
1346 if ( is_null($data) ) { continue; }
1348 for($i=0; $i<$ct; $i++
) {
1349 $csv_ar[$ky][$h[$i]] = $data[$i];
1360 // invalid file path
1361 csv_edihist_log('csv_assoc_array; invalid file path '.$fp);
1370 * A multidimensional array will be flattened to a single row.
1372 * @param array $array array to be flattened
1375 function csv_array_flatten($array) {
1377 if (!is_array($array)) {return FALSE;}
1379 foreach ($array as $key => $value) {
1380 if (is_array($value)) {
1381 $result = array_merge($result, csv_array_flatten($value));
1383 $result[$key] = $value;
1391 * Write parsed data from edi x12 files to csv file
1393 * @uses csv_parameters()
1394 * @usescsv_table_header()
1396 * @param array data array from parse functions
1397 * @return bool true if no error
1399 function edih_csv_write($csv_data) {
1401 if ( ! (is_array($csv_data) && count($csv_data)) ){
1402 csv_edihist_log('edih_csv_write(): invalid data array');
1406 foreach($csv_data as $icn=>$isa) {
1407 // should be array[icn] => [file][j][key] [claim][j][key] [type]
1408 $ft = ( isset($isa['type']) ) ?
$isa['type'] : '';
1410 csv_edihist_log('edih_csv_write(): invalid file type');
1414 $param = csv_parameters($ft);
1415 $f_hdr = csv_table_header($ft, 'file');
1416 $c_hdr = csv_table_header($ft, 'claim');
1417 if (is_array($param)) {
1418 // if either csv files does not exist, create them both
1419 // all unlisted files in type directory will be processed on next process round
1420 if (is_file($param['files_csv']) && (filesize($param['files_csv']) > 20)) {
1421 csv_edihist_log('edih_csv_write: csv check for files csv '.$ft);
1423 $nfcsv = $param['files_csv'];
1424 $fh = fopen($nfcsv, 'wb');
1425 if ($fh !== false) {
1426 fputcsv($fh, $f_hdr);
1428 chmod($nfcsv, 0600);
1430 csv_edihist_log('edih_csv_write: created files_csv file for '.$ft);
1432 if (is_file($param['claims_csv']) && filesize($param['claims_csv'])) {
1433 csv_edihist_log('edih_csv_write: csv check for claims csv '.$ft);
1435 $nfcsv = $param['claims_csv'];
1436 $fh = fopen($nfcsv, 'wb');
1437 if ($fh !== false) {
1438 fputcsv($fh, $c_hdr);
1440 chmod($nfcsv, 0600);
1442 csv_edihist_log('edih_csv_write: created claims_csv file for '.$ft);
1445 csv_edihist_log('edih_csv_write: parameters error for type '.$ft);
1449 foreach($isa as $key=>$data) {
1450 if ($key == 'type') { continue; }
1451 // get the csv file path from parameters
1452 $fp = ($key == 'file') ?
$param['files_csv'] : $param['claims_csv'];
1453 // get the csv row header
1454 $order_ar = ($key == 'file') ?
$f_hdr : $c_hdr;
1455 $ct = count($order_ar);
1459 $fh = fopen( $fp, 'ab');
1460 if (is_resource($fh)) {
1461 // to assure proper order of data in each row, the
1462 // csv row is assembled by matching keys to the header row
1463 foreach($data as $ky=>$row) {
1465 for ($i=0; $i<$ct; $i++
) {
1466 $csvrow[$i] = $row[$order_ar[$i]];
1468 $chrs +
= fputcsv ( $fh , $csvrow );
1472 csv_edihist_log('edih_csv_write(): failed to open '.$fp);
1476 csv_edihist_log('edih_csv_write() wrote '.$rws.' rows to '.basename($fp));
1485 * Search a csv record file and return the row or values from selected columns
1487 * This function requires that the $search_ar parameter be an array
1488 * with keys ['s_val']['s_col']['r_cols'], and 'r_cols' is an array
1489 * 's_val' is the search value, s_col is the column to check, r_cols is an array
1490 * of column numbers from which values are returned. If r_cols is not an array,
1491 * then the entire row will be returned. If the 'expect' parameter is 1, then
1492 * the search will stop after the first success and return the result. Otherwise, the
1493 * entire file will be searched.
1494 * ex: csv_search_record('batch', 'claim', array('s_val'=>'0024', 's_col'=>9, 'r_cols'=>array(1, 2, 7)), "1" )
1496 * @uses csv_parameters()
1497 * @param string $file_type
1498 * @param string $csv_type
1499 * @param array $search_ar
1500 * @param mixed $expect
1503 function csv_search_record($file_type, $csv_type, $search_ar, $expect='1') {
1505 csv_edihist_log("csv_search_record: ".strval($file_type)." ".strval($csv_type)." ".strval($search_ar['s_val']));
1507 $tp = csv_file_type($file_type);
1509 csv_edihist_log("csv_search_record: incorrect type $file_type");
1513 $params = csv_parameters($tp);
1515 if ($csv_type == 'claim') {
1516 $fp = $params['claims_csv'];
1517 } elseif ($csv_type == 'file') {
1518 $fp = $params['files_csv'];
1520 csv_edihist_log('csv_search_record: incorrect csv type '.$csv_type);
1524 if (!is_array($search_ar) ||
array_keys($search_ar) != array('s_val', 's_col', 'r_cols')) {
1525 csv_edihist_log('csv_search_record: invalid search criteria');
1528 $sv = $search_ar['s_val'];
1529 $sc = $search_ar['s_col'];
1530 $rv = (is_array($search_ar['r_cols']) && count($search_ar['r_cols'])) ?
$search_ar['r_cols'] : 'all';
1533 if (($fh1 = fopen($fp, "r")) !== false) {
1534 while (($data = fgetcsv($fh1)) !== false) {
1535 // check for a match
1536 if ($data[$sc] == $sv) {
1538 $ret_ar[$idx] = $data;
1540 // now loop through the 'r_cols' array for data index
1541 $dct = count($data);
1542 foreach($rv as $c) {
1543 // make sure we don't access a non-existing index
1544 if ($c >= $dct) { continue; }
1546 $ret_ar[$idx][] = $data[$c];
1550 if ($expect == '1') { break; }
1555 csv_edihist_log('csv_search_record: failed to open '.$fp);
1558 if (empty($ret_ar) ) {
1566 * Search the 'claims' csv table for the patient control and find the associated file name
1569 * In 'claims' csv tables, clm01 is position 2, ISA13 number is pos 4, and filename is pos 5;
1570 * Since we are interested usually in the filename, ISA13 is irrelevant usually.
1572 * @uses csv_parameters()
1573 * @uses csv_pid_enctr_parse()
1574 * @param string patient control-- pid-encounter, encounter, or pid
1575 * @param string filetype -- x12 type or f837, f277, etc
1576 * @param string search type encounter, pid, or clm01
1577 * @return array|bool [i] data row array or empty on error
1579 function csv_file_by_enctr($clm01, $filetype='f837') {
1581 // return array of [i](pid_encounter, filename), there may be more than one file
1584 return 'invalid encounter data<br>' . PHP_EOL
;
1588 $ft = csv_file_type($filetype);
1591 csv_edihist_log('csv_file_by_enctr: incorrect file type '.$filetype);
1594 $params = csv_parameters($ft);
1595 //$fp = isset($params['claims_csv']) ? dirname(__FILE__).$params['claims_csv'] : false;
1596 $fp = isset($params['claims_csv']) ?
$params['claims_csv'] : false;
1597 $h_ar = csv_table_header($ft, 'claim');
1598 $hct = count($h_ar);
1600 csv_edihist_log('csv_file_by_enctr: incorrect file type '.$filetype);
1605 $enct = csv_pid_enctr_parse(strval($clm01));
1606 $p = (isset($enct['pid'])) ?
$enct['pid'] : '';
1607 $e = (isset($enct['enctr'])) ?
$enct['enctr'] : '';
1612 $srchtype = 'encounter';
1616 csv_edihist_log('csv_file_by_enctr: unable to determine encounter value '.$clm01);
1617 return 'unable to determine encounter value '.$clm01.'<br />'.PHP_EOL
;
1619 // OpenEMR creates CLM01 as nnn-nnn in genX12 batch
1620 //$pm = preg_match('/\D/', $enctr, $match2, PREG_OFFSET_CAPTURE);
1622 //array_combine ( array $keys , array $values )
1623 // in 'claims' csv tables, clm01 is position 2 and filename is position 5
1624 if (($fh1 = fopen($fp, "r")) !== FALSE) {
1625 if ($srchtype == 'encounter') {
1626 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
1627 // check for a match
1628 if (strpos($data[2], $e)) {
1629 $te = substr($data[2], strpos($data[2],'-')+
1);
1630 if (strcmp($te, $e) === 0) {
1631 for ($i=0; $i<$hct; $i++
) { $val[$h_ar[$i]] = $data[$i]; }
1632 $ret_ar[] = $val; // array_combine($h_ar, $data);
1636 } elseif ($srchtype == 'pid') {
1637 while (($data = fgetcsv($fh1, 1024, ',')) !== FALSE) {
1638 if (strpos($data[2], $p) !== false) {
1639 $te = (strpos($data[2], '-')) ?
substr($data[2], 0, strpos($data[2],'-')) : '';
1640 if (strcmp($te, $p) === 0) {
1641 for ($i=0; $i<$hct; $i++
) { $val[$h_ar[$i]] = $data[$i]; }
1642 $ret_ar[] = $val; // $ret_ar[] = array_combine($h_ar, $data);
1647 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
1648 // check for a match
1649 if ( strcmp($data[2], $pe) === 0 ) {
1650 for ($i=0; $i<$hct; $i++
) { $val[$h_ar[$i]] = $data[$i]; }
1651 $ret_ar[] = $val; // $ret_ar[] = array_combine($h_ar, $data);
1657 csv_edihist_log('csv_file_by_enctr: failed to open csv file '.basename($fp));
1665 * get the x12 file containing the control_num ISA13
1667 * @todo the csv for x12 files 999, 277, 835, 837 must have the control number
1669 * @uses csv_search_record()
1670 * @param string $control_num the interchange control number, isa13
1671 * @return string the file name
1673 function csv_file_by_controlnum($type, $control_num) {
1674 // get the batch file containing the control_num
1676 $tp = csv_file_type($type);
1678 $hdr = csv_table_header($tp, 'file');
1679 $scol = array_search('Control', $hdr);
1680 $rcol = array_search('FileName', $hdr);
1682 // $search_ar should have keys ['s_val']['s_col'] array(['r_cols'][])
1683 // like "batch', 'claim, array(9, '0024', array(1, 2, 7))
1684 //$csv_hd_ar['batch']['file'] = array('time', 'file_name', 'control_num', 'claims', 'x12_partner', 'x12_version');
1687 $ctln = (strlen($control_num) >= 9) ?
substr($control_num, 0, 9) : $control_num;
1688 $search = array('s_val'=>$ctln, 's_col'=>$scol, 'r_cols'=>array($rcol));
1689 $result = csv_search_record($tp, 'file', $search, "1");
1690 if (is_array($result) && count($result[0]) == 1) {
1691 $fn = $result[0][0];
1698 * Search the csv table to obtain the file name for a given
1699 * trace value (835 / 997 999 type only)
1701 * Note: the 997/999 trace is the ISA13 of a batch file
1704 * @param string trace value (TRN02, TA101, or BHT03)
1705 * @param string from type (default is f835)
1706 * @param string to type (default is f835)
1707 * @return string file name or empty string
1709 function csv_file_by_trace($trace, $from_type='f835', $to_type='f837') {
1710 // get the file referenced by the trace value
1712 $ft = ($from_type) ?
csv_file_type($from_type) : '';
1713 $tt = ($to_type) ?
csv_file_type($to_type) : '';
1719 csv_edihist_log("csv_file_by_trace: $trace from $ft to $tt");
1721 // $search_ar should have keys ['s_val']['s_col'] array(['r_cols'])
1722 // like "f837', 'claim, array(9, '0024', array(1, 2, 7))
1724 if ($ft == 'f835') {
1725 // trace payment to status or claim
1726 $search = array('s_val'=>$trace, 's_col'=>3, 'r_cols'=>'All');
1729 } elseif ($ft == 'f997') {
1730 // trace ACK to batch file
1731 $icn = (is_numeric($trace) && strlen($trace) >= 9) ?
substr($trace, 0, 9) : $trace;
1732 $search = array('s_val'=>$icn, 's_col'=>2, 'r_cols'=>'All');
1735 } elseif ($ft == 'f277') {
1736 // trace status to status req or claim
1737 if ($tt == 'f276') {
1738 $search = array('s_val'=>$trace, 's_col'=>7, 'r_cols'=>'All');
1740 $csv_type = 'claim';
1741 } elseif ($tt == 'f837') {
1742 // expect CLM01 for trace value
1743 $search = array('s_val'=>$trace, 's_col'=>2, 'r_cols'=>'All');
1745 $csv_type = 'claim';
1747 } elseif ($ft == 'f271') {
1748 // trace benefit to benefit req
1749 if ($tt == 'f270') {
1750 $search = array('s_val'=>$trace, 's_col'=>2, 'r_cols'=>'All');
1752 $csv_type = 'claim';
1754 } elseif ($ft == 'f278') {
1755 // trace auth to auth req
1756 $search = array('s_val'=>$trace, 's_col'=>2, 'r_cols'=>'All');
1758 $csv_type = 'claim';
1760 csv_edihist_log('csv_file_by_trace: incorrect file type '.$file_type);
1764 if ($type && $csv_type && $search) {
1765 $result = csv_search_record($type, $csv_type, $search, false);
1766 if (is_array($result) && count($result)) {
1767 if ($ft == 'f278') {
1768 foreach($result as $r) {
1769 if ($r[6] == 'Rsp' ||
$r[6] == 'Reply') {
1770 $fn = $result[0][5];
1774 } elseif ($csv_type == 'claim') {
1775 $fn = $result[0][5];
1777 $fn = $result[0][1];
1780 csv_edihist_log("csv_file_by_trace: search failed $type csv $csv_type for trace $trace $from_type $to_type");
1783 csv_edihist_log("csv_file_by_trace: error type $type csv $csv_type for trace $trace $from_type $to_type");
1789 * list claim records with Denied or Reject status in given file
1796 function csv_denied_by_file($filetype, $filename, $trace='') {
1799 $ft = csv_file_type($filetype);
1800 if (strpos('|f997|f271|f277|f835', $ft)) {
1801 $param = csv_parameters($ft);
1802 $csv_file = $param['claims_csv'];
1804 csv_edihist_log("csv_errors_by_file: incorrect file type $filetype");
1808 csv_edihist_log("csv_errors_by_file: $ft searching $filename with trace $trace");
1810 if (($fh1 = fopen($csv_file, "r")) !== false) {
1811 if ($ft == 'f835') {
1812 while (($data = fgetcsv($fh1, 1024, ",")) !== false) {
1813 // check filename, then status
1815 if ($data[4] == $trace) {
1816 if (!in_array($data[3], array('1', '2', '3', '19', '20', '21')) ) { $ret_ar[] = $data; }
1818 } elseif ($data[5] == $filename) {
1819 if (!in_array($data[3], array('1', '2', '3', '19', '20', '21')) ) { $ret_ar[] = $data; }
1823 } elseif ($ft == 'f277') {
1824 while (($data = fgetcsv($fh1, 1024, ",")) !== false) {
1825 if ($data[5] == $filename) {
1826 if ( !strpos('|A1|A2|A5', substr($data[3], 0, 2))) {
1831 } elseif (strpos('|f997|f999|f271', $ft)) {
1832 while (($data = fgetcsv($fh1, 1024, ",")) !== false) {
1833 if ($data[5] == $filename) {
1834 if ($data[3] !== 'A') {
1840 csv_edihist_log("csv_errors_by_file: file type did not match $filetype");
1850 * A function to try and assure the pid-encounter is correctly parsed
1852 * assume a format of pid-encounter, since that is sent in the OpenEMR x12 837
1854 * @param string $pid_enctr the value from element CLM01
1855 * return array array('pid' => $pid, 'enctr' => $enc)
1857 function csv_pid_enctr_parse( $pid_enctr ) {
1858 // evaluate the patient account field
1860 if (!$pid_enctr ||
!is_string($pid_enctr) ) {
1861 csv_edihist_log("csv_pid_enctr_parse: invalid argument");
1864 $pval = trim($pid_enctr);
1865 if ( strpos($pval, '-') ) {
1866 $pid = substr($pval, 0, strpos($pval, '-'));
1867 $enc = substr($pval, strpos($pval, '-')+
1);
1868 } elseif ( ctype_digit($pval) ) {
1869 if ( preg_match('/(19|20)\d{2}[01]\d{1}[0-3]\d{1}/', $pval) ) {
1872 $enc = ( strlen($pval) ) >= ENCOUNTER_MIN_DIGIT_LENGTH ?
$pval : '';
1875 } elseif ( preg_match('/\D/', $pval, $match2, PREG_OFFSET_CAPTURE
) ) {
1876 $inv_split = (count($match2)) ?
preg_split('/\D/', $pval, 2, PREG_SPLIT_NO_EMPTY
) : false;
1878 $pid = $inv_split[0];
1879 $enc = $inv_split[1];
1882 $enc = ( strlen($pval) ) >= ENCOUNTER_MIN_DIGIT_LENGTH ?
$pval : '';
1885 return array('pid' => $pid, 'enctr' => $enc);