RSS Git Download  Clone
Raw Blame History
package LIMS::Model::Request;

use Moose;
extends 'LIMS::Model::Base';
with (
    'LIMS::Model::Roles::QueryFormatter',
	'LIMS::Model::Roles::RequestUpdate',
	'LIMS::Model::Roles::ScreenUpdate', # do_new_lab_test
);
use namespace::clean -except => 'meta';

has frozen_data => ( is => 'rw', isa => 'HashRef');
has archive => ( 
    is      => 'ro',
    isa     => 'ArrayRef[HashRef]',
    default => sub { [] },
    lazy    => 1,
	traits  => ['Array'],
	handles => {
		add_to_archive => 'push',
		archived_data  => 'elements',
	},
);

has $_ => ( is => 'ro', isa => 'HashRef', lazy_build => 1 )
	for qw(	referral_type_map specimen_lab_test_map request_status_options_map );

__PACKAGE__->meta->make_immutable;

use Data::Dumper;
use LIMS::Local::Utils;

#-------------------------------------------------------------------------------
sub new_request {
	my $self = shift;
	my $data = shift; # DEBUG $data; return;

    my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
	
	# extract request, options & consent data from $data:
	my $request_data = $self->_get_request_data($data); 
	my $trial_data   = $self->_get_clinical_trial_data($data);

	# get specimens table iterator:
	my $specimen = LIMS::DB::Specimen::Manager
		->get_specimens_iterator(sort_by => 'sample_code');

	# get specimen -> lab_test map (for any auto-generated lab tests): 
	my $specimen_lab_test_map = $self->specimen_lab_test_map;

	# get consent_options iterator:
	my $consent_options = LIMS::DB::ConsentOption::Manager
		->get_consent_options_iterator();
	
	# get additional_options table iterator:
	my $request_options = LIMS::DB::AdditionalOption::Manager
		->get_additional_options_iterator();

	# split specimens on comma and/or space(s):
    my @specimens = split /\,\s?|\s+/, $data->{specimen}; # DEBUG \@specimens;

	my $user_id = $self->user_profile->{id};
	
	# create new request in transaction:
	my $new_request = sub {
		# save request_data to request table 1st so we can get request.id:
        my $request = LIMS::DB::Request->new(%$request_data);
        $request->save;

		# cycle specimens iterator, adding specimen_id to request_specimen table:
		while ( my $o = $specimen->next ) {
			next unless grep { lc $o->sample_code eq lc $_ } @specimens; # DEBUG $o->id;

			LIMS::DB::RequestSpecimen->new(
				request_id  => $request->id,
				specimen_id => $o->id,
			)->save;
			
			# any auto-generated specimen-associated lab-tests ?
			if ( my $lab_test_ref = $specimen_lab_test_map->{$o->id} ) { 
				map { # generate lab-test request(s):
					$self->do_new_lab_test({ 
						_request_id  => $request->id, # method required underscored var
						lab_test_id => $_,
					});
				} @$lab_test_ref;
			}
		}

		# clinical trial:
		if ( my $trial_id = $trial_data->{trial_id} ) {
			LIMS::DB::RequestTrial->new(
				trial_id   => $trial_id,
				request_id => $request->id,
			)->save;
			# trial_number - should not be submitted without trial_id:
			if ( my $trial_number = $trial_data->{trial_number} ) {
				# update existing or create new patient trial data:
				LIMS::DB::PatientTrial->new(
					patient_id   => $trial_data->{patient_id},
					trial_number => $trial_number,
					trial_id     => $trial_id,
				)->insert_or_update;
			}
		}

		# consent data:
		while ( my $o = $consent_options->next ) {
			my $option_name = $o->consent_name;
			next unless $data->{$option_name};
			
			LIMS::DB::RequestConsent->new(
				status     => $data->{$option_name},
				request_id => $request->id,
				consent_id => $o->id,
			)->save;
		}
		
		# error codes:
		if ( my $error_code_id = $data->{error_code_id} ) {				
			my %data = (
				request_id => $request->id,
				user_id    => $user_id,
			);
			
			# get error_code_ids as an arrayref (if not already):
			my $codes = ref $error_code_id eq 'ARRAY'
				? $error_code_id : [ $error_code_id ];
				
			for (@$codes) {
				$data{error_code_id} = $_;
				LIMS::DB::RequestErrorCode->new(%data)->save;	
			}
		}
		
		# cycle request_options iterator, adding additional_option_id to request_options table:
		while ( my $o = $request_options->next ) {
			my $option_name = $o->option_name;
			next unless $data->{$option_name};

			LIMS::DB::RequestOption->new(
				request_id => $request->id,
				option_id  => $o->id,
			)->save;
		}

		# external_reference:
		if ( my $ref = $data->{external_reference} ) {
			LIMS::DB::RequestExternalRef->new(
				request_id  => $request->id,
				external_reference => $ref,
			)->save;			
		}
		
		# request_history:
		LIMS::DB::RequestHistory->new(
            request_id  => $request->id,
            user_id     => $user_id,
            action      => 'registered',
        )->save;
    };

    # do_transaction() returns true if succeeds; sets $db->error on failure:
    my $ok = $db->do_transaction($new_request);

    return $ok ? 0 : 'new_request() error - ' . $db->error;
}

