Bug 25534: Add reason to pendingreserves.pl
[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         authnotrequired => 0,
50         flagsrequired   => { circulate => "circulate_remaining_permissions" },
51         debug           => 1,
52     }
53 );
54
55 my @messages;
56 if ( $op eq 'cancel_reserve' and $reserve_id ) {
57     my $hold = Koha::Holds->find( $reserve_id );
58     if ( $hold ) {
59         my $cancellation_reason = $input->param('cancellation-reason');
60         $hold->cancel({ cancellation_reason => $cancellation_reason });
61         push @messages, { type => 'message', code => 'hold_cancelled' };
62     }
63 } elsif ( $op =~ m|^mark_as_lost| ) {
64     my $hold = Koha::Holds->find( $reserve_id );
65     die "wrong reserve_id" unless $hold; # This is a bit rude, but we are not supposed to get a wrong reserve_id
66     my $item = $hold->item;
67     if ( $item and C4::Context->preference('CanMarkHoldsToPullAsLost') =~ m|^allow| ) {
68         my $patron = $hold->borrower;
69         C4::Circulation::LostItem( $item->itemnumber, "pendingreserves" );
70         if ( $op eq 'mark_as_lost_and_notify' and C4::Context->preference('CanMarkHoldsToPullAsLost') eq 'allow_and_notify' ) {
71             my $library = $hold->branch;
72             my $letter = C4::Letters::GetPreparedLetter(
73                 module => 'reserves',
74                 letter_code => 'CANCEL_HOLD_ON_LOST',
75                 branchcode => $patron->branchcode,
76                 lang => $patron->lang,
77                 tables => {
78                     branches    => $library->branchcode,
79                     borrowers   => $patron->borrowernumber,
80                     items       => $item->itemnumber,
81                     biblio      => $hold->biblionumber,
82                     biblioitems => $hold->biblionumber,
83                     reserves    => $hold->unblessed,
84                 },
85             );
86             if ( $letter ) {
87                 my $admin_email_address = $library->branchemail || C4::Context->preference('KohaAdminEmailAddress');
88
89                 C4::Letters::EnqueueLetter(
90                     {   letter                 => $letter,
91                         borrowernumber         => $patron->borrowernumber,
92                         message_transport_type => 'email',
93                         from_address           => $admin_email_address,
94                     }
95                 );
96                 unless ( $patron->notice_email_address ) {
97                     push @messages, {type => 'alert', code => 'no_email_address', };
98                 }
99                 push @messages, { type => 'message', code => 'letter_enqueued' };
100             } else {
101                 push @messages, { type => 'error', code => 'no_template_notice' };
102             }
103         }
104         $hold->cancel;
105         if ( $item->homebranch ne $item->holdingbranch ) {
106             C4::Items::ModItemTransfer( $item->itemnumber, $item->holdingbranch, $item->homebranch, 'LostReserve' );
107         }
108
109         if ( my $yaml = C4::Context->preference('UpdateItemWhenLostFromHoldList') ) {
110             $yaml = "$yaml\n\n";  # YAML is anal on ending \n. Surplus does not hurt
111             my $assignments;
112             eval { $assignments = YAML::Load($yaml); };
113             if ($@) {
114                 warn "Unable to parse UpdateItemWhenLostFromHoldList syspref : $@" if $@;
115             }
116             else {
117                 eval {
118                     while ( my ( $f, $v ) = each( %$assignments ) ) {
119                         $item->$f($v);
120                     }
121                     $item->store;
122                 };
123                 warn "Unable to modify item itemnumber=" . $item->itemnumber . ": $@" if $@;
124             }
125         }
126
127     } elsif ( not $item ) {
128         push @messages, { type => 'alert', code => 'hold_placed_at_biblio_level'};
129     } # else the url parameters have been modified and the user is not allowed to continue
130 }
131
132
133 my $today = dt_from_string;
134
135 if ( $startdate ) {
136     $startdate =~ s/^\s+//;
137     $startdate =~ s/\s+$//;
138     $startdate = eval{dt_from_string( $startdate )};
139 }
140 unless ( $startdate ){
141     # changed from delivered range of 10 years-yesterday to 2 days ago-today
142     # Find two days ago for the default shelf pull start date, unless HoldsToPullStartDate sys pref is set.
143     $startdate = $today - DateTime::Duration->new( days => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL );
144 }
145
146 if ( $enddate ) {
147     $enddate =~ s/^\s+//;
148     $enddate =~ s/\s+$//;
149     $enddate = eval{dt_from_string( $enddate )};
150 }
151 unless ( $enddate ) {
152     #similarly: calculate end date with ConfirmFutureHolds (days)
153     $enddate = $today + DateTime::Duration->new( days => C4::Context->preference('ConfirmFutureHolds') || 0 );
154 }
155
156 my @reservedata;
157 my $dbh = C4::Context->dbh;
158 my $sqldatewhere = "";
159 my $startdate_iso = output_pref({ dt => $startdate, dateformat => 'iso', dateonly => 1 });
160 my $enddate_iso   = output_pref({ dt => $enddate, dateformat => 'iso', dateonly => 1 });
161
162 $debug and warn $startdate_iso. "\n" . $enddate_iso;
163
164 my @query_params = ();
165
166 if ($startdate_iso) {
167     $sqldatewhere .= " AND reservedate >= ?";
168     push @query_params, $startdate_iso;
169 }
170 if ($enddate_iso) {
171     $sqldatewhere .= " AND reservedate <= ?";
172     push @query_params, $enddate_iso;
173 }
174
175 my $item_type = C4::Context->preference('item-level_itypes') ? "items.itype" : "biblioitems.itemtype";
176
177 # Bug 21320
178 if ( ! C4::Context->preference('AllowHoldsOnDamagedItems') ) {
179     $sqldatewhere .= " AND damaged = 0";
180 }
181
182 my $strsth =
183     "SELECT min(reservedate) as l_reservedate,
184             reserves.reserve_id,
185             reserves.borrowernumber as borrowernumber,
186
187             GROUP_CONCAT(DISTINCT items.holdingbranch 
188                     ORDER BY items.itemnumber SEPARATOR '|') l_holdingbranch,
189             reserves.biblionumber,
190             reserves.branchcode as l_branch,
191             reserves.itemnumber,
192             items.holdingbranch,
193             items.homebranch,
194             GROUP_CONCAT(DISTINCT $item_type
195                     ORDER BY items.itemnumber SEPARATOR '|') l_item_type,
196             GROUP_CONCAT(DISTINCT items.location 
197                     ORDER BY items.itemnumber SEPARATOR '|') l_location,
198             GROUP_CONCAT(DISTINCT items.itemcallnumber 
199                     ORDER BY items.itemnumber SEPARATOR '|') l_itemcallnumber,
200             GROUP_CONCAT(DISTINCT items.enumchron
201                     ORDER BY items.itemnumber SEPARATOR '|') l_enumchron,
202             GROUP_CONCAT(DISTINCT items.copynumber
203                     ORDER BY items.itemnumber SEPARATOR '|') l_copynumber,
204             biblio.title,
205             biblio.copyrightdate,
206             biblioitems.publicationyear,
207             biblio.subtitle,
208             biblio.medium,
209             biblio.part_number,
210             biblio.part_name,
211             biblio.author,
212             biblioitems.editionstatement,
213             count(DISTINCT items.itemnumber) as icount,
214             count(DISTINCT reserves.borrowernumber) as rcount,
215             borrowers.firstname,
216             borrowers.surname
217     FROM  reserves
218         LEFT JOIN items ON items.biblionumber=reserves.biblionumber 
219         LEFT JOIN biblio ON reserves.biblionumber=biblio.biblionumber
220         LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
221         LEFT JOIN branchtransfers ON items.itemnumber=branchtransfers.itemnumber
222         LEFT JOIN issues ON items.itemnumber=issues.itemnumber
223         LEFT JOIN borrowers ON reserves.borrowernumber=borrowers.borrowernumber
224         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 )
225     WHERE
226     reserves.found IS NULL
227     $sqldatewhere
228     AND (reserves.itemnumber IS NULL OR reserves.itemnumber = items.itemnumber)
229     AND items.itemnumber NOT IN (SELECT itemnumber FROM branchtransfers where datearrived IS NULL)
230     AND items.itemnumber NOT IN (SELECT itemnumber FROM reserves WHERE found IS NOT NULL AND itemnumber IS NOT NULL)
231     AND issues.itemnumber IS NULL
232     AND reserves.priority <> 0 
233     AND reserves.suspend = 0
234     AND notforloan = 0 AND itemlost = 0 AND withdrawn = 0
235     AND ( circulation_rules.rule_value IS NULL OR circulation_rules.rule_value != 0 )
236     ";
237     # GROUP BY reserves.biblionumber allows only items that are not checked out, else multiples occur when 
238     #    multiple patrons have a hold on an item
239 #FIXME "found IS NOT NULL AND itemnumber IS NOT NULL" is just a workaround: see BZ 25726
240
241 if (C4::Context->preference('IndependentBranches')){
242     $strsth .= " AND items.holdingbranch=? ";
243     push @query_params, C4::Context->userenv->{'branch'};
244 }
245 $strsth .= " GROUP BY reserves.biblionumber ORDER BY biblio.title ";
246
247 my $sth = $dbh->prepare($strsth);
248 $sth->execute(@query_params);
249
250 while ( my $data = $sth->fetchrow_hashref ) {
251     push(
252         @reservedata, {
253             reservedate      => $data->{l_reservedate},
254             firstname        => $data->{firstname} || '',
255             surname          => $data->{surname},
256             title            => $data->{title},
257             editionstatement => $data->{editionstatement},
258             subtitle         => $data->{subtitle},
259             medium           => $data->{medium},
260             part_number      => $data->{part_number},
261             part_name        => $data->{part_name},
262             author           => $data->{author},
263             borrowernumber   => $data->{borrowernumber},
264             biblionumber     => $data->{biblionumber},
265             holdingbranches  => [split('\|', $data->{l_holdingbranch})],
266             branch           => $data->{l_branch},
267             itemcallnumber   => [split('\|', $data->{l_itemcallnumber})],
268             enumchron        => [split('\|', $data->{l_enumchron})],
269             copyno           => [split('\|', $data->{l_copynumber})],
270             count            => $data->{icount},
271             rcount           => $data->{rcount},
272             pullcount        => $data->{icount} <= $data->{rcount} ? $data->{icount} : $data->{rcount},
273             itemTypes        => [split('\|', $data->{l_item_type})],
274             locations        => [split('\|', $data->{l_location})],
275             reserve_id       => $data->{reserve_id},
276             holdingbranch    => $data->{holdingbranch},
277             homebranch       => $data->{homebranch},
278             itemnumber       => $data->{itemnumber},
279             publicationyear  => C4::Context->preference('marcflavour') eq "MARC21" ? $data->{copyrightdate} : $data->{publicationyear},
280         }
281     );
282 }
283 $sth->finish;
284
285 $template->param(
286     todaysdate          => $today,
287     from                => $startdate,
288     to                  => $enddate,
289     reserveloop         => \@reservedata,
290     "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
291     HoldsToPullStartDate => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL,
292     HoldsToPullEndDate  => C4::Context->preference('ConfirmFutureHolds') || 0,
293     messages            => \@messages,
294 );
295
296 output_html_with_http_headers $input, $cookie, $template->output;