Bug 23485: Show barcode in holds to pull
[koha.git] / circ / pendingreserves.pl
1 #!/usr/bin/perl
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use constant PULL_INTERVAL => 2;
23
24 use C4::Context;
25 use C4::Output;
26 use CGI qw ( -utf8 );
27 use C4::Auth;
28 use C4::Debug;
29 use C4::Items qw( ModItemTransfer );
30 use C4::Reserves qw( ModReserveCancelAll );
31 use Koha::Biblios;
32 use Koha::DateUtils;
33 use Koha::Holds;
34 use DateTime::Duration;
35
36 my $input = new CGI;
37 my $startdate = $input->param('from');
38 my $enddate = $input->param('to');
39 my $theme = $input->param('theme');    # only used if allowthemeoverride is set
40 my $op         = $input->param('op') || '';
41 my $borrowernumber = $input->param('borrowernumber');
42 my $reserve_id = $input->param('reserve_id');
43
44 my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
45     {
46         template_name   => "circ/pendingreserves.tt",
47         query           => $input,
48         type            => "intranet",
49         flagsrequired   => { circulate => "circulate_remaining_permissions" },
50         debug           => 1,
51     }
52 );
53
54 my @messages;
55 if ( $op eq 'cancel_reserve' and $reserve_id ) {
56     my $hold = Koha::Holds->find( $reserve_id );
57     if ( $hold ) {
58         my $cancellation_reason = $input->param('cancellation-reason');
59         $hold->cancel({ cancellation_reason => $cancellation_reason });
60         push @messages, { type => 'message', code => 'hold_cancelled' };
61     }
62 } elsif ( $op =~ m|^mark_as_lost| ) {
63     my $hold = Koha::Holds->find( $reserve_id );
64     die "wrong reserve_id" unless $hold; # This is a bit rude, but we are not supposed to get a wrong reserve_id
65     my $item = $hold->item;
66     if ( $item and C4::Context->preference('CanMarkHoldsToPullAsLost') =~ m|^allow| ) {
67         my $patron = $hold->borrower;
68         C4::Circulation::LostItem( $item->itemnumber, "pendingreserves" );
69         if ( $op eq 'mark_as_lost_and_notify' and C4::Context->preference('CanMarkHoldsToPullAsLost') eq 'allow_and_notify' ) {
70             my $library = $hold->branch;
71             my $letter = C4::Letters::GetPreparedLetter(
72                 module => 'reserves',
73                 letter_code => 'CANCEL_HOLD_ON_LOST',
74                 branchcode => $patron->branchcode,
75                 lang => $patron->lang,
76                 tables => {
77                     branches    => $library->branchcode,
78                     borrowers   => $patron->borrowernumber,
79                     items       => $item->itemnumber,
80                     biblio      => $hold->biblionumber,
81                     biblioitems => $hold->biblionumber,
82                     reserves    => $hold->unblessed,
83                 },
84             );
85             if ( $letter ) {
86                 my $admin_email_address = $library->branchemail || C4::Context->preference('KohaAdminEmailAddress');
87
88                 C4::Letters::EnqueueLetter(
89                     {   letter                 => $letter,
90                         borrowernumber         => $patron->borrowernumber,
91                         message_transport_type => 'email',
92                         from_address           => $admin_email_address,
93                     }
94                 );
95                 unless ( $patron->notice_email_address ) {
96                     push @messages, {type => 'alert', code => 'no_email_address', };
97                 }
98                 push @messages, { type => 'message', code => 'letter_enqueued' };
99             } else {
100                 push @messages, { type => 'error', code => 'no_template_notice' };
101             }
102         }
103         $hold->cancel;
104         if ( $item->homebranch ne $item->holdingbranch ) {
105             C4::Items::ModItemTransfer( $item->itemnumber, $item->holdingbranch, $item->homebranch, 'LostReserve' );
106         }
107
108         if ( my $yaml = C4::Context->preference('UpdateItemWhenLostFromHoldList') ) {
109             $yaml = "$yaml\n\n";  # YAML is anal on ending \n. Surplus does not hurt
110             my $assignments;
111             eval { $assignments = YAML::Load($yaml); };
112             if ($@) {
113                 warn "Unable to parse UpdateItemWhenLostFromHoldList syspref : $@" if $@;
114             }
115             else {
116                 eval {
117                     while ( my ( $f, $v ) = each( %$assignments ) ) {
118                         $item->$f($v);
119                     }
120                     $item->store;
121                 };
122                 warn "Unable to modify item itemnumber=" . $item->itemnumber . ": $@" if $@;
123             }
124         }
125
126     } elsif ( not $item ) {
127         push @messages, { type => 'alert', code => 'hold_placed_at_biblio_level'};
128     } # else the url parameters have been modified and the user is not allowed to continue
129 }
130
131
132 my $today = dt_from_string;
133
134 if ( $startdate ) {
135     $startdate =~ s/^\s+//;
136     $startdate =~ s/\s+$//;
137     $startdate = eval{dt_from_string( $startdate )};
138 }
139 unless ( $startdate ){
140     # changed from delivered range of 10 years-yesterday to 2 days ago-today
141     # Find two days ago for the default shelf pull start date, unless HoldsToPullStartDate sys pref is set.
142     $startdate = $today - DateTime::Duration->new( days => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL );
143 }
144
145 if ( $enddate ) {
146     $enddate =~ s/^\s+//;
147     $enddate =~ s/\s+$//;
148     $enddate = eval{dt_from_string( $enddate )};
149 }
150 unless ( $enddate ) {
151     #similarly: calculate end date with ConfirmFutureHolds (days)
152     $enddate = $today + DateTime::Duration->new( days => C4::Context->preference('ConfirmFutureHolds') || 0 );
153 }
154
155 my @reservedata;
156 my $dbh = C4::Context->dbh;
157 my $sqldatewhere = "";
158 my $startdate_iso = output_pref({ dt => $startdate, dateformat => 'iso', dateonly => 1 });
159 my $enddate_iso   = output_pref({ dt => $enddate, dateformat => 'iso', dateonly => 1 });
160
161 $debug and warn $startdate_iso. "\n" . $enddate_iso;
162
163 my @query_params = ();
164
165 if ($startdate_iso) {
166     $sqldatewhere .= " AND reservedate >= ?";
167     push @query_params, $startdate_iso;
168 }
169 if ($enddate_iso) {
170     $sqldatewhere .= " AND reservedate <= ?";
171     push @query_params, $enddate_iso;
172 }
173
174 my $item_type = C4::Context->preference('item-level_itypes') ? "items.itype" : "biblioitems.itemtype";
175
176 # Bug 21320
177 if ( ! C4::Context->preference('AllowHoldsOnDamagedItems') ) {
178     $sqldatewhere .= " AND damaged = 0";
179 }
180
181 my $strsth =
182     "SELECT min(reservedate) as l_reservedate,
183             reserves.reserve_id,
184             reserves.borrowernumber as borrowernumber,
185
186             GROUP_CONCAT(DISTINCT items.holdingbranch 
187                     ORDER BY items.itemnumber SEPARATOR '|') l_holdingbranch,
188             reserves.biblionumber,
189             reserves.branchcode as l_branch,
190             reserves.itemnumber,
191             items.holdingbranch,
192             items.homebranch,
193             GROUP_CONCAT(DISTINCT $item_type
194                     ORDER BY items.itemnumber SEPARATOR '|') l_item_type,
195             GROUP_CONCAT(DISTINCT items.location 
196                     ORDER BY items.itemnumber SEPARATOR '|') l_location,
197             GROUP_CONCAT(DISTINCT items.itemcallnumber 
198                     ORDER BY items.itemnumber SEPARATOR '|') l_itemcallnumber,
199             GROUP_CONCAT(DISTINCT items.enumchron
200                     ORDER BY items.itemnumber SEPARATOR '|') l_enumchron,
201             GROUP_CONCAT(DISTINCT items.copynumber
202                     ORDER BY items.itemnumber SEPARATOR '|') l_copynumber,
203             GROUP_CONCAT(DISTINCT items.barcode
204                     ORDER BY items.itemnumber SEPARATOR '|') l_barcode,
205             biblio.title,
206             biblio.copyrightdate,
207             biblioitems.publicationyear,
208             biblio.subtitle,
209             biblio.medium,
210             biblio.part_number,
211             biblio.part_name,
212             biblio.author,
213             biblioitems.editionstatement,
214             count(DISTINCT items.itemnumber) as icount,
215             count(DISTINCT reserves.borrowernumber) as rcount,
216             borrowers.firstname,
217             borrowers.surname
218     FROM  reserves
219         LEFT JOIN items ON items.biblionumber=reserves.biblionumber 
220         LEFT JOIN biblio ON reserves.biblionumber=biblio.biblionumber
221         LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
222         LEFT JOIN branchtransfers ON items.itemnumber=branchtransfers.itemnumber
223         LEFT JOIN issues ON items.itemnumber=issues.itemnumber
224         LEFT JOIN borrowers ON reserves.borrowernumber=borrowers.borrowernumber
225         LEFT JOIN circulation_rules ON ( items.itype=circulation_rules.itemtype AND rule_name = 'holdallowed' AND circulation_rules.branchcode IS NULL AND circulation_rules.categorycode IS NULL )
226     WHERE
227     reserves.found IS NULL
228     $sqldatewhere
229     AND (reserves.itemnumber IS NULL OR reserves.itemnumber = items.itemnumber)
230     AND items.itemnumber NOT IN (SELECT itemnumber FROM branchtransfers where datearrived IS NULL)
231     AND items.itemnumber NOT IN (SELECT itemnumber FROM reserves WHERE found IS NOT NULL AND itemnumber IS NOT NULL)
232     AND issues.itemnumber IS NULL
233     AND reserves.priority <> 0 
234     AND reserves.suspend = 0
235     AND notforloan = 0 AND itemlost = 0 AND withdrawn = 0
236     AND ( circulation_rules.rule_value IS NULL OR circulation_rules.rule_value != 0 )
237     ";
238     # GROUP BY reserves.biblionumber allows only items that are not checked out, else multiples occur when 
239     #    multiple patrons have a hold on an item
240 #FIXME "found IS NOT NULL AND itemnumber IS NOT NULL" is just a workaround: see BZ 25726
241
242 if (C4::Context->preference('IndependentBranches')){
243     $strsth .= " AND items.holdingbranch=? ";
244     push @query_params, C4::Context->userenv->{'branch'};
245 }
246 $strsth .= " GROUP BY reserves.biblionumber ORDER BY biblio.title ";
247
248 my $sth = $dbh->prepare($strsth);
249 $sth->execute(@query_params);
250
251 while ( my $data = $sth->fetchrow_hashref ) {
252     push(
253         @reservedata, {
254             reservedate      => $data->{l_reservedate},
255             firstname        => $data->{firstname} || '',
256             surname          => $data->{surname},
257             title            => $data->{title},
258             editionstatement => $data->{editionstatement},
259             subtitle         => $data->{subtitle},
260             medium           => $data->{medium},
261             part_number      => $data->{part_number},
262             part_name        => $data->{part_name},
263             author           => $data->{author},
264             borrowernumber   => $data->{borrowernumber},
265             biblionumber     => $data->{biblionumber},
266             holdingbranches  => [split('\|', $data->{l_holdingbranch})],
267             branch           => $data->{l_branch},
268             itemcallnumber   => [split('\|', $data->{l_itemcallnumber})],
269             enumchron        => [split('\|', $data->{l_enumchron})],
270             copyno           => [split('\|', $data->{l_copynumber})],
271             barcode          => [split('\|', $data->{l_barcode})],
272             count            => $data->{icount},
273             rcount           => $data->{rcount},
274             pullcount        => $data->{icount} <= $data->{rcount} ? $data->{icount} : $data->{rcount},
275             itemTypes        => [split('\|', $data->{l_item_type})],
276             locations        => [split('\|', $data->{l_location})],
277             reserve_id       => $data->{reserve_id},
278             holdingbranch    => $data->{holdingbranch},
279             homebranch       => $data->{homebranch},
280             itemnumber       => $data->{itemnumber},
281             publicationyear  => C4::Context->preference('marcflavour') eq "MARC21" ? $data->{copyrightdate} : $data->{publicationyear},
282         }
283     );
284 }
285 $sth->finish;
286
287 $template->param(
288     todaysdate          => $today,
289     from                => $startdate,
290     to                  => $enddate,
291     reserveloop         => \@reservedata,
292     "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
293     HoldsToPullStartDate => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL,
294     HoldsToPullEndDate  => C4::Context->preference('ConfirmFutureHolds') || 0,
295     messages            => \@messages,
296 );
297
298 output_html_with_http_headers $input, $cookie, $template->output;