Files
streamline-emr/docker/streamline-src/app/Http/Controllers/AuditTrailController.php
T
alec.turner a424394109 Build modified streamline images
The official image from streamline does not currently work for the arm64
platform. As a temporary measure, the source code and docker build scripts
have been lifted from the official images and are used to build locally.

Some additional modifications are made to reduce overall image size, these
are documented in docker/README.md
2024-03-10 16:17:50 -07:00

626 lines
27 KiB
PHP
Executable File

<?php
namespace Streamline\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use OwenIt\Auditing\Models\Audit;
use Carbon\Carbon;
use Streamline\Models\User;
use Streamline\Models\Patient;
use Streamline\Models\PatientEpisode;
use Streamline\Models\Triage;
use Barryvdh\Snappy\Facades\SnappyPdf;
class AuditTrailController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('permission:audit_trail-list', ['only' => ['index', 'search', 'patient_timeline', 'user_timeline']]);
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$audits = Audit::with('user')
->where('created_at', '>=', Carbon::now()->subDay())
->orderBy('created_at', 'desc')
->paginate(100);
if ($audits->count() <= 0) {
flash("There are no records in the audit trail")->error();
}
// users with activity
$user_id_array = DB::table('audits')
->groupBy('user_id')
->select('user_id')
->get()
->toArray();
$user_id = array(count($user_id_array));
foreach ($user_id_array as $value) {
$user_id[$value->user_id] = get_full_name($value->user_id, 'id', 'first_name', 'last_name', 'users');
}
// remove the count from the array
unset($user_id[0]);
// add --select--
$user_id = ['all' => 'ALL'] + $user_id;
// users with activity
$action_performed_array = DB::table('audits')
->groupBy('event')
->select('event')
->get()
->toArray();
$action_performed = array(count($action_performed_array));
foreach ($action_performed_array as $value) {
$action_performed[$value->event] = ucwords($value->event);
}
// remove the count from the array
unset($action_performed[0]);
// add --select--
$action_performed = ['all' => 'ALL'] + $action_performed;
// users with activity
$records_affected_array = DB::table('audits')
->groupBy('auditable_type')
->select('auditable_type')
->get()
->toArray();
$records_affected = array(count($records_affected_array));
foreach ($records_affected_array as $value) {
$record = str_replace('Streamline\\', '', $value->auditable_type);
/*================ show better text on forming the drop down ===========*/
$cleaned_model_name = str_replace('Streamline\\', '', $value->auditable_type);
$cleaned_model_name = preg_split('/(?=[A-Z])/', $cleaned_model_name);
$spaced_model_name = implode(' ', $cleaned_model_name);
/*========================================================*/
$records_affected[$record] = $spaced_model_name;
}
// remove the count from the array
unset($records_affected[0]);
// add --select--
$records_affected = ['all' => 'ALL'] + $records_affected;
// get all first names
$first_names = DB::table('users')->pluck('first_name', 'id');
// get all last names
$last_names = DB::table('users')->pluck('last_name', 'id');
return view('audit_trail.index',
compact([
'audits',
'first_names',
'last_names',
'user_id',
'action_performed',
'records_affected'
]));
}
public function audit_trail_print(Request $request)
{
$audit_trail_values = json_decode($request->data, 1);
$hospitalInfo = DB::table('hospital_information')->find(1);
$receipt_date = date('Y-m-d h:i:s');
$data = [
'hospitalInfo' => $hospitalInfo,
'audit_trail' => $request->data,
'receipt_data' => $receipt_date
];
$audit_trail = json_decode($data['audit_trail'])->data;
//return view('audit_trail.audit_trail_print', compact('audit_trail'));
$pdf = SnappyPDF::loadView('audit_trail.audit_trail_print', compact('audit_trail'))
->setOrientation('portrait')
->setOption('margin-bottom', 7)
->setOption('margin-top', 5)
->setOption('footer-html', '<i>Stre@mline</i>');
return $pdf->inline('Audit Trail' . date(" d-m-y h:ia") . '.pdf');
}
public function failed_login_print(Request $request){
$audit_trail_values = json_decode($request->data, 1);
$hospitalInfo = DB::table('hospital_information')->find(1);
$receipt_date = date('Y-m-d h:i:s');
$data = [
'hospitalInfo' => $hospitalInfo,
'failed_login_data' => $request->data,
'receipt_data' => $receipt_date
];
$failed_login_data = json_decode($data['failed_login_data'])->data;
//return view('audit_trail.failed_login_print', compact('data', 'failed_login_data'));
$pdf = SnappyPDF::loadView('audit_trail.failed_login_print', compact('failed_login_data'))
->setOrientation('portrait')
->setOption('margin-bottom', 7)
->setOption('margin-top', 5)
->setOption('footer-html', '<i>Stre@mline</i>');
return $pdf->inline('Audit Trail' . date(" d-m-y h:ia") . '.pdf');
}
public function online_user_print(Request $request){
$audit_trail_values = json_decode($request->data, 1);
$hospitalInfo = DB::table('hospital_information')->find(1);
$receipt_date = date('Y-m-d h:i:s');
$data = [
'hospitalInfo' => $hospitalInfo,
'audit_trail' => $audit_trail_values,
'receipt_data' => $receipt_date
];
$online_users = $audit_trail_values['data'];
//return view('audit_trail.online_user_print', compact('online_users'));
$pdf = SnappyPDF::loadView('audit_trail.online_user_print', compact('online_users'))
->setOrientation('portrait')
->setOption('margin-bottom', 7)
->setOption('margin-top', 5)
->setOption('footer-html', '<i>Stre@mline</i>');
return $pdf->inline('Audit Trail' . date(" d-m-y h:ia") . '.pdf');
}
/**
* Return audit table.
*
* @return \Illuminate\Http\Response
*/
public static function return_audit_table() {
$audits = Audit::with('user')->orderBy('created_at', 'desc')->paginate(100);
echo " <b>Old values</b>
<table>
@if(array_key_exists('updated_at', $audit->toArray()['old_values']))
@foreach ($audit->toArray()['old_values'] as $key => $value)
<tr>
<td>{{ format_key($key) }}</td>
$patient = Streamline\Models\Patient::where('id', $value)->first();
$user = Streamline\Models\User::where('id', $value)->first();
if($patient->id == $value && $key == 'patient_id')
<td>{{ $patient ? $patient->first_name : '' }}
{{ $patient ? $patient->last_name : '' }}</td>
@elseif($key == 'updated_by' || $key == 'created_by' || $key ==
'staff_id')
<td>{{ $user ? $user->first_name : '' }}
{{ $user ? $user->last_name : '' }}</td>
@else
<td>{{ $value }}</td>
@endif
</tr>
@endforeach
@endif
</table>
<b>New values</b>
</html>";
}
/**
* Search for audit trail records with given parameters
*
* @param Request $request
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Foundation\Application|\Illuminate\View\View
*/
public function search(Request $request)
{
$user_id = $request->user_id;
$action_performed = $request->action_performed;
$records_affected = $request->records_affected;
// Get the selected date criteria
$dates = $request->dates;
$filters = [];
if ($user_id != 'all') :
array_push($filters, ['user_id', '=', $user_id]);
endif;
if ($action_performed != 'all') :
array_push($filters, ['event', '=', $action_performed]);
endif;
if ($records_affected != 'all') :
array_push($filters, ['auditable_type', '=', 'Streamline\\' . $records_affected]);
endif;
// switch the selected date search criteria - today, yesterday, last week, last month etc
switch ($dates) {
case 'today':
$today = Carbon::today()->format('Y-m-d');
$audits = Audit::orderBy('created_at', 'desc')->where($filters)->whereDate('created_at', $today)->paginate(100);
break;
case 'yesterday':
$yesterday = Carbon::yesterday()->format('Y-m-d');
$audits = Audit::orderBy('created_at', 'desc')->where($filters)->whereDate('created_at', $yesterday)->paginate(100);
break;
case 'week':
$week_ago = Carbon::today()->subDays(7)->format('Y-m-d');
$audits = Audit::orderBy('created_at', 'desc')->where($filters)->whereDate('created_at', '>=', $week_ago)->paginate(100);
break;
case 'month':
$month_ago = Carbon::today()->subDays(30)->format('Y-m-d');
$audits = Audit::orderBy('created_at', 'desc')->where($filters)->whereDate('created_at', '>=', $month_ago)->paginate(100);
break;
case 'custom-date':
$start_date = Carbon::parse($request->start_date)->format('Y-m-d');
$audits = Audit::orderBy('created_at', 'desc')->where($filters)->whereDate('created_at', '=', $start_date)->paginate(100);
break;
case 'custom-range':
$start_date = Carbon::parse($request->start_date)->format('Y-m-d');
$end_date = Carbon::parse($request->end_date)->format('Y-m-d');
$audits = Audit::orderBy('created_at', 'desc')->where($filters)->whereBetween('created_at', [$start_date, $end_date])->paginate(100);
break;
default:
$audits = Audit::orderBy('created_at', 'desc')->where($filters)->paginate(150);
break;
}
if ($audits->count() <= 0) {
flash("There are no records found in the search")->error();
}
// users with activity
$user_id_array = DB::table('audits')->groupBy('user_id')->select('user_id')->get()->toArray();
$user_id = array(count($user_id_array));
foreach ($user_id_array as $value) {
$user_id[$value->user_id] = get_full_name($value->user_id, 'id', 'first_name', 'last_name', 'users');
}
// remove the count from the array
unset($user_id[0]);
// add --select--
$user_id = ['all' => 'ALL'] + $user_id;
// users with activity
$action_performed_array = DB::table('audits')->groupBy('event')->select('event')->get()->toArray();
$action_performed = array(count($action_performed_array));
foreach ($action_performed_array as $value) {
$action_performed[$value->event] = ucwords($value->event);
}
// remove the count from the array
unset($action_performed[0]);
// add --select--
$action_performed = ['all' => 'ALL'] + $action_performed;
// users with activity
$records_affected_array = DB::table('audits')->groupBy('auditable_type')->select('auditable_type')->get()->toArray();
$records_affected = array(count($records_affected_array));
foreach ($records_affected_array as $value) {
$record = str_replace('Streamline\\', '', $value->auditable_type);
//$records_affected[$record] = $record;
/*================ show better text on forming the drop down ===========*/
$cleaned_model_name = str_replace('Streamline\\', '', $value->auditable_type);
$cleaned_model_name = preg_split('/(?=[A-Z])/', $cleaned_model_name);
$spaced_model_name = implode(' ', $cleaned_model_name);
/*========================================================*/
$records_affected[$record] = $spaced_model_name;
}
// remove the count from the array
unset($records_affected[0]);
// add --select--
$records_affected = ['all' => 'ALL'] + $records_affected;
// get all first names
$first_names = DB::table('users')->pluck('first_name', 'id');
// get all last names
$last_names = DB::table('users')->pluck('last_name', 'id');
return view('audit_trail.index', compact('audits', 'first_names', 'last_names', 'user_id', 'action_performed', 'records_affected'));
}
public function audit_trail_details($id)
{
//$audits = Audit::where(['id' => $id])->get();
$audit = Audit::where(['id' => $id])->first();
$user = User::where('id', $audit->user_id)->first();
//dd($audit);
// get all first names
$first_names = DB::table('users')->pluck('first_name', 'id');
// get all last names
$last_names = DB::table('users')->pluck('last_name', 'id');
// get full names
$full_names = $user->first_name." ".$user->last_name;
return view('audit_trail.audit_trail_details', compact('full_names', 'audit', 'last_names', 'first_names'));
}
public function select_patient_and_episode(Request $request) {
$patient = [];
$patient_episodes_details = [];
if (isset($request->patient_id)) {
$patient_searched = true;
$patient = Patient::find($request->patient_id);
if ($patient) {
$patient_episodes = PatientEpisode::where('patient_id', $patient->id)->get();
$counter = 0;
foreach ($patient_episodes as $episode) {
$patient_episodes_details[$counter]['id'] = $episode->id;
$patient_episodes_details[$counter]['created_at'] = streamline_date_time($episode->created_at);
$counter++;
}
}
} else {
$patient_searched = false;
}
return view('audit_trail.select_patient_and_episode', compact('patient_searched', 'patient', 'patient_episodes_details'));
}
public function patient_timeline($patient_id, $episode_id)
{
$patient = Patient::find($patient_id);
$episode = PatientEpisode::find($episode_id);
$patient_timeline = [];
$index = 0;
// add when episode was created
$patient_timeline[$index]["date"] = streamline_date_time($episode->created_at);
$patient_timeline[$index]["text"] = "Episode was started by " . get_full_name($episode->created_by, 'id', 'first_name', 'last_name', 'users');
$index++;
// fetch triage info
if (!is_null($episode->triage_id)) {
$triage_audits = Audit::where(['auditable_type' => 'Streamline\\Triage', 'auditable_id' => $episode->triage_id])
->select('old_values', 'new_values', 'event', 'created_at', 'user_id')
->orderBy('created_at', 'desc')
->get()
->toArray();
foreach ($triage_audits as $audit) {
if ($audit["event"] == "created") {
$patient_timeline[$index]["date"] = streamline_date_time($audit["created_at"]);
$patient_timeline[$index]["text"] = "Patient Triage was carried out by " . get_full_name($audit["user_id"], 'id', 'first_name', 'last_name', 'users') .
" with patient being assigned to the " . get_name($audit["new_values"]["clinic_allocation"], 'id', 'name', 'clinics') . " clinic with severe grade of " . severe_grade($audit["new_values"]["severe_grade"]);
} else {
$text = "Patient Triage was edited by " . get_full_name($audit["user_id"], 'id', 'first_name', 'last_name', 'users');
if (key_exists("clinic_allocation", $audit["new_values"])) {
$text .= "<br>Clinic allocation changed to " . get_name($audit["new_values"]["clinic_allocation"], 'id', 'name', 'clinics');
}
if (key_exists("severe_grade", $audit["new_values"])) {
$text .= "<br>Severe Grade changed to " . severe_grade($audit["new_values"]["severe_grade"]);
}
$patient_timeline[$index]["date"] = streamline_date_time($audit["created_at"]);
$patient_timeline[$index]["text"] = $text;
}
$index++;
}
}
// fetch consultation data
if (!is_null($episode->consultation_id)) {
$consultation_audits = Audit::where(['auditable_type' => 'Streamline\\Consultation', 'auditable_id' => $episode->consultation_id])
->select('old_values', 'new_values', 'event', 'created_at', 'user_id')
->orderBy('created_at', 'desc')
->get()
->toArray();
foreach ($consultation_audits as $audit) {
if ($audit["event"] == "created") {
$patient_timeline[$index]["date"] = streamline_date_time($audit["created_at"]);
$patient_timeline[$index]["text"] = "Patient Consultation was carried out by " . get_full_name($audit["user_id"], 'id', 'first_name', 'last_name', 'users') .
" with primary diagnosis set as " . get_name($audit["new_values"]["primary_diagnosis"], 'id', 'name', 'diagnoses') . " and outcome of " . get_name($audit["new_values"]["outcome_id"], 'id', 'name', 'outcomes');
} else {
$text = "Patient Consultation was edited by " . get_full_name($audit["user_id"], 'id', 'first_name', 'last_name', 'users');
if (key_exists("primary_diagnosis", $audit["new_values"])) {
$text .= "<br>Primary Diagnosis changed to " . get_name($audit["new_values"]["primary_diagnosis"], 'id', 'name', 'diagnoses');
}
if (key_exists("outcome_id", $audit["new_values"])) {
$text .= "<br>Outcome changed to " . get_name($audit["new_values"]["outcome_id"], 'id', 'name', 'outcomes');
}
$patient_timeline[$index]["date"] = streamline_date_time($audit["created_at"]);
$patient_timeline[$index]["text"] = $text;
}
$index++;
}
}
// fetch treatment data
if (!is_null($episode->treatment_id)) {
$treatment_audits = Audit::where(['auditable_type' => 'Streamline\\Treatment', 'auditable_id' => $episode->treatment_id])
->select('old_values', 'new_values', 'event', 'created_at', 'user_id')
->orderBy('created_at', 'desc')
->get()
->toArray();
foreach ($treatment_audits as $audit) {
if ($audit["event"] == "created") {
$patient_timeline[$index]["date"] = streamline_date_time($audit["created_at"]);
$text = "Prescriptions were ordered by " . get_full_name($audit["user_id"], 'id', 'first_name', 'last_name', 'users');
$text .= "<br><b>Treatments</b>:";
$drugs = explode(",", $audit["new_values"]["drugs"]);
$durations = explode(",", $audit["new_values"]["durations"]);
for ($i = 0; $i < count($drugs); $i++) {
$text .= "<br>" . get_name($drugs[$i], 'id', 'name', 'drugs') .
" for " . $durations[$i];
}
$patient_timeline[$index]["text"] = $text;
} else if ($audit["event"] == "updated" && key_exists("drugs", $audit["new_values"]) && key_exists("durations", $audit["new_values"])) {
$patient_timeline[$index]["date"] = streamline_date_time($audit["created_at"]);
$text = "Prescriptions were edited by " . get_full_name($audit["user_id"], 'id', 'first_name', 'last_name', 'users');
$text .= "<br><b>Treatments</b>:";
$drugs = explode(",", $audit["new_values"]["drugs"]);
$durations = explode(",", $audit["new_values"]["durations"]);
for ($i = 0; $i < count($drugs); $i++) {
$text .= "<br>" . get_name($drugs[$i], 'id', 'name', 'drugs') .
" for " . $durations[$i];
}
$patient_timeline[$index]["text"] = $text;
}
$index++;
}
}
// check for any ordered investigations
$ordered_investigations = DB::table('ordered_investigations')
->where('patient_id', $patient_id)
->where('episode_id', $episode_id)
->get();
foreach ($ordered_investigations as $inv) {
$investigation_audits = Audit::where(['auditable_type' => 'Streamline\\OrderedInvestigation', 'auditable_id' => $inv->id])
->select('old_values', 'new_values', 'event', 'created_at', 'user_id')
->orderBy('created_at', 'desc')
->get()
->toArray();
foreach ($investigation_audits as $audit) {
if ($audit["event"] == "created") {
$patient_timeline[$index]["date"] = streamline_date_time($audit["created_at"]);
$text = "Investigations were ordered by " . get_full_name($audit["user_id"], 'id', 'first_name', 'last_name', 'users');
$text .= "<br><b>Investigations ( " . $audit["new_values"]["order_type"] . ")</b>:";
$invs_id = explode(",", $audit["new_values"]["investigation_id"]);
for ($i = 0; $i < count($invs_id); $i++) {
$text .= "<br>" . get_name($invs_id[$i], 'id', 'name', 'investigations');
}
$patient_timeline[$index]["text"] = $text;
} else if ($audit["event"] == "updated" && key_exists("investigation_id", $audit["new_values"])) {
$patient_timeline[$index]["date"] = streamline_date_time($audit["created_at"]);
$text = "Investigations were edited by " . get_full_name($audit["user_id"], 'id', 'first_name', 'last_name', 'users');
$text .= "<br><b>Investigations ( " . get_name($inv->id, 'id', 'order_type', 'ordered_investigations') . ")</b>:";
$invs_id = explode(",", $audit["new_values"]["investigation_id"]);
for ($i = 0; $i < count($invs_id); $i++) {
$text .= "<br>" . get_name($invs_id[$i], 'id', 'name', 'investigations');
}
$patient_timeline[$index]["text"] = $text;
}
$index++;
}
}
// check for any ordered procedures
$ordered_procedures = DB::table('ordered_procedures')
->where('patient_id', $patient_id)
->where('episode_id', $episode_id)
->get();
// here we shall categorize the procedures. If their created at is within 5 seconds
// then they are in the same group of being created
$categorized_procedures = [];
$counter = 0;
$time_last_procedure_was_created = 0;
foreach ($ordered_procedures as $procedure) {
if ($time_last_procedure_was_created != 0) {
$diff_in_time = Carbon::createFromTimeString($procedure->created_at)->diffInSeconds($time_last_procedure_was_created);
if ($diff_in_time < 5) {
$categorized_procedures[$counter - 1]['ids'][] = $procedure->procedure_id;
$categorized_procedures[$counter - 1]['deleted'][] = $procedure->deleted_at;
} else {
$categorized_procedures[$counter]['ids'] = [$procedure->procedure_id];
$categorized_procedures[$counter]['time'] = $procedure->created_at;
$categorized_procedures[$counter]['user_id'] = $procedure->created_by;
$categorized_procedures[$counter]['deleted'] = [$procedure->deleted_at];
$time_last_procedure_was_created = $procedure->created_at;
$counter++;
}
} else {
$categorized_procedures[$counter]['ids'] = [$procedure->procedure_id];
$categorized_procedures[$counter]['time'] = $procedure->created_at;
$categorized_procedures[$counter]['user_id'] = $procedure->created_by;
$categorized_procedures[$counter]['deleted'] = [$procedure->deleted_at];
$time_last_procedure_was_created = $procedure->created_at;
$counter++;
}
}
foreach ($categorized_procedures as $record) {
$patient_timeline[$index]["date"] = streamline_date_time($record['time']);
$text = "Procedures were ordered by " . get_full_name($record['user_id'], 'id', 'first_name', 'last_name', 'users');
$text .= "<br><b>Procedures</b>:";
for ($i = 0; $i < count($record['ids']); $i++) {
$text .= "<br>" . get_name($record['ids'][$i], 'id', 'name', 'procedures');
if ($record['deleted'][$i]) {
$text .= " <span style='color: red'>was later removed at " . streamline_date_time($record['deleted'][$i]) . "</span>";
}
}
$patient_timeline[$index]["text"] = $text;
$index++;
}
// sort the array in descending order according to the date
usort($patient_timeline, array($this, "sort_timeline_by_date"));
return view('audit_trail.patient_timeline', compact('patient', 'patient_timeline'));
}
public function user_timeline(Request $request)
{
//
}
function sort_timeline_by_date($a, $b)
{
return strcmp($a["date"], $b["date"]);
}
}