RSS Git Download  Clone
Raw Blame History
#!/usr/bin/perl

=begin -------------------------------------------------------------------------
Compiles & emails authorised (or amended) reports as PDF attachments for
locations specified in email_contacts table, where 'type' col is set to 'report'

Required entries must be in email_contacts table (doesn't use lib/contacts.lib
as most of addresses are the same as for mdt contact), with 'type' col set to
'report'. May require entries in %departments & @require_new in config section
below.

Scope = organisation, hospital or department. If set to 'organisation', records
are retrieved using parent_organisation_id; if scope set to 'department', expects
entry in %departments map to provide hospital_department.display_name to filter
on; if set to hospital, uses the referral_source_id to retrieve records

Skip reports configured in skip_email_reports.yml

Uses LIMS::Controller::Roles::format_report() to generate PDF reports, via
LIMS::Local::Reports to provide methods - render_view(), model(), etc - required
by LIMS methods

Uses 'file' as session store (set in L::Local::LIMS), /chart/ url calls LIMS so
it provides USE_FILE_SESSIONS query param flag to prevent new db session created
=cut ---------------------------------------------------------------------------

use strict;
use warnings;

my $JUST_TESTING = 0; # emails just raj, dumps history data to lims_test db

################################################################################
my %departments = ( # entry MUST match display_name in email_contacts table
    # email_contacts.display_name => hospital_departments.display_name
    Airedale  => 'histopathology',
    Blackburn => 'histopathology',
    Preston   => 'histopathology',
    Salford   => 'histopathology',
    Wigan     => 'histopathology',
);

# recipients requiring only 'new' diagnoses - otherwise defaults to 'all':
my @require_new = ( # entry MUST match display_name in email_contacts table
    'Calderdale (CWT)',  # Cancer Waiting Times
    'Scarborough (CWT)', # Cancer Waiting Times
);

my $delta = 1; # days ago (1 for production)
################################################################################

BEGIN {
    use FindBin qw($Bin); # warn $Bin; exit;
    use lib (
        "$Bin/../../../lib",
        '/home/raj/perl5/lib/perl5',
    );
    # override default db test:
    $ENV{ROSEDB_DEVINIT} = "$Bin/../../../config/rosedb_devinit_prod.pl";
}

use IO::All;
use File::Slurp;
use Config::Auto;
use Data::Dumper;
use DateTime::Format::MySQL;

use LIMS::Local::Reports;
use LIMS::Local::ScriptHelpers;

my $cfg_file = "$Bin/../lib/settings.cfg";
my $settings = Config::Auto::parse($cfg_file); # warn Dumper $settings;
    
# get tools from LIMS::Local::ScriptHelpers:
my $tools = LIMS::Local::ScriptHelpers->new();
$tools->test_only($JUST_TESTING);

my $sql_lib = $tools->sql_lib();
my $config  = $tools->config();
my $dbix    = $tools->dbix();

my $date = $tools->date_subtract( days => $delta )->ymd; # warn $date;
# substitutes LIMS methods required by Role::RecordHandler :
my $lims = LIMS::Local::Reports->new({tools => $tools});

{ # application http address - for charts:
    my $http_addr = $config->{http_addr};
    $lims->tt_params( app_url => $http_addr );
}

#-------------------------------------------------------------------------------
# common vars for email message:
my %mail = (		
	config  => $config,
);

# universally required constraints:
my @common_constraints = (
    q!DATE(rr.updated_at) = ?!, 
    q!so.description IN ('authorised', 'complete')!,
);

# get contents of email_contacts table:
my $email_contacts = get_active_email_contacts(); # AoH
   
# get referral_source_id map of locations requiring only 'new' reports:
my $ids_requiring_new = get_locations_requiring_new($email_contacts); # hashref

# get referral_source_id map of locations requiring departmental reports:
my $department_location_ids = get_department_locations($email_contacts); # hashref

# load exemptions:
my $skip_reports = $tools->get_yaml_file('skip_email_reports'); # hashref or 0
#-------------------------------------------------------------------------------