#-------------------------------------------------------------------------------
sub get_requests_count {
	my $self = shift;

	my ($request_number, $year) = @_;

	my %args = (
		query => [
	        request_number => $request_number,
            year => $year || DateTime->now->year,
		],
	);

	my $total = LIMS::DB::Request::Manager->get_requests_count(%args);

    return $total;
}

#-------------------------------------------------------------------------------
sub get_previous_diagnosis_count {
    my ($self, $request_id) = @_;
	
	my %args = (
		query => [ request_id => $request_id ],
	);
	
	my $count = LIMS::DB::RequestDiagnosisHistory::Manager
		->get_request_diagnosis_histories_count(%args);
		
	return $count;
}

#-------------------------------------------------------------------------------
# gets request.id if $n == 1:
sub get_request_id { 
    my ($self, $search_constraints) = @_;
	
	my $formatted_args = $self->get_args_for_request_id($search_constraints);

    my $request = LIMS::DB::Request::Manager->get_requests(%$formatted_args);

    return $request->[0]->id;
}

#-------------------------------------------------------------------------------
# called by Search::do_search() to find number of requests matching search criteria:
sub search_requests_count {
    my ($self, $search_constraints) = @_; # warn Dumper $search_constraints;

	my $formatted_args
		= $self->get_args_for_requests_count($search_constraints); # warn Dumper $formatted_args;
		
#$self->set_rose_debug(1);	
    my $total = LIMS::DB::Request::Manager->get_requests_count(%$formatted_args);
#$self->set_rose_debug(0);	
    return $total;
}

#-------------------------------------------------------------------------------
# called by Search::do_search() to retrieve records matching search criteria for n > 1:
sub find_requests {
	my $self = shift;
	my $args = shift; # hashref with keys = 'search_constraints' & 'args_for_search'

	# get query, with_objects, require_objects, etc from $args:
	my $formatted_args = $self->get_args_for_find_requests($args);
	
#$self->set_rose_debug(1);
    my $requests = LIMS::DB::Request::Manager->get_requests(%$formatted_args);
#$self->set_rose_debug(0);
    return $requests;
}

#-------------------------------------------------------------------------------
# accepts request.id & returns all required request data:
sub get_single_request {
	my ($self, $request_id) = @_; # warn $request_id;
	
	# check it exists 1st (in case user input of request_id into url):
	$self->_verify_request_exists($request_id) || return 0;
	
	# require data from these tables:
	my @tables = qw(
		patients
		referrers
		diagnoses
		status_options
		clinical_trials
		referral_sources
		request_external_ref
		hospital_departments
		request_general_notes
		request_initial_screen
		request_gross_description
	);
		
	my $relationships = $self->get_relationships(\@tables);
	
#$self->set_rose_debug(1);
    my $request
		= LIMS::DB::Request->new(id => $request_id)->load(with => $relationships);
#$self->set_rose_debug(0);

    return $request;
}

#-------------------------------------------------------------------------------
# finds previous requests on current request nhs_number or patient id:
sub get_previous_requests {
	my $self = shift;
	my $args = shift; # hashref of args for query (eg nhs_number => 123, etc)
	
	# require data from these tables:
	my @tables = qw( patients diagnoses );
	
	my $relationships = $self->get_relationships(\@tables);
	
	my %params = (
		query => [ %$args ],
		require_objects => $relationships,
		sort_by => 'year DESC, request_number DESC',
		nested_joins => 0,
	);
	
	my $records = LIMS::DB::Request::Manager->get_requests_iterator(%params);
	
	return $records;
}

#-------------------------------------------------------------------------------
sub get_request_options {
	my ($self, $request_id) = @_; # warn $request_id;
	
	my %args = (
		query => [ request_id => $request_id ],
		require_objects => 'option',
	);

	my $request_options
		= LIMS::DB::RequestOption::Manager->get_request_options(%args);

	return $request_options;
}

#-------------------------------------------------------------------------------
sub get_request_report_diagnosis {
	my ($self, $request_id) = @_; # warn $request_id;

	my %args = (
		query => [ request_id => $request_id ],
		require_objects => 'request_report.diagnosis',
	);
	
	my $request_diagnosis =
		ref $request_id eq 'ARRAY' # can handle either single or list
			? LIMS::DB::Request::Manager->get_requests(%args)
			: LIMS::DB::Request->new(request_id => $request_id)
				->load(with => 'request_report.diagnosis');
			
	return $request_diagnosis;
}

