Bug 37000: (follow-up) Add foreign key last
[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 $mss = Koha::MarcSubfieldStructures->search(
111     {
112         frameworkcode    => '', kohafield => 'items.damaged',
113         authorised_value => [ -and => { '!=' => undef }, { '!=' => '' } ]
114     }
115 );
116 my $damaged_values = $mss->count ? GetAuthorisedValues( $mss->next->authorised_value ) : [];
117
118 if ( Koha::MarcSubfieldStructures->search( { frameworkcode => '', kohafield => 'items.new_status' } )->count ) {
119     $template->param( has_new_status => 1 );
120 }
121
122 if ( defined $format ) {
123     # Parameters given, it's a search
124
125     my $filter = {
126         conjunction => 'AND',
127         filters => [],
128     };
129
130     foreach my $p (
131         qw(homebranch holdingbranch location itype ccode issues datelastborrowed notforloan itemlost withdrawn damaged))
132     {
133
134         my @q;
135         @q = $cgi->multi_param( $p . "[]" );
136         if ( scalar @q == 0 ) {
137             @q = $cgi->multi_param($p);
138         }
139         if (@q) {
140             if ( $q[0] ne '' ) {
141                 my $f = {
142                     field => $p,
143                     query => \@q,
144                 };
145                 if ( my $op = scalar $cgi->param( $p . '_op' ) ) {
146                     $f->{operator} = $op;
147                 }
148                 push @{ $filter->{filters} }, $f;
149             }
150         }
151     }
152
153     my @c = $cgi->multi_param('c');
154     my @fields = $cgi->multi_param('f');
155     my @q = $cgi->multi_param('q');
156     my @op = $cgi->multi_param('op');
157
158     my $f;
159     for (my $i = 0; $i < @fields; $i++) {
160         my $field = $fields[$i];
161         my $q = shift @q;
162         my $op = shift @op;
163         if (defined $q and $q ne '') {
164             if (C4::Context->preference("marcflavour") ne "UNIMARC" && $field eq 'publicationyear') {
165                 $field = 'copyrightdate';
166             }
167
168             if ($i == 0) {
169                 $f = {
170                     field => $field,
171                     query => $q,
172                     operator => $op,
173                 };
174             } else {
175                 my $c = shift @c;
176                 $f = {
177                     conjunction => $c,
178                     filters => [
179                         $f, {
180                             field => $field,
181                             query => $q,
182                             operator => $op,
183                         }
184                     ],
185                 };
186             }
187         }
188     }
189     push @{ $filter->{filters} }, $f;
190
191     # Yes/No parameters
192     foreach my $p (qw( new_status )) {
193         my $v = $cgi->param($p) // '';
194         my $f = {
195             field => $p,
196             query => 0,
197         };
198         if ( $p eq 'new_status' ) {
199             $f->{ifnull} = 0;
200         }
201         if ( $v eq 'yes' ) {
202             $f->{operator} = '!=';
203             push @{ $filter->{filters} }, $f;
204         } elsif ( $v eq 'no' ) {
205             $f->{operator} = '=';
206             push @{ $filter->{filters} }, $f;
207         }
208     }
209
210     # null/is not null parameters
211     foreach my $p (qw( onloan )) {
212         my $v = $cgi->param($p) // '';
213         my $f = {
214             field => $p,
215             operator => "is",
216         };
217         if ( $v eq 'IS NOT NULL' ) {
218             $f->{query} = "not null";
219         } elsif ( $v eq 'IS NULL' ) {
220             $f->{query} = "null";
221         }
222         push @{ $filter->{filters} }, $f unless ( $v eq "" );
223     }
224
225     if (my $itemcallnumber_from = scalar $cgi->param('itemcallnumber_from')) {
226         push @{ $filter->{filters} }, {
227             field => 'itemcallnumber',
228             query => $itemcallnumber_from,
229             operator => '>=',
230         };
231     }
232     if (my $itemcallnumber_to = scalar $cgi->param('itemcallnumber_to')) {
233         push @{ $filter->{filters} }, {
234             field => 'itemcallnumber',
235             query => $itemcallnumber_to,
236             operator => '<=',
237         };
238     }
239
240     my $sortby = $cgi->param('sortby') || 'itemnumber';
241     if (C4::Context->preference("marcflavour") ne "UNIMARC" && $sortby eq 'publicationyear') {
242         $sortby = 'copyrightdate';
243     }
244     my $search_params = {
245         rows => scalar $cgi->param('rows') // 20,
246         page => scalar $cgi->param('page') || 1,
247         sortby => $sortby,
248         sortorder => scalar $cgi->param('sortorder') || 'asc',
249     };
250
251     my ($results, $total_rows) = SearchItems($filter, $search_params);
252
253     if ($format eq 'barcodes') {
254         print $cgi->header({
255             type => 'text/plain',
256             attachment => 'barcodes.txt',
257         });
258
259         foreach my $item (@$results) {
260             print $item->{barcode} . "\n";
261         }
262         exit;
263     }
264
265     if ($results) {
266         foreach my $item (@$results) {
267             my $biblio = Koha::Biblios->find( $item->{biblionumber} );
268             $item->{biblio} = $biblio;
269             $item->{biblioitem} = $biblio->biblioitem->unblessed;
270             my $checkout = Koha::Checkouts->find({ itemnumber => $item->{itemnumber} });
271             $item->{checkout} = $checkout;
272         }
273     }
274
275     $template->param(
276         filter        => $filter,
277         search_params => $search_params,
278         results       => $results,
279         total_rows    => $total_rows,
280         user          => Koha::Patrons->find( $borrowernumber ),
281     );
282
283     if ($format eq 'csv') {
284         print $cgi->header({
285             type => 'text/csv',
286             attachment => 'items.csv',
287         });
288
289         for my $line ( split '\n', $template->output ) {
290             print "$line\n" unless $line =~ m|^\s*$|;
291         }
292     } elsif ($format eq 'json') {
293         $template->param(draw => scalar $cgi->param('draw'));
294         output_with_http_headers $cgi, $cookie, $template->output, 'json';
295     }
296
297     exit;
298 }
299
300 # Display the search form
301
302 my @branches = map { value => $_->branchcode, label => $_->branchname }, Koha::Libraries->search( {}, { order_by => 'branchname' } )->as_list;
303 my @itemtypes = map { value => $_->itemtype, label => $_->translated_description }, Koha::ItemTypes->search_with_localization->as_list;
304
305 my @ccodes = Koha::AuthorisedValues->get_descriptions_by_koha_field({ kohafield => 'items.ccode' });
306 foreach my $ccode (@ccodes) {
307     $ccode->{value} = $ccode->{authorised_value},
308     $ccode->{label} = $ccode->{lib},
309 }
310
311 my @itemlosts;
312 foreach my $value (@$itemlost_values) {
313     push @itemlosts, {
314         value => $value->{authorised_value},
315         label => $value->{lib},
316     };
317 }
318
319 my @withdrawns;
320 foreach my $value (@$withdrawn_values) {
321     push @withdrawns, {
322         value => $value->{authorised_value},
323         label => $value->{lib},
324     };
325 }
326
327 my @damageds;
328 foreach my $value (@$damaged_values) {
329     push @damageds, {
330         value => $value->{authorised_value},
331         label => $value->{lib},
332     };
333 }
334
335 my @items_search_fields = GetItemSearchFields();
336
337 my $authorised_values = {};
338 foreach my $field (@items_search_fields) {
339     if (my $category = ($field->{authorised_values_category})) {
340         $authorised_values->{$category} = GetAuthorisedValues($category);
341     }
342 }
343
344 $template->param(
345     branches => \@branches,
346     itemtypes => \@itemtypes,
347     ccodes => \@ccodes,
348     itemlosts => \@itemlosts,
349     withdrawns => \@withdrawns,
350     damageds => \@damageds,
351     items_search_fields => \@items_search_fields,
352     authorised_values_json => to_json($authorised_values),
353 );
354
355 output_html_with_http_headers $cgi, $cookie, $template->output;