View | Details | Raw Unified | Return to bug 39637
Collapse All | Expand All

(-)a/Koha/REST/V1/Patrons.pm (-5 / +101 lines)
Lines 20-25 use Modern::Perl; Link Here
20
use Mojo::Base 'Mojolicious::Controller';
20
use Mojo::Base 'Mojolicious::Controller';
21
21
22
use Koha::Database;
22
use Koha::Database;
23
use Koha::DateUtils;
23
use Koha::Exceptions;
24
use Koha::Exceptions;
24
use Koha::Patrons;
25
use Koha::Patrons;
25
use C4::Letters qw( GetPreparedLetter EnqueueLetter SendQueuedMessages );
26
use C4::Letters qw( GetPreparedLetter EnqueueLetter SendQueuedMessages );
Lines 46-60 sub list { Link Here
46
    my $c = shift->openapi->valid_input or return;
47
    my $c = shift->openapi->valid_input or return;
47
48
48
    return try {
49
    return try {
50
        my $patrons_rs = Koha::Patrons->search();
49
51
50
        my $query      = {};
51
        my $restricted = $c->param('restricted');
52
        my $restricted = $c->param('restricted');
52
        $c->req->params->remove('restricted');
53
        $c->req->params->remove('restricted');
53
        $query->{debarred} = { '!=' => undef }
54
        if ($restricted) {
54
            if $restricted;
55
            $patrons_rs = $patrons_rs->search( { debarred => { '!=' => undef } } );
56
        }
55
57
56
        my $patrons_rs = Koha::Patrons->search($query);
58
        my $search        = $c->param('search');
57
        my $patrons    = $c->objects->search($patrons_rs);
59
        my $search_type   = $c->param('search_type');
60
        my $search_fields = $c->param('search_fields');
61
        $c->req->params->remove('search');
62
        $c->req->params->remove('search_type');
63
        $c->req->params->remove('search_fields');
64
65
        my $query = $c->_buildPatronSearchQuery(
66
            { search => $search, search_type => $search_type, search_fields => $search_fields } );
67
        $patrons_rs = $patrons_rs->search( $query, { join => 'extended_attributes' } ) if $query;
68
69
        my $patrons = $c->objects->search($patrons_rs);
58
70
59
        return $c->render(
71
        return $c->render(
60
            status  => 200,
72
            status  => 200,
Lines 65-70 sub list { Link Here
65
    };
77
    };
66
}
78
}
67
79
80
sub _buildPatronSearchQuery {
81
    my ( $self, $args ) = @_;
82
    my ( $search, $search_type, $search_fields ) = @$args{qw(search search_type search_fields)};
83
84
    $search //= '';
85
    $search =~ s/^\s+|\s+$//;
86
    if ( $search eq '' ) {
87
        return;
88
    }
89
90
    $search_type //= C4::Context->preference('DefaultPatronSearchMethod') // 'contains';
91
    my $leading_wildcard = $search_type eq 'contains' ? '%' : '';
92
93
    my $is_standard = 0;
94
    if ( !defined $search_fields || $search_fields eq '' || $search_fields eq 'standard' ) {
95
        $is_standard   = 1;
96
        $search_fields = C4::Context->preference('DefaultPatronSearchFields')
97
            // 'firstname|preferred_name|middle_name|surname|othernames|cardnumber|userid';
98
    } elsif ( $search_fields eq 'full_address' ) {
99
        $search_fields = 'streetnumber|streettype|address|address2|city|state|zipcode|country';
100
    } elsif ( $search_fields eq 'all_emails' ) {
101
        $search_fields = 'email|emailpro|B_email';
102
    } elsif ( $search_fields eq 'all_phones' ) {
103
        $search_fields = 'phone|phonepro|B_phone|altcontactphone|mobile';
104
    }
105
    my @search_fields = split /\|/, $search_fields;
106
107
    my @searched_attribute_fields = map { s/^_ATTR_//r } grep { /^_ATTR_/ } @search_fields;
108
    @search_fields = grep { !/^_ATTR_/ } @search_fields;
109
110
    my @words = split /[\s,]+/, $search;
111
    @words = grep { length > 0 } @words;
112
113
    my @queries;
114
115
    my @word_subquery_and;
116
    foreach my $word (@words) {
117
        my @word_subquery_or;
118
        foreach my $field (@search_fields) {
119
            push @word_subquery_or, { "me.$field" => { like => sprintf( '%s%s%s', $leading_wildcard, $word, '%' ) } };
120
121
            if ( $field eq 'dateofbirth' ) {
122
                eval {
123
                    my $dt   = Koha::DateUtils::dt_from_string($word);
124
                    my $date = Koha::DateUtils::output_pref( { dt => $dt, dateformat => 'rfc3339', dateonly => 1 } );
125
                    push @word_subquery_or, { "me.$field" => $date };
126
                };
127
            }
128
        }
129
        push @word_subquery_and, \@word_subquery_or;
130
    }
131
    push @queries, { '-and' => \@word_subquery_and };
132
133
    my @search_subquery_or;
134
    foreach my $field (@search_fields) {
135
        push @search_subquery_or, { "me.$field" => { like => sprintf( '%s%s%s', $leading_wildcard, $search, '%' ) } };
136
    }
137
    push @queries, { '-or' => \@search_subquery_or };
138
139
    if ( $is_standard && !@searched_attribute_fields ) {
140
        @searched_attribute_fields =
141
            Koha::Patron::Attribute::Types->search( { staff_searchable => 1, searched_by_default => 1 } )
142
            ->get_column('code');
143
    }
144
145
    if ( C4::Context->preference('ExtendedPatronAttributes') && 0 < @searched_attribute_fields ) {
146
147
        # Add each word for each extended patron attributes
148
        my @extended_attribute_codes_to_search = @searched_attribute_fields;
149
        my @extended_attribute_subquery_and;
150
        foreach my $word (@words) {
151
            push @extended_attribute_subquery_and, [
152
                {
153
                    'extended_attributes.attribute' => { like => sprintf( '%s%s%s', $leading_wildcard, $word, '%' ) },
154
                    'extended_attributes.code'      => \@extended_attribute_codes_to_search,
155
                },
156
            ];
157
        }
158
        push @queries, { '-and' => \@extended_attribute_subquery_and };
159
    }
160
161
    return \@queries;
162
}
163
68
=head3 get
164
=head3 get
69
165
70
Controller function that handles retrieving a single Koha::Patron object
166
Controller function that handles retrieving a single Koha::Patron object
(-)a/api/v1/swagger/paths/patrons.yaml (+12 lines)
Lines 9-14 Link Here
9
    produces:
9
    produces:
10
      - application/json
10
      - application/json
11
    parameters:
11
    parameters:
12
      - name: search
13
        in: query
14
        required: false
15
        type: string
16
      - name: search_type
17
        in: query
18
        required: false
19
        type: string
20
      - name: search_fields
21
        in: query
22
        required: false
23
        type: string
12
      - name: patron_id
24
      - name: patron_id
13
        in: query
25
        in: query
14
        description: Search on patron_id
26
        description: Search on patron_id
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc (-13 / +6 lines)
Lines 382-400 Link Here
382
                    "-and": function(){
382
                    "-and": function(){
383
                        let filters = [];
383
                        let filters = [];
384
384
385
                        let search_type = patron_search_form.find(".searchtype_filter").val();
386
                        let search_fields = patron_search_form.find(".searchfieldstype_filter").val() || "standard";
387
                        let pattern = patron_search_form.find(".search_patron_filter").val();
388
389
                        filters = buildPatronSearchQuery(
390
                            pattern,
391
                            {
392
                                search_type: search_type,
393
                                search_fields: search_fields,
394
                                ...(typeof extended_attribute_types != 'undefined' && {extended_attribute_types: extended_attribute_types})
395
                            }
396
                        );
397
398
                        let f_sort1 = patron_search_form.find("select[name='sort1_filter']").val();
385
                        let f_sort1 = patron_search_form.find("select[name='sort1_filter']").val();
399
                        if ( f_sort1 ) {
386
                        if ( f_sort1 ) {
400
                            filters.push({
387
                            filters.push({
Lines 783-788 Link Here
783
                    fixedHeader: false,
770
                    fixedHeader: false,
784
                }, typeof table_settings !== 'undefined' ? table_settings : null, 1, additional_filters, undefined, external_filter_nodes, parent_block.find(".search_description"), additional_search_descriptions );
771
                }, typeof table_settings !== 'undefined' ? table_settings : null, 1, additional_filters, undefined, external_filter_nodes, parent_block.find(".search_description"), additional_search_descriptions );
785
772
773
                patrons_table.on('preXhr.dt', function (e, settings, data) {
774
                    data.search_type = patron_search_form.find(".searchtype_filter").val();
775
                    data.search_fields = patron_search_form.find(".searchfieldstype_filter").val() || "standard";
776
                    data.search = patron_search_form.find(".search_patron_filter").val();
777
                });
778
786
                patron_search_form.on('submit', filter);
779
                patron_search_form.on('submit', filter);
787
                patron_search_form.on('submit', update_search_type);
780
                patron_search_form.on('submit', update_search_type);
788
                patron_search_form.on('submit', function(){
781
                patron_search_form.on('submit', function(){
(-)a/t/db_dependent/api/v1/patrons.t (-2 / +54 lines)
Lines 82-88 subtest 'list() tests' => sub { Link Here
82
82
83
    subtest 'librarian access tests' => sub {
83
    subtest 'librarian access tests' => sub {
84
84
85
        plan tests => 15;
85
        plan tests => 16;
86
86
87
        $schema->storage->txn_begin;
87
        $schema->storage->txn_begin;
88
88
Lines 157-162 subtest 'list() tests' => sub { Link Here
157
                ->status_is( 200, "Works, doesn't explode" );
157
                ->status_is( 200, "Works, doesn't explode" );
158
        };
158
        };
159
159
160
        subtest 'search & search_type & search_fields' => sub {
161
            plan tests => 27;
162
163
            my $patron = $builder->build_object(
164
                {
165
                    class => 'Koha::Patrons',
166
                    value => {
167
                        surname     => 'Doswell',
168
                        firstname   => 'Celeste',
169
                        dateofbirth => '2001-12-31',
170
                    },
171
                }
172
            );
173
174
            $t->get_ok("//$userid:$password@/api/v1/patrons?search=celest")->status_is(200)
175
                ->json_is( '/0/patron_id' => $patron->id );
176
177
            $t->get_ok("//$userid:$password@/api/v1/patrons?search=elest")->status_is(200)->json_hasnt('/0');
178
179
            $t->get_ok("//$userid:$password@/api/v1/patrons?search=elest&search_type=contains")->status_is(200)
180
                ->json_is( '/0/patron_id' => $patron->id );
181
182
            $t->get_ok("//$userid:$password@/api/v1/patrons?search=elest&search_type=contains&search_fields=surname")
183
                ->status_is(200)->json_hasnt('/0');
184
185
            $t->get_ok("//$userid:$password@/api/v1/patrons?search=elest&search_type=contains&search_fields=firstname")
186
                ->status_is(200)->json_is( '/0/patron_id' => $patron->id );
187
188
            $t->get_ok("//$userid:$password@/api/v1/patrons?search=celeste&search_fields=surname|firstname")
189
                ->status_is(200)->json_is( '/0/patron_id' => $patron->id );
190
191
            t::lib::Mocks::mock_preference( 'dateformat', 'metric' );
192
            $t->get_ok("//$userid:$password@/api/v1/patrons?search=31/12/2001&search_fields=dateofbirth")
193
                ->status_is(200)->json_is( '/0/patron_id' => $patron->id );
194
195
            t::lib::Mocks::mock_preference( 'dateformat', 'iso' );
196
            $t->get_ok("//$userid:$password@/api/v1/patrons?search=31/12/2001&search_fields=dateofbirth")
197
                ->status_is(200)->json_hasnt('/0');
198
199
            my $patron_attribute_type = $builder->build_object(
200
                {
201
                    class => 'Koha::Patron::Attribute::Types',
202
                    value => {
203
                        staff_searchable    => 1,
204
                        searched_by_default => 1,
205
                    }
206
                }
207
            );
208
            $patron->add_extended_attribute( { code => $patron_attribute_type->code, attribute => '1357924680' } );
209
            $t->get_ok("//$userid:$password@/api/v1/patrons?search=1357924680&search_fields=standard")->status_is(200)
210
                ->json_is( '/0/patron_id' => $patron->id );
211
        };
212
160
        subtest 'searching date and date-time fields' => sub {
213
        subtest 'searching date and date-time fields' => sub {
161
214
162
            plan tests => 12;
215
            plan tests => 12;
163
- 

Return to bug 39637