#-------------------------------------------------------------------------------
sub get_request_consent {
	my ($self, $request_id) = @_; # warn $request_id;
	
	my %args = (
		query => [ request_id => $request_id ],
		require_objects => 'consent',
	);

	my $request_consent
		= LIMS::DB::RequestConsent::Manager->get_request_consents(%args);

	return $request_consent;
}

#-------------------------------------------------------------------------------
sub get_request_errors {
	my ($self, $request_id) = @_; # warn $request_id;
	
	my %args = (
		query => [ request_id => $request_id ],
		require_objects => [ 'error_code', 'user' ],
	);

	my $request_errors
		= LIMS::DB::RequestErrorCode::Manager->get_request_error_codes(%args);

	return $request_errors;	
}

#-------------------------------------------------------------------------------
sub request_error_code {
	my $self = shift;
	my $data = shift; # warn Dumper $data;
	
	my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
	
	my %args = ( _request_id => $data->{request_id} ); # do_request_error_code() format

	my $error = sub {
		if ($data->{LIC}) {
			$self->add_to_actions('completed LIC');
		}
		if ( my $error_code_id = $data->{error_code_id} ) {
			$args{error_code_id} = $error_code_id;
			$self->do_request_error_code(\%args);
		}
		# log history:
		$self->do_history_log(\%args);
	};

	my $result = $db->do_transaction( $error );

	# don't need return value unless error:
    return $result ? 0 : 'error in new_request_error() - ' . $db->error;	
}

#-------------------------------------------------------------------------------
# basic version of get_single_request() - just gets request & patient data:
sub get_patient_and_request_data {
	my ($self, $request_id) = @_; # warn $request_id;
	
	my $data = LIMS::DB::Request->new(id => $request_id)
			->load(with => 'patient_case.patient');
	
	return $data;
}

#-------------------------------------------------------------------------------
sub get_section_notes {
	my ($self, $request_id) = @_;
	
	my %args = (
		query => [ request_id => $request_id ],
	);
	
	my $section_notes = LIMS::DB::RequestLabSectionNote::Manager
		->get_request_lab_section_notes(%args);
		
	return $section_notes;
}

#-------------------------------------------------------------------------------
sub get_laboratory_number {
	my ($self, $request_id) = @_;
	
	my $request = LIMS::DB::Request->new(id => $request_id)->load;
	
	my $lab_number = join '/',
		$request->request_number,
		sprintf '%02d', $request->year - 2000;
		
	return $lab_number;
}

#-------------------------------------------------------------------------------
sub get_request {
	my ($self, $request_id) = @_;
	
	my $request = LIMS::DB::Request->new(id => $request_id)->load;
	
	return $request;	
}

#-------------------------------------------------------------------------------
sub get_request_dispatch_logs {
	my ($self, $request_id) = @_;
	
	my $logs = LIMS::DB::RequestDispatchLog::Manager->get_request_dispatch_logs(
		query => [ request_id => $request_id ]
	);
	
	return $logs;	
}

#-------------------------------------------------------------------------------
# how many requests 'have' this patient_case.id ?
sub get_patient_case_requests_count {
    my $self    = shift;
    my $case_id = shift;

    my $requests_count = LIMS::DB::Request::Manager->get_requests_count(
		query => [ patient_case_id => $case_id ],
	);

    return $requests_count;
}

#-------------------------------------------------------------------------------
sub get_hmrn_new_diagnoses {
	my $self = shift;
	my $args = shift;
	
    # get HMRN parent organisation ids:
    my $o = LIMS::DB::LocalNetworkLocation::Manager->get_local_network_locations;
    my @parent_organisation_ids = map { $_->parent_id } @$o;	
	
	# get referral_type.id for practices (saves multi_many_ok flag in query):
	my $ref_type = LIMS::DB::ReferralType->new(description => 'practice')->load;

	my @query = (
		'request_history.action' => 'authorised',
		or => [ # any network hospital or GP practice:
			'parent_organisations.id' => \@parent_organisation_ids,
			'parent_organisations.referral_type_id' => $ref_type->id, # GP's
		],		
		or => [ # ICDO3 or MGUS diagnoses:
			'request_report.diagnosis.icdo3' => { like => '%3' },
			'request_report.diagnosis.icdo3' => '9765/1', # MGUS
		],
	);
	
	{ # calculate requested duration (previous_week, days, date_range, etc):
		my $constraints = $self->_get_hmrn_new_diagnoses_constraints($args);
		push @query, @$constraints;
	}
	
	my @tables = qw( patients diagnoses parent_organisations request_history
		referrers );	
	my $relationships = $self->get_relationships(\@tables);

	my @params = (
		query => \@query,
		require_objects => $relationships,
	);	
	
	my $cases = LIMS::DB::Request::Manager->get_requests(@params);
	
	return $cases;
}

