package LIMS::Controller::Admin::Patient::Merge;
use base 'LIMS::Base';
use Moose;
with (
'LIMS::Controller::Roles::SearchConstraint', # generate_search_constraints()
'LIMS::Controller::Roles::SessionStore',
);
__PACKAGE__->meta->make_immutable(inline_constructor => 0);
use LIMS::Local::Utils;
use LIMS::Local::Search;
use Data::Dumper;
#-------------------------------------------------------------------------------
sub default : StartRunmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $errs = shift; # $self->stash( errs => $errs );
return $self->forbidden() unless $self->user_can('patient_merge');
$self->js_validation_profile('patient_merge');
return $self->tt_process($errs);
}
#-------------------------------------------------------------------------------
sub search : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('patient_merge');
my $dfv = $self->check_rm( 'default', $self->validate('patient_merge') )
|| return $self->dfv_error_page;
my $data = $dfv->valid; # $self->debug($data);
unless ( $self->get_current_runmode eq 'do_merge' ) { # ie re-direct
# store params in session to save multiple hidden fields:
my $vars = $self->query->Vars(); # need scalar context to get hashref
$self->session_store_form_data($vars);
}
# protect against submission of empty form:
my $search_constraints = $self->generate_search_constraints($data)
|| return $self->forward('default'); # $self->debug($search_constraints);
my %args = ( search_constraints => $search_constraints );
my $records = $self->model('Request')->find_requests(\%args);
$self->tt_params( records => $records );
my $count = scalar @$records; # warn $count;
if ( $count > 50 ) {
my $msg = sprintf $self->messages('patient')->{merge}->{too_many}, $count;
$self->flash( warning => $msg );
# return $self->forward('default'); # probably not - would prevent merge
# on patient with high number of follow-ups - so just set flash msg
}
if ($count) {
# get arrayref of all the request_ids:
my @request_ids = map $_->id, @$records; # $self->debug(\@request_ids);
{ # get report & diagnosis info for each request id:
my $report_map = $self->_get_report_data(\@request_ids);
$self->tt_params( report_map => $report_map );
}
{ # get list of unique patients sorted according to sort_by input:
my $patients = $self->_sort_patients($records);
$self->tt_params( patients => $patients );
}
}
else {
# just set flash warning:
$self->flash( info => $self->messages('search')->{no_records_found} );
}
return $self->tt_process('admin/patient/merge/default.tt');
}
#-------------------------------------------------------------------------------
sub do_merge : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
return $self->forbidden() unless $self->user_can('patient_merge');
my $params = $self->query->Vars(); # $self->debug($params);
{ # need to put original search params back into query for forward to search():
my $search_params = $self->session->param('_patient_merge_data');
while ( my ($param, $val) = each %$search_params ) {
$self->query->param($param, $val);
}
}
# initialise data structure to hold 'to' & 'from' data:
my $data = {
from => [], # can be array of 1 or more patient_case_id's
to => [], # must ONLY have array size of 1
};
PARAM: # sort form params into %$data hash:
while ( my($patient_case_id, $direction_of_change) = each %$params ) {
# skip irrelevant form params:
next PARAM unless grep $direction_of_change eq $_, qw(from to);
my $list = $data->{$direction_of_change}; # ie 'to' or 'from' key
push @$list, $patient_case_id;
} # $self->debug($data);
# check we have AT LEAST one 'from' & ONLY one 'to':
unless ( scalar (@{ $data->{from} }) >= 1 && scalar (@{ $data->{to} }) == 1 ) {
$self->flash( warning => $self->messages('patient')->{merge}->{not_flagged} );
return $self->search(); # don't use forward() here - need to keep current rm
}
# require confirmation:
unless ( $self->query->param('confirm_merge') ) {
return $self->_generate_merge_confirmation($data);
}
# add scope param to $data:
$data->{scope} = $self->query->param('scope');
my $rtn = $self->model('Patient')->merge_patients($data);
if ($rtn) {
return $self->error($rtn);
}
else {
$self->flash( info => $self->messages('patient')->{merge}->{merge_success} );
return $self->search(); # don't use forward() here - need to keep current rm
}
}
#-------------------------------------------------------------------------------
sub _generate_merge_confirmation {
my $self = shift; $self->_debug_path();
my $data = shift;
{ # from:
my @ids = @{ $data->{from} };
my $cases = $self->model('PatientCase')->get_patient_cases(\@ids);
$self->tt_params( merge_from => $cases );
}
{ # to:
my ($id) = @{ $data->{to} };
my $case = $self->model('PatientCase')->get_patient_case($id);
$self->tt_params( merge_to => $case );
}
return $self->tt_process('admin/patient/merge/confirm.tt');
}
#-------------------------------------------------------------------------------
sub _get_report_data {
my ($self, $request_ids) = @_; $self->_debug_path(); # arrayref
my $reports
= $self->model('Request')->get_request_report_diagnosis($request_ids);
my $today = DateTime->now->ymd('-');
my %report_data; # report data for each $request_id
for my $report (@$reports) { # warn Dumper $report->created_at;
my $data = $report->as_tree(deflate => 0);
{ # calculate age of report in delta business day & add to $data:
my $report_date = $report->created_at->ymd('-') # deflate for delta_business_days()
|| $today; # if not reported, so delta days = 0
$data->{age_of_report}
= LIMS::Local::Utils::delta_business_days($report_date, $today);
} # warn Dumper $data;
$report_data{$report->id} = $data;
} # warn Dumper \%report_map;
return \%report_data;
}
#-------------------------------------------------------------------------------
# get list of unique patients sorted according to sort_by input:
# possible sorts: id, name, dob, unit_no, nhs_no, count:
sub _sort_patients {
my ($self, $records) = @_; $self->_debug_path();
# extract unique patients from $records arrayref:
my %unique_patients; # as hash to eliminate duplicates
for (@$records) {
my $patient_case = $_->patient_case;
my $id = $patient_case->patient_id;
$unique_patients{$id}{count}++; # auto increment
# only need patient_case once:
next if $unique_patients{$id}{patient_case};
# convert patient_case object to hashref for clarity downstream:
$unique_patients{$id}{patient_case} = $patient_case->as_tree(deflate => 0);
}; # warn Dumper \%patient_map;
# sort order supplied by user, or default to patient_id:
my $sort_by = $self->query->param('sort_by') || 'patient_id';
# dispatch table of sort methods:
my %methods = (
dob => \&_sort_by_dob,
name => \&_sort_by_name,
count => \&_sort_by_count,
nhs_number => \&_sort_by_nhs_number,
patient_id => \&_sort_by_patient_id,
unit_number => \&_sort_by_unit_number,
);
# get required sort_method:
my $sort_method = $methods{$sort_by};
# pass \%unique_patients to the required sort method & get sorted list of
# patient.id's back:
my $patient_ids = $sort_method->(\%unique_patients);
# create an array of patient data hashrefs, sorted according to @ids:
my @sorted_patients = map $unique_patients{$_}, @$patient_ids;
return \@sorted_patients;
}
sub _sort_by_dob {
my $unique_patients = shift;
my $NULL = DateTime->now; # need a datetime object for null dob's
my @ids = sort {
my $A = $unique_patients->{$a}{patient_case}{patient}{dob} || $NULL;
my $B = $unique_patients->{$b}{patient_case}{patient}{dob} || $NULL;
DateTime->compare( $A, $B );
} keys %$unique_patients;
return \@ids;
}
sub _sort_by_name {
my $unique_patients = shift;
my @ids = sort {
my $A_LN = $unique_patients->{$a}{patient_case}{patient}{last_name};
my $B_LN = $unique_patients->{$b}{patient_case}{patient}{last_name};
my $A_FN = $unique_patients->{$a}{patient_case}{patient}{first_name};
my $B_FN = $unique_patients->{$b}{patient_case}{patient}{first_name};
$A_LN cmp $B_LN || $A_FN cmp $B_FN;
} keys %$unique_patients;
return \@ids;
}
sub _sort_by_unit_number {
my $unique_patients = shift;
my @ids = sort {
$unique_patients->{$a}->{patient_case}{unit_number}
cmp
$unique_patients->{$b}->{patient_case}{unit_number}
} keys %$unique_patients;
return \@ids;
}
sub _sort_by_nhs_number {
my $unique_patients = shift;
my @ids = sort {
my $A = $unique_patients->{$a}{patient_case}{patient}{nhs_number} || 0;
my $B = $unique_patients->{$b}{patient_case}{patient}{nhs_number} || 0;
$A <=> $B;
} keys %$unique_patients;
return \@ids;
}
sub _sort_by_count {
my $unique_patients = shift;
my @ids = sort {
$unique_patients->{$a}{count} <=> $unique_patients->{$b}{count};
} keys %$unique_patients;
return \@ids;
}
sub _sort_by_patient_id { # default if nothing supplied
my $unique_patients = shift;
return [ sort { $a <=> $b } keys %$unique_patients ];
}
1;