# go:
{
    # get base query, appended with restriction depending on scope (dept, hospital, organisation) 
    # my $base_sql = join ' ', <DATA>; warn $base_sql; exit;
    my $base_sql = $sql_lib->retr('mail_reports_request_ids');
	
    # get list of request_ids to send to compile_reports():
    ENTRY: # loop through each contact and get request_id's as required:
    for my $recipient (@$email_contacts) { # warn Dumper $recipient;
        # next ENTRY unless $recipient->{is_active} eq 'yes'; in query now

        my $ref_src_id = $recipient->{referral_source_id};
        my $entry_id   = $recipient->{id}; # email_contacts.id
        my $scope      = $recipient->{scope}; # dept, hospital or organisation

        # using GROUP_CONCAT on contact_address to avoid repeat data retrieval:
        my @contacts = split ',', $recipient->{contacts}; # maybe > 1
        
        # copy of $base_sql initial state (gets modified inside blocks):
        my $local_sql = $base_sql;		
        
		# re-initialise arrays:
        my @local_constraints = (); # clear
        my @bind = ($date); # $date universally required

        # reports can be for department, hospital or organisation:
        if ( $scope eq 'department' ) {            
            # add restrictions:
            push @local_constraints, ('rs.id = ?', 'hd.display_name = ?');
            
            my $department = $department_location_ids->{$entry_id} # warn $department;
            || die "cannot find data for email_contacts.id = $entry_id";            
            push @bind, ($ref_src_id, $department);
        }
        elsif ( $scope eq 'organisation' ) {
            # add restrictions:
            push @local_constraints, q!rd.parent_organisation_id = ?!;
            
            # find parent_organisation of ref_src_id:
            my $sql = q!select parent_organisation_id from referral_sources
                where id = ?!;
            $dbix->query( $sql, $ref_src_id )->into( my $parent_org_id );            
            push @bind, $parent_org_id;            
        }
        elsif ( $scope eq 'hospital' ) {
            # add restrictions:
            push @local_constraints, q!rs.id = ?!;            
            push @bind, $ref_src_id;
        }
        else { die "unknown scope: $scope" }
        
        # restrict to new reports if required:
        if ( $ids_requiring_new->{$entry_id} ) {
            push @local_constraints, q!rr.status = ?!;
			push @bind, 'new';
        }        
        
        if ($skip_reports) { # skip reports:
            { # initial screens:
                my $ary = $skip_reports->{presentation}; # arrayref
                my $str = join ',', map $dbix->dbh->quote($_), @$ary;
                push @local_constraints, qq!s.description NOT IN ($str)!;
            }
            { # clinical trials:
                my $ary = $skip_reports->{clinical_trial}; # arrayref
                my $str = join ',', map $dbix->dbh->quote($_), @$ary;
                push @local_constraints, # need 1st part or query only returns trial cases:
					qq!( ct.trial_name IS NULL OR ct.trial_name NOT IN ($str) )!; 
            }
        }
        
        $local_sql .= join ' AND ', (@common_constraints, @local_constraints);
			# warn Dumper [$local_sql, \@bind]; next ENTRY;
        my $request_ids = $dbix->query($local_sql, @bind)->flat;
        
        compile_reports( $request_ids, \@contacts ) if @$request_ids;
    }
    # compile_reports([148879], ['ra.jones@nhs.net']); # has chart
    # compile_reports([155975], ['ra.jones@nhs.net']); # has clinical trial
    # compile_reports([175234], ['ra.jones@nhs.net']); # is anonymised patient
}

# IO::All method supsended pending bugfix - https://rt.cpan.org/Public/Bug/Display.html?id=41819
{ # tidy up cgisess files in /tmp from LIMS::Local::Reports session()
    # my @files = io('/tmp')->all_files; # get all files in /tmp
    # map $_->unlink, grep $_->filename =~ /\Acgisess_/, @files;
    my @files = File::Slurp::read_dir('/tmp'); # warn Dumper \@files;
	map { io('/tmp/'.$_)->unlink } grep /\Acgisess_/, @files;
}

sub compile_reports {
    my ($request_ids, $contacts) = @_; # arrayref, arrayref
    
    for my $request_id (@$request_ids) { # warn $request_id;
        { # generate pdf attachment:
			my $pdf = $lims->format_report($request_id); # L::C::Roles::RecordHandler
            # $pdf > io("reports/$request_id.pdf");
			$mail{attachment} = $pdf;
		}
		# retrieve request data object stashed in Recordhandler::_format_report():
		my $data = $lims->stash->{request_data}; 
        
		{ # add subject line:
			my $referrer_department = $data->referrer_department;
			
			my $subject = sprintf 'HMDS Report for %s / %s', 
				$referrer_department->hospital_department->display_name, # speciality
				$referrer_department->referrer->name; # referrer
			$mail{subject} = $subject;
		}
		{ # add filename:
			my $filename = sprintf '%02d_%05d.pdf',
				$data->year - 2000,
				$data->request_number;
			$mail{filename} = $filename;
		} # warn Dumper \%mail;
        # send report to recipient(s):
        for my $recipient (@$contacts) {
            $mail{recipient} = $recipient;
            dispatch_report({ request_id => $request_id, message => \%mail });
        }
    }
}