#-------------------------------------------------------------------------------
sub get_new_and_relapsed_cases {
	my $self = shift;
	my $args = shift; # warn Dumper $args;

	my $org_code = $args->{org_code};
	
	# need org_code = 'xxxxx' OR org_code like 'xxx%':
	my $org_code_expr = length($org_code) < 5
		? { like => $org_code . '%' } # '%%' ok for wild-card
		: $org_code; # ie look for specific location
	
	my $days_ago =
		$self->time_now
			->subtract( days => $args->{duration} )
			->truncate( to => 'day' );
	
	my @tables = qw( patients diagnoses referral_sources request_history );
	
	my $relationships = $self->get_relationships(\@tables);
	
	my $option_authorised
		= LIMS::DB::StatusOption->new(description => 'authorised')->load;

	# set status level required for displaying reports:
	my $report_status = $option_authorised->is_active eq 'yes'
		? 'authorised'
		: 'reported';

	my @params = (
		query => [
			'request_report.status'	 => { ne => 'default' },
			'request_history.time'   => { ge => $days_ago },
			'request_history.action' => $report_status,
			'referral_sources.organisation_code' => $org_code_expr,
		],
		require_objects => $relationships,
		# sort_by => 'request_history.time', # doesn't work as t1.id gets priority
	);
	
	my $cases = LIMS::DB::Request::Manager->get_requests(@params);
	
	return $cases;
}

#-------------------------------------------------------------------------------
sub update_request {
	my ($self, $form_data) = @_; # use Data::Dumper; # warn Dumper $form_data;
	
	# get original data from session (and store it in $self->frozen_data):
	my $original_data = $self->_get_original_data;
	
    my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;

	my $update_request = sub {
		# referrer_code (only submitted if field edited):
		if ( $form_data->{referrer_code} ) {  
			my $original_referrer_code = $self->frozen_data->{referrer_code};
    
			if ( $form_data->{referrer_code} ne $original_referrer_code ) {
				$self->do_referrer_update($form_data);
			} # warn "$form_data->{referrer_code} ne $original_referrer_code";
		}
		# specimen_code (always submitted):
		{
			my $original_specimen_code = $self->frozen_data->{specimen_code};
   
			if ( $form_data->{specimen_code} ne $original_specimen_code ) {
				$self->do_specimen_code_update($form_data);
			} # warn "$form_data->{specimen_code} ne $original_specimen_code";
		}
		# clinical trial:
		{ 
			no warnings 'uninitialized'; # either original or form params can be undef
			my $original_trial_id = $self->frozen_data->{trial_id};
			
			if ( $form_data->{trial_id} != $original_trial_id ) {
				$self->do_clinical_trial_update($form_data);
			}		
		}
		# external reference:
		{
			no warnings 'uninitialized'; # either original or form param can be undef
			my $original_external_ref = $self->frozen_data->{external_reference};
			
			if ( $form_data->{external_reference} ne $original_external_ref ) {
				$self->do_external_ref_update($form_data);
			}		
		}
		# trial number:
		{
			no warnings 'uninitialized'; # either original or form params can be undef
			my $original_trial_number = $self->frozen_data->{trial_number};
			
			if ( $form_data->{trial_number} ne $original_trial_number ) {
				$self->do_trial_number_update($form_data);
			}		
		}
		# request_options:
		{
			no warnings 'uninitialized'; # any of form params or original data can be undef
			# get list of all additional options:
			my $request_options
				= LIMS::DB::AdditionalOption::Manager->get_additional_options;
			
			my $frozen = $self->frozen_data; # warn Dumper $frozen;
			
			# hash for new request options:
			my %new_request_options = ();
			
			foreach (@$request_options) {
				my $option   = $_->option_name; # warn Dumper $option;
				my $original = $frozen->{$option} || '';
				
				$new_request_options{$_->id}{new}  = $form_data->{$option}; # 1 or undef				
				$new_request_options{$_->id}{old}  = $original; # old value
				$new_request_options{$_->id}{name} = $option;
			}
			
			if (%new_request_options) { # warn Dumper \%new_request_options;
				$self->do_request_options_update($form_data, \%new_request_options);
			}
		}
		# request_consent:
		{
			no warnings 'uninitialized'; # any of form params or original data can be undef
			# get list of all additional options:
			my $request_consent
				= LIMS::DB::ConsentOption::Manager->get_consent_options;
			
			my $frozen = $self->frozen_data;
			
			# hash for new request consent:
			my %new_request_consent = ();
			
			foreach (@$request_consent) {
				my $consent_name = $_->consent_name; 

				$new_request_consent{$_->id}{new}  = $form_data->{$consent_name}; # yes, no or undef				
				$new_request_consent{$_->id}{old}  = $frozen->{$consent_name}; # old value
				$new_request_consent{$_->id}{name} = $consent_name;
			}
			
			if (%new_request_consent) {
				$self->do_request_consent_update($form_data, \%new_request_consent);
			}
		}
		
		# update error code & history log:
		$self->do_request_error_code($form_data); 
		$self->do_history_log($form_data); 		
	};
	
#$self->set_rose_debug(1);	
	my $result = $db->do_transaction( $update_request );
#$self->set_rose_debug(0);

	# return value needs to match format in update_patient_case():
	return {
		error   => $db->error ? 'update_request() error - ' . $db->error : undef,
		success => $result, # true on success, false on failure ( & will set $db->error)
	}
}

