package LIMS::Controller::Outreach;
use Moose;
BEGIN { extends 'LIMS::Base' }
with (
'LIMS::Controller::Roles::Misc', # redirect_after_edit_success(), format_dfv_msg()
'LIMS::Controller::Roles::FormData', # get_data_from_dfv
'LIMS::Controller::Roles::DataFile', # slurp_file_contents()
'LIMS::Controller::Roles::DataImport', # parse_data_file(), check_datafile_integrity()
'LIMS::Controller::Roles::RecordHandler',
);
use namespace::clean -except => 'meta';
__PACKAGE__->meta->make_immutable(inline_constructor => 0);
use Data::Dumper;
use LIMS::Controller::Report;
# ------------------------------------------------------------------------------
# default() should never be called direct - redirect to start page:
sub default : StartRunmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->redirect( $self->query->url );
}
# ------------------------------------------------------------------------------
# outreach/=/x should never be called direct - forward to startrunmode:
sub load : Runmode {
return shift->forward('default');
}
# ------------------------------------------------------------------------------
sub demographics : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('edit_outreach_data');
my $request_id = $self->param('id')
|| return $self->error('no request_id passed to '.$self->get_current_runmode);
my $patient_id = $self->param('Id')
|| return $self->error('no patient_id passed to '.$self->get_current_runmode);
# can't use DFV check_rm() here as target is in another controller:
my $profile = $self->validate('outreach_demographics');
my $dfv = $self->validate_form_params($profile); # warn Dumper $dfv;
my $home = $self->query->url . '/report/outreach/' . $request_id;
if ($dfv->has_invalid || $dfv->has_missing) {
my $msg = $self->format_dfv_msg($dfv); # parse dfv template
$self->flash( error => $msg ); # warn Dumper $dfv->msgs;
return $self->redirect($home);
}
my $data = $dfv->valid;
# add patient_id:
$data->{patient_id} = $patient_id; # warn Dumper $data; return 1;
# param 'dispatch_to' is Outreach param - remove & update separately:
if ( my $dispatch_to = $data->{dispatch_to} ) {
my %args = (
patient_id => $patient_id,
dispatch_to => $dispatch_to,
);
my $rtn = $self->model('Outreach')->update_dispatch_detail(\%args);
return $self->error($rtn) if $rtn; # only return on error here
delete $data->{dispatch_to}; # or downstream model method blows up
}
my $rtn = $self->model('Patient')->update_patient_demographics($data);
return $rtn
? $self->error($rtn)
: $self->redirect_after_edit_success('/report/outreach/' . $request_id);
}
# ------------------------------------------------------------------------------
# DFV error return page for questionnaire():
sub questionnaire {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $errs = shift; $self->stash( errs => $errs );
my $menu_options = $self->model('Outreach')->get_menu_options;
my %h = ( menu_options => $menu_options ); # format required by outreach tt's
$self->tt_params( outreach => \%h );
return $self->tt_process('outreach/err_questionnaire.tt', $errs);
}
# ------------------------------------------------------------------------------
sub do_questionnaire : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('edit_outreach_data');
my $request_id = $self->param('id')
|| return $self->error('no id passed to '.$self->get_current_runmode);
my $dfv = $self->check_rm(
questionnaire => $self->validate('outreach_questionnaire')
); # don't return on error, update tables then handle error
my $data = $dfv->valid;
$data->{request_id} = $request_id; # warn Dumper $data;
# update questionnaire tables even if have missing fields:
my $rtn = $self->model('Outreach')->update_patient_questionnaire($data);
if ($rtn) { # if db error
return $self->error($rtn);
}
elsif ($self->dfv_error_page) { # only for info - still want db updates
return $self->dfv_error_page;
}
else {
my $msg = $self->messages('outreach')->{questionnaire_success};
$self->flash( info => $msg );
}
$self->redirect( $self->query->url . '/report/outreach/' . $request_id );
}
# ------------------------------------------------------------------------------
sub lab_results : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('edit_outreach_data');
my $request_id = $self->param('id')
|| return $self->error('no id passed to '.$self->get_current_runmode);
# can't use DFV check_rm() here as target is in another controller:
my $profiles = $self->validate('outreach_lab_results'); # profiles for all depts
my $dept = $self->query->param('department') # eg immunology
|| return $self->error("no hidden 'department' param passed to "
. $self->get_current_runmode); # or data validation methods fail
my $dfv = $self->validate_form_params($profiles->{$dept}); # warn Dumper $dfv;
my $data = $dfv->valid;
# add patient_id:
$data->{_request_id} = $request_id; # warn Dumper $data;
my $rtn = $self->model('Outreach')->update_lab_params($data);
return $self->error($rtn) if $rtn; # if db error
my $addr = '/report/outreach/' . $request_id;
if ($dfv->has_invalid || $dfv->has_missing) {
my $msg = $self->format_dfv_msg($dfv); # parse dfv template
$self->flash( error => $msg ); # warn Dumper $dfv->msgs;
return $self->redirect( $self->query->url . $addr );
}
else {
return $self->redirect_after_edit_success($addr);
}
}
# ------------------------------------------------------------------------------
# import flow data from text file:
sub import_datafile : Runmode { # code taken from C::Results::import_datafile()
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('edit_outreach_data');
my $request_id = $self->param('id')
|| return $self->error('no id passed to ' . $self->get_current_runmode);
my $filename = $self->query->param('datafile')
|| return $self->error('no filename passed to ' . $self->get_current_runmode);
# extract contents of datafile to object accessor:
my $data = $self->get_datafile_contents($filename);
$self->parse_data_file($data);
# check internal datafile refs (filename & patient ids) match
$self->_check_datafile_integrity() # sets flash message for redirect
|| return $self->redirect( $self->query->url . '/image/=/' . $request_id );
# get results from data file:
my $results = $self->_get_datafile_results() # sets flash message for redirect
|| return $self->redirect( $self->query->url . '/image/=/' . $request_id );
# get datafiles config:
my $datafile_cfg = $self->datafiles_config; # warn Dumper $datafile_cfg;
# get list of field names for table update:
my $cols = $datafile_cfg->{CMP}->{cols}; # warn Dumper $cols;
# create hash of col => result:
my %data; @data{@$cols} = @$results;
# add request_id & department:
$data{_request_id} = $request_id;
$data{department} = 'flow_cytometry'; # warn Dumper \%data;
my $rtn = $self->model('Outreach')->update_lab_params(\%data);
if ($rtn) { # if db error
return $self->error($rtn);
}
else {
$self->flash( info => $self->messages('outreach')->{data_import_success} );
return $self->redirect(
$self->query->url . '/report/outreach/' . $request_id );
}
}
# ------------------------------------------------------------------------------
sub edit_followup : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $errs = shift;
return $self->forbidden() unless $self->user_can('edit_outreach_data');
my $request_id = $self->param('id')
|| return $self->error('no request_id passed to '.$self->get_current_runmode);
{ # follow-up data:
my $data = $self->model('Outreach')->get_followup_data($request_id);
$self->tt_params( followup_data => $data );
}
{ # request_data:
my $data
= $self->model('Request')->get_patient_and_request_data($request_id);
$self->tt_params( request_data => $data );
}
{ # follow-up options:
my $options = $self->model('Outreach')->followup_options_map;
$self->tt_params( followup_options => $options );
}
$self->js_validation_profile('outreach_followup');
return $self->tt_process();
}
# ------------------------------------------------------------------------------
sub do_edit_followup : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('edit_outreach_data');
my $request_id = $self->param('id')
|| return $self->error('no request_id passed to '.$self->get_current_runmode);
my $dfv = $self->check_rm('edit_followup', $self->validate('outreach_followup') )
|| return $self->dfv_error_page;
my $data = $dfv->valid; # warn Dumper $data;
$data->{_request_id} = $request_id;
my $rtn = $self->model('Outreach')->update_followup($data);
return $rtn
? $self->error($rtn)
: $self->redirect_after_edit_success('/report/outreach/' . $request_id);
}
# ------------------------------------------------------------------------------
sub do_reports_issued : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('edit_outreach_data');
my $rm = '/local_worklist?function_name=outreach_reports_to_issue';
my $url = $self->query->url() . $rm;
my @request_ids = $self->query->param('request_id');
if (! @request_ids) {
$self->flash( warning => $self->messages('worklist')->{no_request_ids} );
$self->redirect($url);
}
else { # do update; return error if one occured:
my $rtn = $self->model('Outreach')->do_reports_issued(\@request_ids);
return $self->error($rtn) if $rtn;
}
return @request_ids > 1
? $self->redirect_after_edit_successes($rm)
: $self->redirect_after_edit_success($rm);
}
# ------------------------------------------------------------------------------
sub do_pack_dispatch : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('edit_outreach_data');
my @request_ids = $self->query->param('request_id');
unless (@request_ids) {
$self->flash( warning => $self->messages('worklist')->{no_request_ids} );
my $url = join '/', $self->query->url(),
'local_worklist?function_name=outreach_pack_dispatch';
return $self->redirect($url);
}
{ # do update; return error if one occured:
my $rtn = $self->model('Outreach')->do_pack_dispatch(\@request_ids);
return $self->error($rtn) if $rtn;
}
{ # get data for summary:
my $data = $self->model('Request')->requests_summary_data(\@request_ids);
$self->tt_params( requests => $data );
}
return $self->tt_process();
}
# ------------------------------------------------------------------------------
sub do_letter_dispatch : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $notification = $self->param('id'); # warn $notification;
my @request_ids = $self->query->param('request_id');
my %data = (
request_ids => \@request_ids,
notification => $notification,
); # warn Dumper \%data;
{ # do update; return error if one occured:
my $rtn = $self->model('Outreach')->do_letter_dispatch(\%data);
return $self->error($rtn) if $rtn;
}
{ # get data for summary:
my $data = $self->model('Request')->requests_summary_data(\@request_ids);
$self->tt_params( requests => $data );
}
return $self->tt_process();
}
# ------------------------------------------------------------------------------
sub report_labels : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $vars = $self->query->Vars();
my $to_datetime = sub { LIMS::Local::Utils::to_datetime_using_datecalc(@_) };
my $from = &$to_datetime($vars->{date_from});
my $to = &$to_datetime($vars->{date_to});
# require valid 'date_from' & 'date_to' params:
unless ($from && $to) {
$self->flash( error => $self->messages('outreach')->{missing_dates} );
return $self->redirect( $self->query->url .
'/local_worklist?function_name=outreach_report_labels' );
}
my $dates = { begin => $from, end => $to };
my $data = $self->model('Outreach')->get_authorised_cases_data($dates);
$self->tt_params(
requests => $data,
dates => $dates,
);
return $self->tt_process();
}
# ------------------------------------------------------------------------------
sub do_report_labels : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my @patient_ids = $self->query->param('patient_id');
my $labels = $self->model('Outreach')->report_labels(\@patient_ids);
$self->tt_params( addresses => $labels );
return $self->tt_process();
}
# ------------------------------------------------------------------------------
sub data_table : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $param = $self->param('id');
my $patient_id = $self->param('Id');
my $patient = $self->model('Patient')->get_patient($patient_id);
my $data = $self->model('Outreach')->get_chart_results($patient_id, $param);
# convert dates to datetime:
my $dt = sub { LIMS::Local::Utils::to_datetime_using_parsedate(@_) };
$_->{created_at} = &$dt($_->{created_at}) for @$data;
my $lab_param = $self->model('Outreach')->get_lab_param($param);
$self->tt_params(
data => $data,
param => $lab_param,
patient => $patient,
);
return $self->render_view($self->tt_template_name);
}
# ------------------------------------------------------------------------------
sub doubling_time : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $param = $self->param('id');
my $patient_id = $self->param('Id');
my $p = $self->query->Vars();
unless ( $p->{to} && $p->{from} ) { # need both
$self->tt_params( errs => 'require both A and B values' );
return $self->forward('data_table');
}
my $data = $self->model('Outreach')->get_chart_results($patient_id, $param);
# get 'to' and 'from' datasets from form loop count (minus 1 for array pos):
my $from = $data->[$p->{from} - 1]; # warn Dumper $from;
my $to = $data->[$p->{to} - 1]; # warn Dumper $to;
my $dt = sub { LIMS::Local::Utils::to_datetime_using_parsedate(@_) };
my $dt1 = &$dt($from->{created_at});
my $dt2 = &$dt( $to->{created_at});
# Td = (t2 - t1) * log(2) / log(q2 / q1) # doubling time calculation:
my $delta_days = $dt1->delta_days($dt2)->delta_days; # (t2 - t1) days
my ($q1, $q2) = ($from->{result}, $to->{result});
eval { # in case division by zero error
my $t = $delta_days * log(2) / log( $q2 / $q1 ); # warn $t;
my $d = DateTime::Duration->new( years => $t / 365 ); # to allow in_units('months')
$self->tt_params( duration => $d );
};
$self->tt_params( eval_err => $@ ) if $@;
$self->tt_params( q1 => $q1, q2 => $q2, delta_days => $delta_days );
# round duration value callback:
$self->tt_params( round => sub { LIMS::Local::Utils::round_value(@_) } );
return $self->forward('data_table');
}
# ------------------------------------------------------------------------------
sub alternate_address : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('edit_outreach_data');
my $request_id = $self->param('id')
|| return $self->error('no request_id passed to '.$self->get_current_runmode);
my $patient_id = $self->param('Id')
|| return $self->error('no patient_id passed to '.$self->get_current_runmode);
my $q = $self->query;
my $vars = $q->Vars();
# can't use DFV check_rm() here as target is in another controller:
my ($address, $post_code) = @$vars{qw(address post_code)}; # warn Dumper [$address, $post_code];
unless ($address && $post_code) {
$self->flash( error => $self->messages('outreach')->{alternate} );
return $self->redirect( $q->url . '/report/outreach/' . $request_id);
}
$vars->{patient_id} = $patient_id;
my $rtn = $self->model('Outreach')->update_alternate_address($vars);
return $rtn
? $self->error($rtn)
: $self->redirect_after_edit_success('/report/outreach/' . $request_id);
}
# ------------------------------------------------------------------------------
sub _get_datafile_results {
my $self = shift; $self->_debug_path();
my $result = $self->get_datafile_results(); # arrayref
if ( @$result ) {
return $result;
}
else {
$self->flash( error => $self->messages('results')->{empty_result} );
return 0;
}
}
#-------------------------------------------------------------------------------
# check internal datafile refs (filename & patient ids) match
# sets $self->param_mismatch() arrayref on error:
sub _check_datafile_integrity {
my $self = shift; $self->_debug_path();
# check_datafile_integrity() returns names of any errors:
if ( my $error_names = $self->check_datafile_integrity() ) {
my @errs = map $self->messages('results')->{$_}, @$error_names;
$self->flash( error => join '; ', @errs );
return 0;
}
return 1;
}
1;