sub dispatch_report {
    my $args = shift;

    my $request_id = $args->{request_id};
    my $message    = $args->{message};
    
    # substitute recipient address here if $JUST_TESTING (L::L::Mail doesn't any more):
    if ($JUST_TESTING) {
        my $safe_address = $config->{email_from};
        die "unsafe recipient address $safe_address"
            unless $safe_address =~ /\@nhs.net\Z/;
        $message->{recipient} = $safe_address;
    }
    
    my $result = LIMS::Model::Email->send_message($message); # Return::Value
	
	if ( $result->type eq 'success' ) {
		printf "%s reports %s to %s\n",
			$tools->script_filename, lc $result->string, $message->{recipient};
        
        my %data = (
            request_id => $request_id,
            recipient  => $message->{recipient},
        );

        # log details in request_dispatch_log & request_history tables:
        unless ($JUST_TESTING) {
            my $rtn = $lims->model('Request')->update_request_dispatch_log(\%data);
            warn "Error in $0: $rtn" if $rtn;
        }
        else { # manual input into lims_test db:
            $dbix->insert('lims_test.request_dispatch_log', \%data);
            
            my $service_user = $tools->get_server_user_details();
            $data{user_id} = $service_user->{id};

            $data{action} = 'dispatched report to ' . $data{recipient};
            delete $data{recipient};
            $dbix->insert('lims_test.request_history', \%data);
        }    
    }
	else {
		warn $result->string;
        $tools->mail_admin({ script => $0, msg => $result->string });
	}
	
=begin
	if ($rtn->{success}) {
        my $filename = $tools->script_filename;
		print "$filename reports " . lc $rtn->{message} . ' to '
            . $message->{recipient} . "\n";
        
        my %data = (
            request_id => $request_id,
            recipient  => $message->{recipient},
        );

        # log details in request_dispatch_log & request_history tables:
        unless ($JUST_TESTING) {
            my $rtn = $lims->model('Request')->update_request_dispatch_log(\%data);
            warn "Error in $0: $rtn" if $rtn;
        }
        else { # manual input into lims_test db:
            $dbix->insert('lims_test.request_dispatch_log', \%data);
            
            my $service_user = $tools->get_server_user_details();
            $data{user_id} = $service_user->{id};

            $data{action} = 'dispatched report to ' . $data{recipient};
            delete $data{recipient};
            $dbix->insert('lims_test.request_history', \%data);
        }    
    }
	else {
		warn $rtn->{message};
        $tools->mail_admin({ script => $0, msg => $rtn->{message} });
	}
=cut
}

sub get_active_email_contacts { # get array of hashrefs of location/scope => email:
# query uses GROUP_CONCAT on contact_address to avoid repeat data retrieval for
# same location/scope if multiple contacts configured:
    my $sql = $sql_lib->retr('get_email_contacts');
    my $email_contacts = $dbix->query($sql, 'report')->hashes; # warn Dumper $email_contacts;
    return $email_contacts;
}

sub get_department_locations { # locations requiring departmental reports
    my $email_contacts = shift;
   
    my @locations = keys %departments;
    
    my %map;
    CONTACT: for my $entry (@$email_contacts) {
        next CONTACT unless $entry->{scope} eq 'department'
            && ( grep $entry->{display_name} eq $_, @locations );
        my $location    = $entry->{display_name};
        my $location_id = $entry->{id};
        my $department  = $departments{$location};
        
        $map{$location_id} = $department;
    }
    
    return \%map;
}

sub get_locations_requiring_new { # require 'new' reports only
    my $email_contacts = shift;
    
    my @ids;
    CONTACT: for my $location (@$email_contacts) {
        next CONTACT unless grep $location->{display_name} eq $_, @require_new;
        push @ids, $location->{id};
    }
    
    my %map = map { $_ => 1 } @ids;
    return \%map;
}

__DATA__
SELECT r.id
FROM requests r 
    JOIN patient_case pc on (r.patient_case_id = pc.id)
	JOIN referrer_department rd on (r.referrer_department_id = rd.id)
	JOIN hospital_departments hd on (rd.hospital_department_code = hd.id)
    JOIN referral_sources rs on (pc.referral_source_id = rs.id)
    JOIN request_initial_screen ris on (ris.request_id = r.id)
    JOIN screens s on (ris.screen_id = s.id)
    JOIN request_report_view rr ON (r.id = rr.request_id)  
    JOIN status_options so ON (r.status_option_id = so.id)
    LEFT JOIN ( request_trial rt JOIN clinical_trials ct on (rt.trial_id = ct.id) )
        on (rt.request_id = r.id )
WHERE /* constraints added in script */