#-------------------------------------------------------------------------------
sub unlock_request {
	my ($self, $request_id) = @_;
	
	my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
	
	my $unlock = sub {
		my $o = LIMS::DB::Request->new(id => $request_id)->load;
		
		$o->updated_at($self->time_now);
=begin # would need to manually reset status to locked, reverted to timestamp:
		# update request_status:
		my $status_otions_map = $self->request_status_options_map;
		
		my $status_option_id
			= $status_otions_map->{authorised}->{is_active} eq 'yes'
			? $status_otions_map->{authorised}->{id}
			: $status_otions_map->{reported}->{id};
			
		$o->status_option_id($status_option_id);	
=cut		
		$o->save(changes_only => 1);
		
		LIMS::DB::RequestHistory->new(
			request_id => $request_id,
			user_id    => $self->user_profile->{id},
			action     => 'unlocked record',
		)->save;
	};
	
#$self->set_rose_debug(1);	
	my $ok = $db->do_transaction($unlock);	
#$self->set_rose_debug(0);	
	
	# don't need return value unless error:
    return $ok ? 0 : 'unlock_request() error - ' . $db->error;	
}

#-------------------------------------------------------------------------------
sub delete_request {
	my $self = shift;
	my $data = shift; # $self->debug($data); # return;
	
	my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
	
	my $request_id = $data->{request_id};
	
	my $request = LIMS::DB::Request->new(id => $request_id)
		->load(with => ['patient_case.patient', 'request_report.diagnosis']);
	
	my %current_action = (
		user_id => $self->user_profile->{id},
		time 	=> $self->time_now(),
		action 	=> sprintf 'deleted record (%s)', $data->{reason},
	);
	
	my $delete = sub {
		# retrieve all history data for archiving before cascade delete:
		$self->_archive_request_history($request); # warn Dumper $self->archive;
		# add current  action (delete):
		$self->add_to_archive(\%current_action);
		
		# sort archive into datetime order:
		my @chronological = sort {
			DateTime->compare($a->{time}, $b->{time})
		} $self->archived_data; # warn Dumper \@chronological;
		
		# save to deleted_requests table:
		for my $entry (@chronological) { # warn Dumper $data;
			LIMS::DB::DeletedRequest->new(
				request_id 		=> $request_id,
				request_number 	=> $request->request_number,
				year 			=> $request->year,
				action 			=> $entry->{action},
				user_id 		=> $entry->{user_id},
				time 			=> $entry->{time},
			)->save;			
		}

		LIMS::DB::Request->new(id => $request_id)->delete;
			# (cascade => 'delete'); using ON DELETE CASCADE in sql
		
	};
#$self->set_rose_debug(1);	
	my $ok = $db->do_transaction($delete);	
#$self->set_rose_debug(0);	
	
	# don't need return value unless error:
    return $ok ? 0 : 'delete_request() error - ' . $db->error;	
}

#-------------------------------------------------------------------------------
sub get_print_run_request_ids {
	my ($self, $start, $end) = @_;	# start & end datetimes

	my $option_authorised
		= LIMS::DB::StatusOption->new(description => 'authorised')->load;

	# set status level required for printing reports:
	my $status = $option_authorised->is_active eq 'yes'
		? 'authorised'
		: 'reported';

	# get all reports where status = authorised/reported or complete AND
	# (requests.updated_at OR request_report.updated_at) between start & end dates:
	my @query = (
		description => [ $status, 'complete' ], # status options
		or => [
			and => [
				'requests.updated_at' => { ge => $start },
				'requests.updated_at' => { le => $end   },
			],
			and => [
				'request_report.updated_at' => { ge => $start },
				'request_report.updated_at' => { le => $end   },
			],
		],
	);
	
	my %args = (
		query  => \@query,
		select => [ 'id' ], # only need id's
		require_objects => [ qw(request_report status_option) ],
	);
	
	my $requests = LIMS::DB::Request::Manager->get_requests(%args);
	
	my @request_ids = map { $_->id } @$requests;
	return \@request_ids;
}

