4 * interface/modules/zend_modules/module/Carecoordination/src/Carecoordination/Controller/CarecoordinationController.php
7 * @link https://www.open-emr.org
8 * @author Vinish K <vinish@zhservices.com>
9 * @author Chandni Babu <chandnib@zhservices.com>
10 * @author Riju KP <rijukp@zhservices.com>
11 * @copyright Copyright (c) 2014 Z&H Consultancy Services Private Limited <sam@zhservices.com>
12 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
15 namespace Carecoordination\Controller
;
17 use Application\Model\ApplicationTable
;
18 use Application\Plugin\CommonPlugin
;
19 use Laminas\Mvc\Controller\AbstractActionController
;
20 use Laminas\View\Model\ViewModel
;
21 use Laminas\View\Model\JsonModel
;
22 use Application\Listener\Listener
;
23 use Documents\Controller\DocumentsController
;
24 use Carecoordination\Model\CarecoordinationTable
;
28 use OpenEMR\Common\Logging\SystemLogger
;
29 use OpenEMR\Services\Cda\CdaValidateDocuments
;
30 use xmltoarray_parser_htmlfix
;
32 class CarecoordinationController
extends AbstractActionController
35 * @var Carecoordination\Model\CarecoordinationTable
37 private $carecoordinationTable;
40 * @var Documents\Controller\DocumentsController
42 private $documentsController;
45 * @var Application\Listener\Listener
47 private $listenerObject;
54 public function __construct(CarecoordinationTable
$table, DocumentsController
$documentsController)
56 $this->carecoordinationTable
= $table;
57 $this->listenerObject
= new Listener();
58 $this->date_format
= ApplicationTable
::dateFormat($GLOBALS['date_display_format']);
59 $this->documentsController
= $documentsController;
65 * @param int $id menu id
66 * $param array $data menu details
67 * @param string $slug controller name
68 * @return \Laminas\View\Model\ViewModel
70 public function indexAction()
72 $this->redirect()->toRoute('encountermanager', array('action' => 'index'));
76 * delete an audit record
80 public function deleteAuditAction()
82 $request = $this->getRequest();
83 $amid = $request->getPost('am_id') ??
null;
85 $this->getCarecoordinationTable()->deleteImportAuditData(array('audit_master_id' => $amid));
87 $category_details = $this->getCarecoordinationTable()->fetch_cat_id('CCDA');
88 $records = $this->getCarecoordinationTable()->document_fetch(array('cat_title' => 'CCDA', 'type' => '12'));
89 $view = new ViewModel(array(
90 'records' => $records,
91 'category_id' => $category_details[0]['id'],
92 'file_location' => basename($_FILES['file']['name'] ??
''),
94 'listenerObject' => $this->listenerObject
103 public function uploadAction()
105 $request = $this->getRequest();
106 $action = $request->getPost('action');
107 $am_id = $request->getPost('am_id');
108 $document_id = $request->getPost('document_id');
110 if ($action == 'add_new_patient') {
111 $this->getCarecoordinationTable()->insert_patient($am_id, $document_id);
113 if (($request->getPost('chart_all_imports') ??
null) === 'true' && empty($action)) {
114 $records = $this->getCarecoordinationTable()->document_fetch(array('cat_title' => 'CCDA', 'type' => '12'));
115 foreach ($records as $record) {
116 if (!empty($record['matched_patient']) && empty($record['is_unstructured_document'])) {
117 // @todo figure out a way to make this auto. $data is array of doc changes.
118 // $this->getCarecoordinationTable()->insertApprovedData($data);
119 // meantime make user approve changes.
122 $this->getCarecoordinationTable()->insert_patient($record['amid'], $record['document_id']);
125 if (($request->getPost('delete_all_imports') ??
null) === 'true' && empty($action)) {
126 $records = $this->getCarecoordinationTable()->document_fetch(array('cat_title' => 'CCDA', 'type' => '12'));
127 foreach ($records as $record) {
128 $this->getCarecoordinationTable()->deleteImportAuditData(array('audit_master_id' => $record['amid']));
132 $upload = $request->getPost('upload');
133 $category_details = $this->getCarecoordinationTable()->fetch_cat_id('CCDA');
136 $time_start = date('Y-m-d H:i:s');
137 $obj_doc = $this->documentsController
;
138 if ($obj_doc->isZipUpload($request)) {
139 $this->importZipUpload($request);
141 $cdoc = $obj_doc->uploadAction($request);
142 $uploaded_documents = $this->getCarecoordinationTable()->fetch_uploaded_documents(
143 array('user' => $_SESSION['authUserID'], 'time_start' => $time_start, 'time_end' => date('Y-m-d H:i:s'))
145 if ($uploaded_documents[0]['id'] > 0) {
146 $_REQUEST["document_id"] = $uploaded_documents[0]['id'];
147 $_REQUEST["batch_import"] = 'YES';
148 $this->importAction();
152 $result = $this->Documents()->fetchXmlDocuments();
153 foreach ($result as $row) {
154 if ($row['doc_type'] == 'CCDA') {
155 $_REQUEST["document_id"] = $row['doc_id'];
156 $this->importAction();
157 $this->documentsController
->getDocumentsTable()->updateDocumentCategoryUsingCatname($row['doc_type'], $row['doc_id']);
162 $records = $this->getCarecoordinationTable()->document_fetch(array('cat_title' => 'CCDA', 'type' => '12'));
163 foreach ($records as $key => $r) {
164 if (!empty($records[$key]['dupl_patient'] ??
null)) {
167 $name = $r['pat_name'];
168 // compare to the other imported items for duplicates being imported
169 foreach ($records as $k => $r1) {
172 if (!empty($r1['dupl_patient'] ??
null) ||
$key == $k) {
173 if (!empty($records[$key]['matched_patient'] ??
null)) {
174 $why = xlt('Duplicate demographics and components for MRN') . ' ' . text($records[$key]['pid'] ??
'');
175 $records[$k]['dupl_patient'] = $why;
179 $n = $r1['pat_name'];
180 $fn = $r1['ad_fname'] == $r['ad_fname'];
181 $ln = $r1['ad_lname'] == $r['ad_lname'];
182 $dob = $r1['dob_raw'] == $r['dob_raw'];
185 $why = xlt('Match DOB');
187 if ($name == $n && ($f ||
$r1['race'] == $r['race'] ||
$r1['ethnicity'] == $r['ethnicity'])) {
189 $why = xlt('Matched Demographic and DOB');
191 $why = xlt('Matched Demographic');
193 if ($r1['enc_count'] != $r['enc_count'] ||
$r1['cp_count'] != $r['cp_count'] ||
$r1['ob_count'] != $r['ob_count']) {
194 $why .= ' ' . xlt('with Mismatched Components');
199 (($ln && !$fn ||
$fn && !$ln) && $dob)
200 && ($r1['race'] == $r['race'] ||
$r1['ethnicity'] == $r['ethnicity'])
202 $why = xlt('Name Misspelled');
204 $r1['enc_count'] != $r['enc_count']
205 ||
$r1['cp_count'] != $r['cp_count']
206 ||
$r1['ob_count'] != $r['ob_count']
208 $why .= ' ' . xlt('with Mismatched Components');
212 if (($r1['is_qrda_document'] ??
0) === 2) {
214 $records[$k]['dupl_patient'] = xlt('Empty Report. No QDM content.');
217 if (empty($records[$k]['matched_patient']) && empty($records[$key]['matched_patient'])) {
218 $why = xlt('Another imported document duplicates') . ' ' . $why;
220 $records[$key]['dupl_patient'] = $records[$k]['dupl_patient'] = $why;
225 $view = new ViewModel(array(
226 'records' => $records,
227 'category_id' => $category_details[0]['id'],
228 'file_location' => basename($_FILES['file']['name'] ??
''),
229 'patient_id' => '00',
230 'listenerObject' => $this->listenerObject
232 // I haven't a clue why this delay is needed to allow batch to work from fetch.
233 if (!empty($upload)) {
240 * Function to import the data CCDA file to audit tables.
242 * @param document_id integer value
243 * @return \Laminas\View\Model\JsonModel
245 public function importAction()
247 $request = $this->getRequest();
248 if ($request->getQuery('document_id')) {
249 $_REQUEST["document_id"] = $request->getQuery('document_id');
250 $category_details = $this->getCarecoordinationTable()->fetch_cat_id('CCDA');
251 $this->documentsController
->getDocumentsTable()->updateDocumentCategory($category_details[0]['id'], $_REQUEST["document_id"]);
254 $document_id = $_REQUEST["document_id"];
255 $this->getCarecoordinationTable()->import($document_id);
257 $view = new JsonModel();
258 $view->setTerminal(true);
262 public function revandapproveAction()
264 $request = $this->getRequest();
265 $document_id = $request->getQuery('document_id') ?
$request->getQuery('document_id') : $request->getPost('document_id', null);
266 $audit_master_id = $request->getQuery('amid') ?
$request->getQuery('amid') : $request->getPost('amid', null);
267 $pid = $request->getQuery('pid') ?
$request->getQuery('pid') : $request->getPost('pid', null);
269 if ($request->getPost('setval') == 'approve') {
270 $this->getCarecoordinationTable()->insertApprovedData($request->getPost());
271 return $this->redirect()->toRoute('carecoordination', array(
272 'controller' => 'Carecoordination',
273 'action' => 'upload'));
274 } elseif ($request->getPost('setval') == 'discard') {
275 $this->getCarecoordinationTable()->discardCCDAData(array('audit_master_id' => $audit_master_id));
276 return $this->redirect()->toRoute('carecoordination', array(
277 'controller' => 'Carecoordination',
278 'action' => 'upload'));
281 $documentationOf = $this->getCarecoordinationTable()->getdocumentationOf($audit_master_id);
282 $demographics = $this->getCarecoordinationTable()->getDemographics(array('audit_master_id' => $audit_master_id));
283 $demographics_old = $this->getCarecoordinationTable()->getDemographicsOld(array('pid' => $pid));
285 $problems = $this->getCarecoordinationTable()->getProblems(array('pid' => $pid));
286 $problems_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'lists1');
288 $allergies = $this->getCarecoordinationTable()->getAllergies(array('pid' => $pid));
289 $allergies_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'lists2');
291 $medications = $this->getCarecoordinationTable()->getMedications(array('pid' => $pid));
292 $medications_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'lists3');
294 $immunizations = $this->getCarecoordinationTable()->getImmunizations(array('pid' => $pid));
295 $immunizations_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'immunization');
297 $lab_results = $this->getCarecoordinationTable()->getLabResults(array('pid' => $pid));
298 $lab_results_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'procedure_result');
300 $vitals = $this->getCarecoordinationTable()->getVitals(array('pid' => $pid));
301 $vitals_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'vital_sign');
303 $social_history = $this->getCarecoordinationTable()->getSocialHistory(array('pid' => $pid));
304 $social_history_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'social_history');
306 $encounter = $this->getCarecoordinationTable()->getEncounterData(array('pid' => $pid));
307 $encounter_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'encounter');
309 $procedure = $this->getCarecoordinationTable()->getProcedure(array('pid' => $pid));
310 $procedure_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'procedure');
312 $care_plan = $this->getCarecoordinationTable()->getCarePlan(array('pid' => $pid));
313 $care_plan_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'care_plan');
315 $functional_cognitive_status = $this->getCarecoordinationTable()->getFunctionalCognitiveStatus(array('pid' => $pid));
316 $functional_cognitive_status_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'functional_cognitive_status');
318 $referral = $this->getCarecoordinationTable()->getReferralReason(array('pid' => $pid));
319 $referral_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'referral');
321 $discharge_medication_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'discharge_medication');
323 $discharge_summary = array(); // TODO: stephen what happened here?? no discharge summary review?
324 $discharge_summary_audit = $this->getRevAndApproveAuditArray($audit_master_id, 'discharge_summary');
326 $gender_list = $this->getCarecoordinationTable()->getList('sex');
327 $country_list = $this->getCarecoordinationTable()->getList('country');
328 $marital_status_list = $this->getCarecoordinationTable()->getList('marital');
329 $religion_list = $this->getCarecoordinationTable()->getList('religious_affiliation');
330 $race_list = $this->getCarecoordinationTable()->getList('race');
331 $ethnicity_list = $this->getCarecoordinationTable()->getList('ethnicity');
332 $state_list = $this->getCarecoordinationTable()->getList('state');
333 $tobacco = $this->getCarecoordinationTable()->getList('smoking_status');
335 $demographics_old[0]['sex'] = $this->getCarecoordinationTable()->getListTitle($demographics_old[0]['sex'], 'sex', '');
336 $demographics_old[0]['country_code'] = $this->getCarecoordinationTable()->getListTitle($demographics_old[0]['country_code'], 'country', '');
337 $demographics_old[0]['status'] = $this->getCarecoordinationTable()->getListTitle($demographics_old[0]['status'], 'marital', '');
338 $demographics_old[0]['religion'] = $this->getCarecoordinationTable()->getListTitle($demographics_old[0]['religion'], 'religious_affiliation', '');
339 $demographics_old[0]['race'] = $this->getCarecoordinationTable()->getListTitle($demographics_old[0]['race'], 'race', '');
340 $demographics_old[0]['ethnicity'] = $this->getCarecoordinationTable()->getListTitle($demographics_old[0]['ethnicity'], 'ethnicity', '');
341 $demographics_old[0]['state'] = $this->getCarecoordinationTable()->getListTitle($demographics_old[0]['state'], 'state', '');
343 $view = new ViewModel(array(
344 'carecoordinationTable' => $this->getCarecoordinationTable(),
345 'ApplicationTable' => $this->getApplicationTable(),
346 'commonplugin' => $this->CommonPlugin(), // this comes from the Application Module
347 'demographics' => $demographics,
348 'demographics_old' => $demographics_old,
349 'problems' => $problems,
350 'problems_audit' => $problems_audit,
351 'allergies' => $allergies,
352 'allergies_audit' => $allergies_audit,
353 'medications' => $medications,
354 'medications_audit' => $medications_audit,
355 'immunizations' => $immunizations,
356 'immunizations_audit' => $immunizations_audit,
357 'lab_results' => $lab_results,
358 'lab_results_audit' => $lab_results_audit,
360 'vitals_audit' => $vitals_audit,
361 'social_history' => $social_history,
362 'social_history_audit' => $social_history_audit,
363 'encounter' => $encounter,
364 'encounter_audit' => $encounter_audit,
365 'procedure' => $procedure,
366 'procedure_audit' => $procedure_audit,
367 'care_plan' => $care_plan,
368 'care_plan_audit' => $care_plan_audit,
369 'functional_cognitive_status' => $functional_cognitive_status,
370 'functional_cognitive_status_audit' => $functional_cognitive_status_audit,
371 'referral' => $referral,
372 'referral_audit' => $referral_audit,
373 'discharge_medication_audit' => $discharge_medication_audit,
374 'discharge_summary' => $discharge_summary,
375 'discharge_summary_audit' => $discharge_summary_audit,
376 'amid' => $audit_master_id,
378 'document_id' => $document_id,
379 'gender_list' => $gender_list,
380 'country_list' => $country_list,
381 'marital_status_list' => $marital_status_list,
382 'religion_list' => $religion_list,
383 'race_list' => $race_list,
384 'ethnicity_list' => $ethnicity_list,
385 'tobacco' => $tobacco,
386 'state_list' => $state_list,
387 'listenerObject' => $this->listenerObject
,
388 'documentationOf' => $documentationOf,
393 public function getCCDAComponentsAction()
395 $request = $this->getRequest();
396 $id = $request->getQuery('id');
397 $arr = explode("-", $id);
400 $components = $this->getCarecoordinationTable()->getCCDAComponents(1);
401 $discharge_medication_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'discharge_medication');
402 $discharge_summary_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'discharge_summary');
403 if (count($discharge_medication_audit) > 0) {
404 $components['discharge_medication'] = 'Dishcharge Medications';
407 if (count($discharge_summary_audit) > 0) {
408 $components['discharge_summary'] = 'Dishcharge Summary';
411 $components = array_diff($components, array('instructions' => 'Instructions'));
414 foreach ($components as $key => $value) {
415 $temp .= '<tr class="se_in_9">
416 <th colspan="1" id="expandCompDetails-' . CommonPlugin
::escape($key . $amid . $pid) . '" class="expandCompDetails se_in_23" component="' . CommonPlugin
::escape($key) . '" amid="' . CommonPlugin
::escape($amid) . '" style="padding: 0px 5px!important;"></th>
417 <th colspan="8" style="padding: 0px 0px!important;"><label>' . CommonPlugin
::escape($value) . '</th>
420 <td colspan="9" id="hideComp-' . CommonPlugin
::escape($key . $amid . $pid) . '" class="imported_ccdaComp_details" style="display: none;"></td>
429 public function getEachCCDAComponentDetailsAction()
431 $request = $this->getRequest();
432 $id = $request->getQuery('id');
433 $component = $request->getQuery('component');
434 $amid = $request->getQuery('amid');
437 switch ($component) {
439 $validate = new CdaValidateDocuments();
440 $temp .= $validate->createSchematronHtml($amid);
443 $allergies_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'lists2');
444 if (count($allergies_audit) > 0) {
445 $temp .= '<div><table class="narr_table" border="1" width="100%!important;">
446 <thead><tr class="narr_tr">
447 <th class="narr_th">' . Listener
::z_xlt('Substance') . '</th>
448 <th class="narr_th">' . Listener
::z_xlt('Reaction') . '</th>
449 <th class="narr_th">' . Listener
::z_xlt('Severity') . '</th>
450 <th class="narr_th">' . Listener
::z_xlt('Status') . '</th>
453 foreach ($allergies_audit['lists2'] as $key => $val) {
454 $severity_option_id = $this->getCarecoordinationTable()->getOptionId('severity_ccda', '', 'SNOMED-CT:' . $val['severity_al']);
455 $severity_text = $this->getCarecoordinationTable()->getListTitle($severity_option_id, 'severity_ccda', 'SNOMED-CT:' . $val['severity_al']);
456 if ($val['enddate'] != 0 && $val['enddate'] != '') {
457 $status = 'completed';
462 $temp .= '<tr class="narr_tr">
463 <td>' . CommonPlugin
::escape($val['list_code_text']) . '</td>
464 <td>' . Listener
::z_xlt($val['reaction_text']) . '</td>
465 <td>' . Listener
::z_xlt($severity_text) . '</td>
466 <td>' . Listener
::z_xlt($status) . '</td>
470 $temp .= '</tbody></table></div>';
472 $temp .= Listener
::z_xlt('No Known Allergies');
476 $medications_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'lists3');
477 if (count($medications_audit) > 0) {
478 $temp .= '<div><table class="narr_table" border="1" width="100%">
479 <thead><tr class="narr_tr">
480 <th class="narr_th">' . Listener
::z_xlt('Medication') . '</th>
481 <th class="narr_th">' . Listener
::z_xlt('Directions') . '</th>
482 <th class="narr_th">' . Listener
::z_xlt('Start Date') . '</th>
483 <th class="narr_th">' . Listener
::z_xlt('Status') . '</th>
484 <th class="narr_th">' . Listener
::z_xlt('Indications') . '</th>
485 <th class="narr_th">' . Listener
::z_xlt('Fill Instructions') . '</th>
488 foreach ($medications_audit['lists3'] as $key => $val) {
489 if ($val['enddate'] && $val['enddate'] != 0) {
490 $active = 'completed';
495 $temp .= '<tr class="narr_tr">
496 <td>' . CommonPlugin
::escape($val['drug_text']) . '</td>
497 <td>' . CommonPlugin
::escape($val['rate'] . " " . $val['rate_unit'] . " " . $val['route_display'] . " " . $val['dose'] . " " . $val['dose_unit']) . '</td>
498 <td>' . ApplicationTable
::fixDate(substr($val['begdate'], 0, 4) . "-" . substr($val['begdate'], 4, 2) . "-" . substr($val['begdate'], 6, 2), $this->date_format
, 'yyyy-mm-dd') . '</td>
499 <td>' . Listener
::z_xlt($active) . '</td>
500 <td>' . CommonPlugin
::escape($val['indication']) . '</td>
501 <td>' . CommonPlugin
::escape($val['note']) . '</td>
505 $temp .= '</tbody></table></div>';
507 $temp .= Listener
::z_xlt('No Known Medications');
511 $problems_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'lists1');
512 if (count($problems_audit) > 0) {
513 $temp .= '<div><ul>';
515 foreach ($problems_audit['lists1'] as $key => $val) {
516 if ($val['enddate'] != 0 && $val['enddate'] != '') {
517 $status = 'Resolved';
522 $temp .= '<li>' . $i . '. ' . CommonPlugin
::escape($val['list_code_text']) . ',' . substr($val['begdate'], 0, 4) . "-" . substr($val['begdate'], 4, 2) . "-" . substr($val['begdate'], 6, 2) . ', ' . Listener
::z_xlt('Status') . ' :' . Listener
::z_xlt($status) . '</li>';
526 $temp .= '</ul></div>';
528 $temp .= Listener
::z_xlt('No Known Problems');
531 case 'immunizations':
532 $immunizations_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'immunization');
533 if (count($immunizations_audit) > 0) {
534 $temp .= '<div><table class="narr_table" border="1" width="100%">
535 <thead><tr class="narr_tr">
536 <th class="narr_th">' . Listener
::z_xlt('Vaccine') . '</th>
537 <th class="narr_th">' . Listener
::z_xlt('Date') . '</th>
538 <th class="narr_th">' . Listener
::z_xlt('Status') . '</th>
541 foreach ($immunizations_audit['immunization'] as $key => $val) {
542 $temp .= '<tr class="narr_tr">
543 <td>' . CommonPlugin
::escape($val['cvx_code_text']) . '</td>
544 <td>' . $this->getCarecoordinationTable()->getMonthString(substr($val['administered_date'], 4, 2)) . ' ' . substr($val['administered_date'], 0, 4) . '</td>
545 <td>' . Listener
::z_xlt('Completed') . '</td>
549 $temp .= '</tbody></table></div>';
551 $temp .= Listener
::z_xlt('No Known Immunizations');
555 $procedure_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'procedure');
556 if (count($procedure_audit) > 0) {
557 $temp .= '<div><table class="narr_table" border="1" width="100%">
558 <thead><tr class="narr_tr">
559 <th class="narr_th">' . Listener
::z_xlt('Procedure') . '</th>
560 <th class="narr_th">' . Listener
::z_xlt('Date') . '</th>
563 foreach ($procedure_audit['procedure'] as $key => $val) {
564 $temp .= '<tr class="narr_tr">
565 <td>' . CommonPlugin
::escape($val['code_text']) . '</td>
566 <td>' . ApplicationTable
::fixDate(substr($val['date'], 0, 4) . "-" . substr($val['date'], 4, 2) . "-" . substr($val['date'], 6, 2), $this->date_format
, 'yyyy-mm-dd') . '</td>
570 $temp .= '</tbody></table></div>';
572 $temp .= Listener
::z_xlt('No Known Procedures');
576 $lab_results_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'procedure_result');
577 if (count($lab_results_audit) > 0) {
579 <table class="narr_table" border="1">
580 <thead><tr class="narr_tr">
581 <th class="narr_th">' . Listener
::z_xlt('Laboratory Information') . '</th>
582 <th class="narr_th">' . Listener
::z_xlt('Result') . '</th>
583 <th class="narr_th">' . Listener
::z_xlt('Date') . '</th>
586 foreach ($lab_results_audit['procedure_result'] as $key => $val) {
587 if ($val['results_text']) {
588 $temp .= '<tr class="narr_tr">
589 <td>' . CommonPlugin
::escape($val['results_text']) . ($val['results_range'] != "-" ?
"(" . CommonPlugin
::escape($val['results_range']) . ")" : "") . '</td>
590 <td>' . CommonPlugin
::escape($val['results_value']) . " " . CommonPlugin
::escape($val['results_unit']) . '</td>
591 <td>' . ApplicationTable
::fixDate(substr($val['date'], 0, 4) . "-" . substr($val['date'], 4, 2) . "-" . substr($val['date'], 6, 2), $this->date_format
, 'yyyy-mm-dd') . '</td>
596 $temp .= '</tbody></table></div>';
598 $temp .= Listener
::z_xlt('No Known Lab Results');
602 $care_plan_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'care_plan');
603 if (count($care_plan_audit) > 0) {
604 $temp .= '<div><table class="narr_table" border="1" width="100%">
605 <head><tr class="narr_tr">
606 <th class="narr_th">' . Listener
::z_xlt('Planned Activity') . '</th>
607 <th class="narr_th">' . Listener
::z_xlt('Planned Date') . '</th>
610 foreach ($care_plan_audit['care_plan'] as $key => $val) {
611 $temp .= '<tr class="narr_tr">
612 <td>' . CommonPlugin
::escape($val['code_text']) . '</td>
613 <td>' . ApplicationTable
::fixDate(substr($val['date'], 0, 4) . "-" . substr($val['date'], 4, 2) . "-" . substr($val['date'], 6, 2), $this->date_format
, 'yyyy-mm-dd') . '</td>
617 $temp .= '</tbody></table></div>';
619 $temp .= Listener
::z_xlt('No Known Plan of Care');
623 $vitals_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'vital_sign');
624 if (count($vitals_audit) > 0) {
625 $temp .= '<div><table class="narr_table" border="1" width="100%">
626 <thead><tr class="narr_tr">
627 <th class="narr_th" align="right">' . Listener
::z_xlt('Date / Time') . ': </th>';
628 foreach ($vitals_audit['vital_sign'] as $key => $val) {
629 $temp .= '<th class="narr_th">' . ApplicationTable
::fixDate(substr($val['date'], 0, 4) . "-" . substr($val['date'], 4, 2) . "-" . substr($val['date'], 6, 2), $this->date_format
, 'yyyy-mm-dd') . '</th>';
632 $temp .= '</tr></thead><tbody>
634 <th class="narr_th" align="left">' . Listener
::z_xlt('Temperature') . '</th>';
635 foreach ($vitals_audit['vital_sign'] as $key => $val) {
636 $temp .= '<td>' . CommonPlugin
::escape($val['temperature']) . '</td>';
641 <th class="narr_th" align="left">' . Listener
::z_xlt('Diastolic') . '</th>';
642 foreach ($vitals_audit['vital_sign'] as $key => $val) {
643 $temp .= '<td>' . CommonPlugin
::escape($val['bpd']) . '</td>';
648 <th class="narr_th" align="left">' . Listener
::z_xlt('Systolic') . '</th>';
649 foreach ($vitals_audit['vital_sign'] as $key => $val) {
650 $temp .= '<td>' . CommonPlugin
::escape($val['bps']) . '</td>';
655 <th class="narr_th" align="left">' . Listener
::z_xlt('Head Circumference') . '</th>';
656 foreach ($vitals_audit['vital_sign'] as $key => $val) {
657 $temp .= '<td>' . CommonPlugin
::escape($val['head_circ']) . '</td>';
662 <th class="narr_th" align="left">' . Listener
::z_xlt('Pulse') . '</th>';
663 foreach ($vitals_audit['vital_sign'] as $key => $val) {
664 $temp .= '<td>' . CommonPlugin
::escape($val['pulse']) . '</td>';
669 <th class="narr_th" align="left">' . Listener
::z_xlt('Height') . '</th>';
670 foreach ($vitals_audit['vital_sign'] as $key => $val) {
671 $temp .= '<td>' . CommonPlugin
::escape($val['height']) . '</td>';
676 <th class="narr_th" align="left">' . Listener
::z_xlt('Oxygen Saturation') . '</th>';
677 foreach ($vitals_audit['vital_sign'] as $key => $val) {
678 $temp .= '<td>' . CommonPlugin
::escape($val['oxygen_saturation']) . '</td>';
683 <th class="narr_th" align="left">' . Listener
::z_xlt('Breath') . '</th>';
684 foreach ($vitals_audit['vital_sign'] as $key => $val) {
685 $temp .= '<td>' . CommonPlugin
::escape($val['breath']) . '</td>';
690 <th class="narr_th" align="left">' . Listener
::z_xlt('Weight') . '</th>';
691 foreach ($vitals_audit['vital_sign'] as $key => $val) {
692 $temp .= '<td>' . CommonPlugin
::escape($val['weight']) . '</td>';
697 <th class="narr_th" align="left">' . Listener
::z_xlt('BMI') . '</th>';
698 foreach ($vitals_audit['vital_sign'] as $key => $val) {
699 $temp .= '<td>' . CommonPlugin
::escape($val['BMI']) . '</td>';
702 $temp .= '</tr></tbody></table></div>';
704 $temp .= Listener
::z_xlt('No Known Vitals');
707 case 'social_history':
708 $social_history_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'social_history');
709 if (count($social_history_audit) > 0) {
710 $temp .= '<div><table class="narr_table" border="1" width="100%">
711 <thead><tr class="narr_tr">
712 <th class="narr_th">' . Listener
::z_xlt('Social History Element') . '</th>
713 <th class="narr_th">' . Listener
::z_xlt('Description') . '</th>
714 <th class="narr_th">' . Listener
::z_xlt('Effective Dates') . '</th>
717 foreach ($social_history_audit['social_history'] as $key => $val) {
718 $array_his_tobacco = explode("|", $val['smoking']);
719 if ($array_his_tobacco[2] != 0 && $array_his_tobacco[2] != '') {
720 $his_tob_date = substr($array_his_tobacco[2], 0, 4) . "-" . substr($array_his_tobacco[2], 4, 2) . "-" . substr($array_his_tobacco[2], 6, 2);
723 $temp .= '<tr class="narr_tr">
724 <td>' . Listener
::z_xlt('Smoking') . '</td>
725 <td>' . CommonPlugin
::escape($array_his_tobacco[0]) . '</td>
726 <td>' . ApplicationTable
::fixDate($his_tob_date, $this->date_format
, 'yyyy-mm-dd') . '</td>
730 $temp .= '</tbody></table></div>';
732 $temp .= Listener
::z_xlt('No Known Social History');
736 $encounter_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'encounter');
737 if (count($encounter_audit) > 0) {
738 $temp .= '<div><table class="narr_table" border="1" width="100%">
739 <thead><tr class="narr_tr">
740 <th class="narr_th">' . Listener
::z_xlt('Encounter') . '</th>
741 <th class="narr_th">' . Listener
::z_xlt('Performer') . '</th>
742 <th class="narr_th">' . Listener
::z_xlt('Location') . '</th>
743 <th class="narr_th">' . Listener
::z_xlt('Date') . '</th>
744 <th class="narr_th">' . Listener
::z_xlt('Encounter Diagnosis') . '</th>
745 <th class="narr_th">' . Listener
::z_xlt('Status') . '</th>
746 <th class="narr_th">' . Listener
::z_xlt('Reason for Visit') . '</th>
749 foreach ($encounter_audit['encounter'] as $key => $val) {
750 if (!empty($val['code_text'])) {
751 $encounter_activity = 'Active';
753 $encounter_activity = '';
756 $enc_date = substr($val['date'], 0, 4) . "-" . substr($val['date'], 4, 2) . "-" . substr($val['date'], 6, 2);
757 $temp .= '<tr class="narr_tr">
758 <td>' . CommonPlugin
::escape($val['pc_catname']) . '</td>
759 <td>' . CommonPlugin
::escape($val['provider_name']) . '</td>
760 <td>' . CommonPlugin
::escape($val['represented_organization_name']) . '</td>
761 <td>' . ApplicationTable
::fixDate($enc_date, $this->date_format
, 'yyyy-mm-dd') . '</td>
762 <td>' . (!empty($val['code_text']) ? CommonPlugin
::escape($val['encounter_diagnosis_issue']) : '') . '</td>
763 <td>' . Listener
::z_xlt($encounter_activity) . '</td>
764 <td>' . (!empty($val['code_text']) ? CommonPlugin
::escape($val['code_text']) : '') . '</td>
769 $temp .= '</tbody></table></div>';
771 $temp .= Listener
::z_xlt('No Known Encounters');
774 case 'functional_status':
775 $functional_cognitive_status_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'functional_cognitive_status');
776 if (count($functional_cognitive_status_audit) > 0) {
777 $temp .= '<div><table class="narr_table" border="1" width="100%">
778 <thead><tr class="narr_tr">
779 <th class="narr_th">' . Listener
::z_xlt('Functional Condition') . '</th>
780 <th class="narr_th">' . Listener
::z_xlt('Effective Dates') . '</th>
781 <th class="narr_th">' . Listener
::z_xlt('Condition Status') . '</th>
784 foreach ($functional_cognitive_status_audit['functional_cognitive_status'] as $key => $val) {
785 $temp .= '<tr class="narr_tr">
786 <td>' . CommonPlugin
::escape($val['description']) . '</td>
787 <td>' . ApplicationTable
::fixDate(substr($val['date'], 0, 4) . "-" . substr($val['date'], 4, 2) . "-" . substr($val['date'], 6, 2), $this->date_format
, 'yyyy-mm-dd') . '</td>
788 <td>' . Listener
::z_xlt('Active') . '</td>
792 $temp .= '</tbody></table></div>';
794 $temp .= Listener
::z_xlt('No Known Social Functional Status');
798 $referral_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'referral');
799 if (count($referral_audit) > 0) {
801 foreach ($referral_audit['referral'] as $key => $val) {
802 $referal_data = explode("#$%^&*", $val['body']);
803 foreach ($referal_data as $k => $v) {
804 $temp .= '<p>' . CommonPlugin
::escape($v) . '</p>';
810 $temp .= Listener
::z_xlt('No Known Referrals');
814 $temp .= Listener
::z_xlt('No Known Clinical Instructions');
816 case 'discharge_medication':
817 $discharge_medication_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'discharge_medication');
818 $temp .= '<div><table class="narr_table" border="1" width="100%">
819 <thead><tr class="narr_tr">
820 <th class="narr_th">' . Listener
::z_xlt('Medication') . '</th>
821 <th class="narr_th">' . Listener
::z_xlt('Directions') . '</th>
822 <th class="narr_th">' . Listener
::z_xlt('Start Date') . '</th>
823 <th class="narr_th">' . Listener
::z_xlt('Status') . '</th>
824 <th class="narr_th">' . Listener
::z_xlt('Indications') . '</th>
825 <th class="narr_th">' . Listener
::z_xlt('Fill Instructions') . '</th>
828 foreach ($discharge_medication_audit['discharge_medication'] as $key => $val) {
829 if ($val['enddate'] && $val['enddate'] != 0) {
830 $active = 'completed';
835 $temp .= '<tr class="narr_tr">
836 <td>' . CommonPlugin
::escape($val['drug_text']) . '</td>
837 <td>' . CommonPlugin
::escape($val['rate'] . " " . $val['rate_unit'] . " " . $val['route_display'] . " " . $val['dose'] . " " . $val['dose_unit']) . '</td>
838 <td>' . ApplicationTable
::fixDate(substr($val['begdate'], 0, 4) . "-" . substr($val['begdate'], 4, 2) . "-" . substr($val['begdate'], 6, 2), $this->date_format
, 'yyyy-mm-dd') . '</td>
839 <td>' . Listener
::z_xlt($active) . '</td>
840 <td>' . CommonPlugin
::escape($val['indication']) . '</td>
841 <td>' . CommonPlugin
::escape($val['note']) . '</td>
845 $temp .= '</tbody></table></div>';
847 case 'discharge_summary':
848 $discharge_summary_audit = $this->getCarecoordinationTable()->createAuditArray($amid, 'discharge_summary');
850 foreach ($discharge_summary_audit['discharge_summary'] as $key => $val) {
851 $text = str_replace("#$%", "<br />", CommonPlugin
::escape($val['text']));
866 * @return \Carecoordination\Model\CarecoordinationTable
868 public function getCarecoordinationTable()
870 return $this->carecoordinationTable
;
874 * Returns the application table.
876 public function getApplicationTable()
878 return $this->applicationTable
;
882 * PHP 7 requires foreach iterables to not be null / undefined. There's a ton of code in the revandapprove.phtml file that assumes
883 * the arrays are not empty... so to skip over the foreach's we are giving them empty values.
885 * @param $audit_master_id
889 private function getRevAndApproveAuditArray($audit_master_id, $table_name)
891 $audit = $this->getCarecoordinationTable()->createAuditArray($audit_master_id, $table_name);
892 if (empty($audit[$table_name])) {
893 $audit[$table_name] = []; // leave it empty so we don't fail in the template
899 private function sanitizeZip($zipLocation)
901 // TODO: @adunsulag NOTE that zip files can be in any order... so we can't assume that this is alphabetical
902 // to fix this may involve extracting the zip and re-ordering all of the entries...
903 // another one would be to just create hash map indexes with patient names mapped to nested documents...
904 // this will only be an issue if we are migrating documents as the CCDA files themselves are self-contained
905 // TODO: fire off an event about sanitizing the zip file
906 // should have sanitization settings and let someone filter them...
907 // event response should have a boolean for skipSanitization in case a module has already done the sanitization
909 $z = new \
ZipArchive();
910 // if a zip file exist we want to overwrite it when we save
911 $z->open($zipLocation);
912 $z->setArchiveComment(""); // remove any comments so we don't deal with buffer overflows on the zip extraction
913 $patientCountHash = [];
915 $patientNameIndex = 1;
916 $patientDocumentsIndex = 2;
919 $maxFileComponents = 5;
921 for ($i = 0; $i < $z->numFiles
; $i++
) {
922 $stat = $z->statIndex($i);
923 // explode and make sure we have our three parts
924 // our max directory structure is 4... anything more than that and we will bail
925 $fileComponents = explode("/", str_replace('\\', '/', $stat['name']), 5);
926 $componentCount = count($fileComponents);
927 $shouldDeleteIndex = false;
929 // now we need to do our document import for our ccda for this patient
930 if ($componentCount <= $patientNameIndex) {
931 $shouldDeleteIndex = false; // we don't want to delete if we are in folders before our patient name index
932 } elseif ($componentCount == ($patientNameIndex +
1)) {
933 // if they have more than maxDocuments in ccd files we need to break out of someone trying to directory
934 // bomb the file system
935 $patientCountHash[$patientNameIndex] = $patientCountHash[$patientNameIndex] ??
0;
937 // let's check for ccda
938 if ($patientCount > $maxPatients) {
939 $shouldDeleteIndex = true; // no more processing of patient ccdas as we've reached our max import size
940 } elseif ($patientCountHash[$patientNameIndex] > $maxDocuments) {
941 $shouldDeleteIndex = true;
942 // can fire off events for modifying what files we keep / process...
943 // we don't process anything but xml ccds
944 } elseif (strrpos($fileComponents[$patientNameIndex], '.xml') === false) {
945 $shouldDeleteIndex = true;
946 // if we have a ccd we need to set our document count and increment our patient count
947 // note this logic allows multiple patient ccds to be here as long as they are in the same folder
948 } elseif (!isset($patientCountHash[$fileComponents[$patientNameIndex]])) {
949 $patientCountHash[$patientNameIndex] = 0;
952 $patientCountHash[$patientNameIndex];
954 } elseif ($componentCount == ($patientDocumentsIndex +
1)) {
955 if ($patientCountHash[$patientNameIndex] ??
'' > $maxDocuments) {
956 $shouldDeleteIndex = true;
958 if (!empty($patientCountHash[$patientNameIndex])) {
959 $patientCountHash[$patientNameIndex] +
= 1;
961 $patientCountHash[$patientNameIndex] = 0;
965 $shouldDeleteIndex = true;
968 // TODO: fire off an event with the patient name, current index, file name, and zip archive object
969 // we can filter on whether we should keep this and let module writers do their own thing if they want to
970 // retain any of the documents or not for their own custom processing.
971 if ($shouldDeleteIndex) {
978 private function printZipContents($zipLocation)
980 $z = new \
ZipArchive();
981 $z->open($zipLocation);
982 for ($i = 0; $i < $z->numFiles
; $i++
) {
983 $stat = $z->statIndex($i);
984 (new SystemLogger())->error("File in zip is " . $stat['name']);
989 private function importZipUpload($request)
991 // our file structure is
992 // import_name / patient_name / ccda.xml
993 // import_name / patient_name / ccda.html
994 // import_name / patient_name / ccda.xsl
996 // we will need to have these limit options configurable but we have to be careful to
997 // we will limit our docsToImport to 500
998 // we will limit our patientsToImport to 500
1000 $z = new \
ZipArchive();
1001 $tmpFile = reset($_FILES);
1002 $tmpFileName = $tmpFile['tmp_name'];
1003 $this->printZipContents($tmpFileName);
1005 // make sure we only have our documents folder and our ccda file
1006 $this->sanitizeZip($tmpFileName);
1007 $z->open($tmpFileName);
1008 $category_details = $this->getCarecoordinationTable()->fetch_cat_id('CCDA');
1009 $catId = $category_details[0]['id'] ??
null;
1010 if (empty($catId)) {
1011 throw new \
RuntimeException("Could not find document category id for category of CCDA");
1013 $auditMasterRecordByPatients = [];
1014 for ($i = 0; $i < $z->numFiles
; $i++
) {
1015 $stat = $z->statIndex($i);
1016 // explode and make sure we have our three parts
1017 // our max directory structure is 4... anything more than that and we will bail
1018 $fileComponents = explode("/", str_replace('\\', '/', $stat['name']), 5);
1019 $componentCount = count($fileComponents);
1021 // now we need to do our document import for our ccda for this patient
1022 if ($componentCount > 0) {
1023 // let's process the ccda
1024 $file_name = basename($stat['name']);
1027 $ob = new Document();
1028 $contents = $z->getFromIndex($i);
1029 if (stripos($file_name, '.xml') !== false) {
1030 $ret = $ob->createDocument($pid, $catId, $file_name, 'text/xml', $contents);
1032 throw new \
RuntimeException("Failed to create document from zip file " . $file_name . " error returned was " . $ret);
1035 $auditMasterRecordId = $this->getCarecoordinationTable()->import($ob->get_id());
1036 // we can use this to do any other processing as the files should be in order
1037 $auditMasterRecordByPatients[$fileComponents[2] ??
''] = $auditMasterRecordId;