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 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 $records = $self->_get_records_for_merge($search_constraints);
$self->tt_params( records => $records ); # warn Dumper $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) { # warn Dumper $_->as_tree for @$records;
# 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');
}
#-------------------------------------------------------------------------------
# returns array(ref) of hashrefs of flattened RDBO::Manager objects
sub _get_records_for_merge {
my ($self, $search_constraints) = @_;
# RDBO::Manager object containing patient_case arrays of request arrays:
my $o = $self->model('Patient')->get_records_for_merge($search_constraints);
my $cloner = sub { LIMS::Local::Utils::clone(@_) };
my $delta = sub { LIMS::Local::Utils::delta_business_days(@_) };
my $today = LIMS::Local::Utils::today->ymd('-');
# get array of hashes (patient data, location, unit_no, labno, diagnosis, reported)
my @records;
REF: for my $ref (@$o) { # warn Dumper $ref; # ref contains arrays of arrays
my %h = (); # reset
$h{patient}{$_} = $ref->$_ for
qw(id last_name first_name middle_name nhs_number gender dob); # warn Dumper \%h;
# get patient_case (an array of PatientCase objects):
my $patient_case = $ref->patient_case; # warn Dumper $patient_case; # AoH
CASE: for my $case (@$patient_case) { # warn ref $case; # hashref
my $c = &$cloner(\%h); # $self->debug($c); # hashref
$c->{patient}{patient_case} = $case->as_tree;
# get request data or add patient data:
my $request = $case->{request} || []; # must exists as arrayref
if ( not @$request ) { push @records, $c; next CASE }
for my $req (@$request) { # warn Dumper $req;
my $d = &$cloner($c); # $self->debug($d); # hashref
$d->{request} = $req;
{ # calculate age of report in delta business days:
my $report_date = $req->created_at->ymd('-') || $today; # deflate
$d->{request}{delta_report} = &$delta($report_date, $today);
} # warn Dumper $d;
push @records, $d;
}
}
} # warn Dumper \@records;
return \@records;
}
#-------------------------------------------------------------------------------
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');
}
#-------------------------------------------------------------------------------
# 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) { # hashref of patient, location and request hashrefs:
my $patient = $_->{patient}; # warn Dumper $patient;
my $patient_id = $patient->{id};
$unique_patients{$patient_id}{count}++; # auto increment
# convert patient_case object to hashref for clarity downstream:
$unique_patients{$patient_id}{patient} ||= $patient;
}; # warn Dumper \%unique_patients;
# sort order supplied by user, or default to patient_id:
my $sort_by = $self->query->param('sort_by') || 'patient_id'; # warn $sort_by;
# 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}{dob} || $NULL;
my $B = $unique_patients->{$b}{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}{last_name};
my $B_LN = $unique_patients->{$b}{patient}{last_name};
my $A_FN = $unique_patients->{$a}{patient}{first_name};
my $B_FN = $unique_patients->{$b}{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}{unit_number}
cmp
$unique_patients->{$b}{patient}{unit_number}
} keys %$unique_patients;
return \@ids;
}
sub _sort_by_nhs_number {
my $unique_patients = shift; # warn Dumper $unique_patients;
my @ids = sort {
my $A = $unique_patients->{$a}{patient}{nhs_number} || 0;
my $B = $unique_patients->{$b}{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;