#-------------------------------------------------------------------------------
sub _archive_request_history {
	my ($self, $request) = @_;
	
	# common to all queries:
	my %args = ( query => [ request_id => $request->id ] );

	{ # request error_code:
		local $args{require_objects} = 'error_code'; # warn Dumper \%args;

		my $o = LIMS::DB::RequestErrorCode::Manager->get_request_error_codes(%args);
		for (@$o) { # warn Dumper $_->as_tree;
			my $data = $self->_format_data_for_archive($_);
			# format action:
			$data->{action} = 'recorded error code ' . $_->error_code->code; 
			$self->add_to_archive($data);
		}
	}
	{ # request_history:
		my $o = LIMS::DB::RequestHistory::Manager->get_request_histories(%args);
		for (@$o) {
			my $data = $self->_format_data_for_archive($_); 
			# format action:
			my $action = $_->action; # warn Dumper $data;
			if ($action eq 'reported') { # get diagnosis:
				$action .= sprintf ' (diagnosis = %s)',
					$request->request_report->diagnosis->name;
			}
			elsif ($action eq 'registered') {
				my $patient = $request->patient_case->patient;
				$action .= sprintf ' (%s, %s, %s)',
					$patient->last_name,
					$patient->first_name,
					$patient->dob ? $patient->dob->ymd : 'NULL';
			}
			$data->{action} = $action;
			$self->add_to_archive($data);
		}
	}
	{ # request_diagnosis_history:
		local $args{require_objects} = 'diagnosis';

		my $o = LIMS::DB::RequestDiagnosisHistory::Manager
			->get_request_diagnosis_histories(%args);

		for (@$o) {
			my $data = $self->_format_data_for_archive($_);
			# format action:
			$data->{action} = sprintf 'amended diagnosis = %s; reason = %s',
				$_->diagnosis->name, $_->reason; 
			$self->add_to_archive($data);
		}
	}
	{ # request_phonelog:
		my $o = LIMS::DB::RequestPhoneLog::Manager->get_request_phone_log(%args);
		for (@$o) {
			my $data = $self->_format_data_for_archive($_);
			# format action:
			$data->{action} = join '; ', $_->status, $_->contact, $_->details;
			$self->add_to_archive($data);
		}		
	}
}

#-------------------------------------------------------------------------------
# returns common data items user_id & time:
sub _format_data_for_archive {
	my $self = shift;
	my $data = shift;
	
	my %data = (
		user_id => $data->user_id,
		time    => $data->time,
	);
	return \%data;			
}

#-------------------------------------------------------------------------------
sub update_patient_case { 
=begin # how it works:
scope determines whether to apply change to single record (default) or to all records.

1) Get a new patient_case.id - 'use_patient_case_id' param if submitted, otherwise
find existing combination of submitted patient_id, referral_source_id & unit_number
in patient_case table, or create a new patient_case.

2) If scope = all (or if only 1 record attached to patient_case), change all
instances of requests.patient_case_id to new value & delete old patient_case entry

3) If scope = single record, just change patient_case_id for single request.id

4) If referral_source is changed, requests.referrer_department_id may be incorrect,
so unless parent_organisation is same as old, requests.referrer_department_id
is updated to a new one if found, or to the entry corresponding to unknown referrer
(clinician or gp, if not already), pending referrer change.
=cut
	my ($self, $form_data) = @_; # warn 'form_data:'; warn Dumper $form_data; # return;
	
	# get original data from session (and store it in $self->frozen_data):
	my $original_data = $self->_get_original_data; # warn 'orginal_data:'; warn Dumper $original_data;
	
    my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
	
	my $i = 0; # successful updates counter
	
	my $update_patient_case = sub {
		# get new patient_case_id:
		my $new_patient_case_id = $self->_get_new_patient_case_id($form_data)
		|| die 'no patient_case_id returned by _get_new_patient_case_id()';

		# protect against old patient_case_id == new patient_case_id, where 'old'
		# patient_case gets deleted if scope set to 'all':
		return 0 unless $new_patient_case_id != $original_data->{patient_case_id};

		if ($form_data->{scope} eq 'all') { # warn 'all';
			{ # update requests table:
				my %args = (
					set   => { patient_case_id => $new_patient_case_id },
					where => [ patient_case_id => $original_data->{patient_case_id} ],
				);
				$i += LIMS::DB::Request::Manager->update_requests(%args);
			}
			{ # delete old patient_case:
				my $patient_case_id = $original_data->{patient_case_id};
				LIMS::DB::PatientCase->new(id => $patient_case_id)->delete();
			}
		}
		else { # just update single record:
			my $o = LIMS::DB::Request->new(id => $form_data->{_request_id})->load;
			$o->patient_case_id($new_patient_case_id); # warn 'one';
			$i += 1 if $o->save(changes_only => 1); # $i gets memory address on success ??	
		}
		
		# might need to update referrer department if new referral source submitted:
		if ( my $new_referral_source_id = $form_data->{referral_source_id} ) {		
			if ( $new_referral_source_id != $original_data->{referral_source_id} ) {
				$self->do_referrer_department_update($form_data);
			}
		}
		
		$self->do_patient_case_history($form_data);
	};
	
