Bug 36563: Turn into array only if required
[koha.git] / catalogue / itemsearch.pl
1 #!/usr/bin/perl
2 # Copyright 2013 BibLibre
3 #
4 # This file is part of Koha
5 #
6 # Koha is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # Koha is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Koha; if not, see <http://www.gnu.org/licenses>.
18
19 use Modern::Perl;
20 use CGI;
21
22 use JSON qw( to_json from_json );
23
24 use C4::Auth qw( get_template_and_user );
25 use C4::Circulation qw( barcodedecode );
26 use C4::Output qw( output_with_http_headers output_html_with_http_headers );
27 use C4::Items qw( SearchItems );
28 use C4::Koha qw( GetAuthorisedValues );
29
30 use Koha::AuthorisedValues;
31 use Koha::Biblios;
32 use Koha::Item::Search::Field qw(GetItemSearchFields);
33 use Koha::ItemTypes;
34 use Koha::Libraries;
35
36 my $cgi = CGI->new;
37
38 my $format = $cgi->param('format');
39 my $template_name = 'catalogue/itemsearch.tt';
40
41 if (defined $format and $format eq 'json') {
42     $template_name = 'catalogue/itemsearch_json.tt';
43
44     # Map DataTables parameters with 'regular' parameters
45     $cgi->param('rows', scalar $cgi->param('length'));
46     $cgi->param('page', (scalar $cgi->param('start') / scalar $cgi->param('length')) + 1);
47     my @columns = @{ from_json $cgi->param('columns') };
48     $cgi->param('sortby', $columns[ $cgi->param('order[0][column]') ]->{name});
49     $cgi->param('sortorder', scalar $cgi->param('order[0][dir]'));
50
51     my @f = $cgi->multi_param('f');
52     my @q = $cgi->multi_param('q');
53
54     # If index indicates the value is a barcode, we need to preproccess it before searching
55     for ( my $i = 0; $i < @q; $i++ ) {
56         $q[$i] = barcodedecode($q[$i]) if $f[$i] eq 'barcode';
57     }
58
59     push @q, '' if @q == 0;
60     my @op = $cgi->multi_param('op');
61     my @c = $cgi->multi_param('c');
62     for my $column (@columns) {
63         my $search = $column->{search}->{value};
64         my $column_name = $column->{name};
65         if (defined $search and $search ne '') {
66             my @words = split /\s+/, $search;
67             foreach my $word (@words) {
68                 push @f, $column_name;
69                 push @c, 'and';
70
71                 if ( grep { $_ eq $column_name } qw( ccode homebranch holdingbranch location itype notforloan itemlost onloan ) ) {
72                     push @q, "$word";
73                     push @op, '=';
74                 } else {
75                     push @q, "%$word%";
76                     push @op, 'like';
77                 }
78             }
79         }
80     }
81     $cgi->param('f', @f);
82     $cgi->param('q', @q);
83     $cgi->param('op', @op);
84     $cgi->param('c', @c);
85 } elsif (defined $format and $format eq 'csv') {
86     $template_name = 'catalogue/itemsearch_csv.tt';
87
88     # Retrieve all results
89     $cgi->param('rows', 0);
90 } elsif (defined $format and $format eq 'barcodes') {
91     # Retrieve all results
92     $cgi->param('rows', 0);
93 } elsif (defined $format) {
94     die "Unsupported format $format";
95 }
96
97 my ($template, $borrowernumber, $cookie) = get_template_and_user({
98     template_name => $template_name,
99     query => $cgi,
100     type => 'intranet',
101     flagsrequired   => { catalogue => 1 },
102 });
103
104 my $mss = Koha::MarcSubfieldStructures->search({ frameworkcode => '', kohafield => 'items.itemlost', authorised_value => [ -and => {'!=' => undef }, {'!=' => ''}] });
105 my $itemlost_values = $mss->count ? GetAuthorisedValues($mss->next->authorised_value) : [];
106
107 $mss = Koha::MarcSubfieldStructures->search({ frameworkcode => '', kohafield => 'items.withdrawn', authorised_value => [ -and => {'!=' => undef }, {'!=' => ''}] });
108 my $withdrawn_values = $mss->count ? GetAuthorisedValues($mss->next->authorised_value) : [];
109
110 if ( Koha::MarcSubfieldStructures->search( { frameworkcode => '', kohafield => 'items.new_status' } )->count ) {
111     $template->param( has_new_status => 1 );
112 }
113
114 if ( defined $format ) {
115     # Parameters given, it's a search
116
117     my $filter = {
118         conjunction => 'AND',
119         filters => [],
120     };
121
122     foreach
123         my $p (qw(homebranch holdingbranch location itype ccode issues datelastborrowed notforloan itemlost withdrawn))
124     {
125         my @q;
126         @q = $cgi->multi_param( $p . "[]" );
127         if ( scalar @q == 0 ) {
128             @q = $cgi->multi_param($p);
129         }
130         if (@q) {
131             if ( $q[0] ne '' ) {
132                 my $f = {
133                     field => $p,
134                     query => \@q,
135                 };
136                 if ( my $op = scalar $cgi->param( $p . '_op' ) ) {
137                     $f->{operator} = $op;
138                 }
139                 push @{ $filter->{filters} }, $f;
140             }
141         }
142     }
143
144     my @c = $cgi->multi_param('c');
145     my @fields = $cgi->multi_param('f');
146     my @q = $cgi->multi_param('q');
147     my @op = $cgi->multi_param('op');
148
149     my $f;
150     for (my $i = 0; $i < @fields; $i++) {
151         my $field = $fields[$i];
152         my $q = shift @q;
153         my $op = shift @op;
154         if (defined $q and $q ne '') {
155             if (C4::Context->preference("marcflavour") ne "UNIMARC" && $field eq 'publicationyear') {
156                 $field = 'copyrightdate';
157             }
158
159             if ($i == 0) {
160                 $f = {
161                     field => $field,
162                     query => $q,
163                     operator => $op,
164                 };
165             } else {
166                 my $c = shift @c;
167                 $f = {
168                     conjunction => $c,
169                     filters => [
170                         $f, {
171                             field => $field,
172                             query => $q,
173                             operator => $op,
174                         }
175                     ],
176                 };
177             }
178         }
179     }
180     push @{ $filter->{filters} }, $f;
181
182     # Yes/No parameters
183     foreach my $p (qw( damaged new_status )) {
184         my $v = $cgi->param($p) // '';
185         my $f = {
186             field => $p,
187             query => 0,
188         };
189         if ( $p eq 'new_status' ) {
190             $f->{ifnull} = 0;
191         }
192         if ($v eq 'yes') {
193             $f->{operator} = '!=';
194             push @{ $filter->{filters} }, $f;
195         } elsif ($v eq 'no') {
196             $f->{operator} = '=';
197             push @{ $filter->{filters} }, $f;
198         }
199     }
200
201     # null/is not null parameters
202     foreach my $p (qw( onloan )) {
203         my $v = $cgi->param($p) // '';
204         my $f = {
205             field => $p,
206             operator => "is",
207         };
208         if ( $v eq 'IS NOT NULL' ) {
209             $f->{query} = "not null";
210         } elsif ( $v eq 'IS NULL' ) {
211             $f->{query} = "null";
212         }
213         push @{ $filter->{filters} }, $f unless ( $v eq "" );
214     }
215
216     if (my $itemcallnumber_from = scalar $cgi->param('itemcallnumber_from')) {
217         push @{ $filter->{filters} }, {
218             field => 'itemcallnumber',
219             query => $itemcallnumber_from,
220             operator => '>=',
221         };
222     }
223     if (my $itemcallnumber_to = scalar $cgi->param('itemcallnumber_to')) {
224         push @{ $filter->{filters} }, {
225             field => 'itemcallnumber',
226             query => $itemcallnumber_to,
227             operator => '<=',
228         };
229     }
230
231     my $sortby = $cgi->param('sortby') || 'itemnumber';
232     if (C4::Context->preference("marcflavour") ne "UNIMARC" && $sortby eq 'publicationyear') {
233         $sortby = 'copyrightdate';
234     }
235     my $search_params = {
236         rows => scalar $cgi->param('rows') // 20,
237         page => scalar $cgi->param('page') || 1,
238         sortby => $sortby,
239         sortorder => scalar $cgi->param('sortorder') || 'asc',
240     };
241
242     my ($results, $total_rows) = SearchItems($filter, $search_params);
243
244     if ($format eq 'barcodes') {
245         print $cgi->header({
246             type => 'text/plain',
247             attachment => 'barcodes.txt',
248         });
249
250         foreach my $item (@$results) {
251             print $item->{barcode} . "\n";
252         }
253         exit;
254     }
255
256     if ($results) {
257         foreach my $item (@$results) {
258             my $biblio = Koha::Biblios->find( $item->{biblionumber} );
259             $item->{biblio} = $biblio;
260             $item->{biblioitem} = $biblio->biblioitem->unblessed;
261             my $checkout = Koha::Checkouts->find({ itemnumber => $item->{itemnumber} });
262             $item->{checkout} = $checkout;
263         }
264     }
265
266     $template->param(
267         filter        => $filter,
268         search_params => $search_params,
269         results       => $results,
270         total_rows    => $total_rows,
271         user          => Koha::Patrons->find( $borrowernumber ),
272     );
273
274     if ($format eq 'csv') {
275         print $cgi->header({
276             type => 'text/csv',
277             attachment => 'items.csv',
278         });
279
280         for my $line ( split '\n', $template->output ) {
281             print "$line\n" unless $line =~ m|^\s*$|;
282         }
283     } elsif ($format eq 'json') {
284         $template->param(draw => scalar $cgi->param('draw'));
285         output_with_http_headers $cgi, $cookie, $template->output, 'json';
286     }
287
288     exit;
289 }
290
291 # Display the search form
292
293 my @branches = map { value => $_->branchcode, label => $_->branchname }, Koha::Libraries->search( {}, { order_by => 'branchname' } )->as_list;
294 my @itemtypes = map { value => $_->itemtype, label => $_->translated_description }, Koha::ItemTypes->search_with_localization->as_list;
295
296 my @ccodes = Koha::AuthorisedValues->get_descriptions_by_koha_field({ kohafield => 'items.ccode' });
297 foreach my $ccode (@ccodes) {
298     $ccode->{value} = $ccode->{authorised_value},
299     $ccode->{label} = $ccode->{lib},
300 }
301
302 my @itemlosts;
303 foreach my $value (@$itemlost_values) {
304     push @itemlosts, {
305         value => $value->{authorised_value},
306         label => $value->{lib},
307     };
308 }
309
310 my @withdrawns;
311 foreach my $value (@$withdrawn_values) {
312     push @withdrawns, {
313         value => $value->{authorised_value},
314         label => $value->{lib},
315     };
316 }
317
318 my @items_search_fields = GetItemSearchFields();
319
320 my $authorised_values = {};
321 foreach my $field (@items_search_fields) {
322     if (my $category = ($field->{authorised_values_category})) {
323         $authorised_values->{$category} = GetAuthorisedValues($category);
324     }
325 }
326
327 $template->param(
328     branches => \@branches,
329     itemtypes => \@itemtypes,
330     ccodes => \@ccodes,
331     itemlosts => \@itemlosts,
332     withdrawns => \@withdrawns,
333     items_search_fields => \@items_search_fields,
334     authorised_values_json => to_json($authorised_values),
335 );
336
337 output_html_with_http_headers $cgi, $cookie, $template->output;