package LIMS::Local::Search; use LIMS::Local::Utils (); use LIMS::Local::Debug; use DateTime; DateTime->DefaultLocale('en_GB'); # set default locale use Data::Dumper; use Moose; use namespace::clean -except => 'meta'; has query => ( is => 'ro', isa => 'HashRef', default => sub { {} }, traits => ['Hash'], handles => { set_query => 'set' }, ); has user_profile => ( is => 'rw', isa => 'HashRef', required => 1 ); # needs to be writable (_process_sql_simple_search() needs to delete keys): has form_fields => ( is => 'ro', isa => 'HashRef', required => 1 ); __PACKAGE__->meta->make_immutable; #------------------------------------------------------------------------------- sub generate_query { my $self = shift; my $params = $self->form_fields; # warn Dumper $params; my $profile = $self->user_profile; # warn Dumper $profile; my $dt = sub { LIMS::Local::Utils::to_datetime_using_parsedate(@_) }; # returns 0 if no date my @date_fields = qw(day month year); my @params_for_deletion = (); # eg date_extend, date_from, date_to, previous_days # process sql_simple_search params if supplied: if ( my $field = $params->{sql_simple_search} ) { $self->_process_sql_simple_search($field); } # first deal with date constraints and reformat date fields: { # requests.created_at & constraints upon; don't use eslif's to ensure all 'invalid' # params added to @params_for_deletion; successive 'created_at' will clobber any previous: if ( grep $params->{$_}, map 'request_'.$_, @date_fields ) { # create date as DT object: my %dmy = map { $_ => $params->{'request_'.$_} } @date_fields; my $date = DateTime->new(%dmy); # warn Dumper $date->ymd; # if all_before or all_after flags set: if ( my $extend_date = $params->{date_extend} ) { my $expr = $extend_date eq 'all_after' ? { ge => $date->ymd } # ie 00:00:00 : { le => $date->add(days => 1)->ymd }; # ie 00:00:00 $self->set_query( created_at => $expr ); push @params_for_deletion, 'date_extend'; } else { # need to create a 'between' to handle timestamp field: my @dates = ( $date->ymd, $date->clone->add(days => 1)->ymd ); $self->set_query( created_at => { ge_le => \@dates } ); } push @params_for_deletion, 'request_'.$_ for @date_fields; } # previous number of days: if ( my $n = $params->{previous_days} ) { my $date = LIMS::Local::Utils::time_now->subtract(days => $n); $self->set_query( created_at => { ge => $date->ymd } ); push @params_for_deletion, 'previous_days'; } # specific year: if ( my $yr = $params->{specific_year} ) { # $self->set_query( year => $yr ); # need to use created_at: my @dates = ( &$dt("1/1/$yr"), &$dt("31/12/$yr")->add(days => 1) ); $self->set_query( created_at => { ge_le => \@dates } ); push @params_for_deletion, 'specific_year'; } # date_from +/- date_to: if ( grep $params->{$_}, qw(date_from date_to) ) { # don't mix with 'date_extend' param my $from = &$dt($params->{date_from}) || &$dt('1/1/2000'); # any valid date before start my $to = &$dt($params->{date_to}) || LIMS::Local::Utils::today(); my @dates = ( $from->ymd, $to->add(days => 1)->ymd ); # ie to 00:00:00 on next day $self->set_query( created_at => { ge_le => \@dates } ); push @params_for_deletion, grep $params->{$_}, qw(date_from date_to); } } { # DoB: if ( grep $params->{$_}, map 'dob_'.$_, @date_fields ) { # create dob as DT object: my %dmy = map { $_ => $params->{'dob_'.$_} } @date_fields; # warn Dumper \%dmy; my $dob = DateTime->new(%dmy); # warn Dumper $dob->ymd; $self->set_query( dob => $dob ); push @params_for_deletion, 'dob_'.$_ for @date_fields; } } # delete redundant entries in %params list: delete $params->{$_} for @params_for_deletion; # warn Dumper \@params_for_deletion; while ( my($field_name, $search_term) = each %$params ) { next unless $search_term; # skip if field empty # referrer name search: if ( $field_name eq 'referrer_name' ) { # converts comma/spaces to single space for GP's & clinicians format: $search_term =~ s/[\,\s]+/ /; $self->set_query( 'referrers.name' => { like => $search_term . '%' } ); } # first_name/last_name/name search: elsif ( $field_name =~ /name\Z/ ) { # \A(first|last)_name\Z # enable 'like' queries on names: $self->set_query( $field_name => { like => $search_term . '%' } ); } # lab_number search: elsif ( $field_name eq 'lab_number' ) { my ($request_number, $year) = LIMS::Local::Utils::split_labno( $search_term ); $self->set_query( request_number => $request_number ); $self->set_query( year => $year ); } # nhs_number search: elsif ( $field_name eq 'nhs_number' ) { # collapse spaces if submitted in nnn nnn nnnn format: $search_term =~ s/\s+//g; $self->set_query( nhs_number => $search_term ); } # request options: elsif ( grep $field_name eq $_, qw(private urgent copy_to) ) { $self->set_query( option_name => $field_name ); } # wildcard search: elsif ( $search_term =~ /[\%\*]/ ) { # substitute * for sql wildcard: $search_term =~ s/\*/\%/; $self->set_query( $field_name => { like => $search_term } ); } # everything else: else { $self->set_query( $field_name => $search_term ); } } # if user is external to local network, are they restricted to own records? unless ($profile->{is_local_network}) { # most are, so don't open $cfg unless my $cfg = LIMS::Local::Config->instance; if ($cfg->{settings}->{local_network_restriction}) { # if users restricted: my $parent_code = $profile->{user_location}->{region_code}; # warn 'HERE'; # only include records from users parent organisation: $self->set_query( parent_code => $parent_code ); } } return $self->query; } #------------------------------------------------------------------------------- sub _process_sql_simple_search { my ($self, $search_field) = @_; my $params = $self->form_fields; my $logic = $params->{logic}; # MATCHES, CONTAINS, LACKS, etc my $search_term = $params->{kwd}; =begin # TODO - could allow AND / OR in search term (need to ensure rels load for AND): foo => { like => [ '%bar%', '%baz%' ] }, # foo LIKE '%bar%' OR foo LIKE '%baz% and => [ foo => { like => 'bar' }, foo => { like => 'baz'} ], # foo LIKE '%bar%' AND foo LIKE '%baz% =cut my $constraints_map = { BEGINS => { like => $search_term . '%' }, ENDS => { like => '%' . $search_term }, CONTAINS => { like => '%' . $search_term . '%' }, LACKS => { 'not like' => '%' . $search_term . '%' }, MATCHES => $search_term, }; # get constraint expression for required logic: my $query_constraint = $constraints_map->{$logic}; $self->set_query( $search_field => $query_constraint ); # delete these keys so generate_query() doesn't choke on them: map { delete $params->{$_} } qw(sql_simple_search logic kwd); } 1;