#$self->set_rose_debug(1);
	# this method differs from the usual do_transaction(), which returns 0 on
	# error and 1 on success, even if coderef exits early; not interested in
	# return value now, only number of successful updates, which gets returned to
	# controller, along with $db->error (if any):
	$db->do_transaction($update_patient_case); # warn $i; # warn Dumper $db->error;
	# or can do: $db->do_transaction( sub { $result = $update_patient_case->() } );
#$self->set_rose_debug(0);		

	# return hashref of db error (if any), and numerical value of success (updates count):
	return {
		error   => $db->error ? 'update_patient_case() error - ' . $db->error : undef,
		success => $i,
	}
}

#-------------------------------------------------------------------------------
sub get_first_request_date {
	my $self = shift;

	return LIMS::DB::Request::Manager->get_requests(limit => 1)->[0]->created_at;
}

#-------------------------------------------------------------------------------
sub _get_hmrn_new_diagnoses_constraints {
	my ($self, $args) = @_;
	
	# if request for single lab number:
	if ( my $lab_number = $args->{lab_number} ) {
		my ($request_number, $year) = LIMS::Local::Utils::split_labno($lab_number);
		
		return [ request_number => $request_number, year => $year ];
	}
	# if date range requested:
	elsif ($args->{date_from}) { # date_from is minimun required to trigger date range
        my $start_date
			= LIMS::Local::Utils::to_datetime_using_datecalc($args->{date_from});
			
		my $end_date
			= $args->{date_to} # date_to is optional
			? LIMS::Local::Utils::to_datetime_using_datecalc($args->{date_to})
			: DateTime->today->ymd; # make it today if not supplied
		# warn Dumper [$start_date, $end_date];
		
		return [
			'request_history.time' => { gt => $start_date },
			'request_history.time' => { le => $end_date },
		];
	}
	else {
		my $days = $args->{duration} || 7; # value for 'previous_week' param & default 
		my $days_ago
			= $self->time_now->subtract( days => $days )->truncate( to => 'day' );
			
		return [ 'request_history.time' => { gt => $days_ago } ];
	}
}

#-------------------------------------------------------------------------------
sub _get_new_patient_case_id {
	my ($self, $form_data) = @_;
	
	# patient_case_id might be submitted by 'use this' radio button:
	my $patient_case_id = $form_data->{use_patient_case_id} || '';
	
	# if $form_data patient_case_id used, original values are submitted in form
	# fields so need to get new values into $form_data to replace original ones:
	if ($patient_case_id) {
		my $patient_case = LIMS::DB::PatientCase->new(id => $patient_case_id)
			->load(with => 'referral_source');
		
		map {
			$form_data->{$_} = $patient_case->$_;
		} qw(unit_number referral_source_id);
		
		$form_data->{_location_name} = $patient_case->referral_source->display_name;
	}
	# if we don't already have a patient_case_id, go and get one:
	else {
		# find matching patient_case, or create new:
		my $patient_case = $self->get_patient_case($form_data);
		$patient_case_id = $patient_case->id;			
	}
	
	return $patient_case_id;
}

#-------------------------------------------------------------------------------
sub _get_original_data {
	my $self = shift;
	
	# put original data into frozen_data():
	my $original_data = $self->session_data('_request_edit_data');
	$self->frozen_data($original_data); # warn Dumper $original_data->{_data};
	
=begin # don't do this - causes fatal error if back button used & then data resubmitted
	# clear session data (not required as it's overwritten anyway, but tidier):
	# $self->clear_data('_request_edit_data');  
=cut
	return $original_data;
}

#-------------------------------------------------------------------------------
sub _verify_request_exists {
	my ($self, $request_id) = @_;
	
	my @q = (id => $request_id);
	return LIMS::DB::Request::Manager->get_requests_count( query => \@q );
}

#-------------------------------------------------------------------------------
sub _get_request_data {
	my $self = shift;
	my $data = shift;

    # get referrer_department.id for supplied referral source & referrer code:
    my $referrer_department = $self->_get_referrer_department($data);
	$data->{referrer_department_id} = $referrer_department->id;

	my %request_data = map {
		$_ => $data->{$_};
	} qw( request_number patient_case_id referrer_department_id );

	# current year:
    # $request_data{year} = DateTime->now->year; # done by DB::Request meta data

=begin # not using this method anymore	
	{ # check for previously deleted request_number/year combination, if so use request.id:
		my %args = (
			request_number => $data->{request_number},
			year => DateTime->now->year,
		);
#$self->set_rose_debug(1);	
		my $deleted_request
			= LIMS::DB::DeletedRequest->new(%args)->load(speculative => 1);
#$self->set_rose_debug(0);	
		if ($deleted_request) {
			$request_data{id} = $deleted_request->request_id;
		}
	}
=cut	
	return \%request_data;
}

#-------------------------------------------------------------------------------
sub _get_referrer_department {
	my $self = shift;
	my $data = shift; # warn Dumper $data;

	my $referrer
		= LIMS::DB::Referrer->new(national_code => $data->{referrer_code})->load;

	my $referral_source
		= LIMS::DB::ReferralSource->new(id => $data->{referral_source_id})
			->load(with => 'referral_type'); # get referral_type for possible use later;
		
    # get referrer_department for supplied referral source & referrer code:
	my %args = (
		referrer_id => $referrer->id,
		parent_organisation_id => $referral_source->parent_organisation_id,
	);
	
	my $referrer_department
		= LIMS::DB::ReferrerDepartment->new(%args)->load(speculative => 1);

	# if no $referrer_department, get referrer_department.id for unknown referrer of correct type:
	if (! $referrer_department ) { # warn 'here';
		# get map between referral_type.id & referrer_department.id for unknown locations:
		my $referral_type_map = $self->referral_type_map; # warn Dumper $referral_type_map;
		
		# get id of default referrer_department for this location type (hospital or practice):
		my $referrer_department_id
			= $referral_type_map->{$referral_source->referral_type_id};
			
		$referrer_department = LIMS::DB::ReferrerDepartment
			->new(id => $referrer_department_id)->load; # warn Dumper $referrer_department->as_tree;
    }
	
	return $referrer_department;
}

#-------------------------------------------------------------------------------
sub _build_request_status_options_map {
	my $self = shift;
	
	my $status_options = LIMS::DB::StatusOption::Manager->get_status_options;
	
	my %map = map {
		$_->description => $_->as_tree;
	} @$status_options; # warn Dumper \%map;
	
	return \%map;
}

#-------------------------------------------------------------------------------
# creates map between referral_type.id and referrer_department.id for unknown locations:
sub _build_referral_type_map {
	my $self = shift;
	
	my %args = ( # using 2 custom relationships in ReferralType & ParentOrg classes:
		require_objects => 'unknown_parent_org.unknown_referrer_department',
	);

	# get referral_types:
	my $referral_types
		= LIMS::DB::ReferralType::Manager->get_referral_types(%args);

=begin # effective sql for ReferralType::Manager->get_referral_types():
SELECT 
  t1.id as 'referral_type_id',
  t3.id as 'referrer_department_id'
FROM
  referral_types t1 
  JOIN parent_organisations t2 ON (t1.default_unknown = t2.parent_code)
  JOIN referrer_department t3 ON (t2.id = t3.parent_organisation_id)
=cut

	# create map:
	my %referral_type_map = map { 
		$_->id => $_->unknown_parent_org->unknown_referrer_department->id;
	} @$referral_types; # warn Dumper \%referral_type_map;

	return \%referral_type_map;
}

#-------------------------------------------------------------------------------
# warning: method of same name exists in C::Roles::DataMap
# returns hash of arrayrefs
sub _build_specimen_lab_test_map { 
	my $self = shift;
	
	my $o = $self->get_objects('SpecimenLabTest');

	my %map;

	for (@$o) {
		my $specimen_id = $_->specimen_id; # warn $specimen_id;
		my $lab_test_id = $_->lab_test_id; # warn $lab_test_id;
		
		push @{ $map{$specimen_id} }, $lab_test_id;
	} # warn Dumper \%map;
	
	return \%map;
}

#-------------------------------------------------------------------------------
# *** method of similar name in Role::RequestUpdate ***
sub _get_clinical_trial_data {
	my $self = shift;
	my $data = shift;

	# no need for db access if no trial_id:
	return unless $data->{trial_id};

	my $patient_case = LIMS::DB::PatientCase->new(
		id => $data->{patient_case_id}
	)->load;

	my %trial_data = (
		trial_id     => $data->{trial_id},
		trial_number => $data->{trial_number},
		patient_id   => $patient_case->patient_id,
	);

	return \%trial_data;
}

1;

__END__
multi_many_ok is used to suppress:
WARNING: Fetching sub-objects via more than one "one to many" relationship in a
single query may produce many redundant rows, and the query may be slow. If
you're sure you want to do this, you can silence this warning by using the
"multi_many_ok" parameter