Bug 26352: Switch from using call() to call_recursive()
[koha.git] / circ / circulation.pl
1 #!/usr/bin/perl
2
3 # script to execute issuing of books
4
5 # Copyright 2000-2002 Katipo Communications
6 # copyright 2010 BibLibre
7 # Copyright 2011 PTFS-Europe Ltd.
8 # Copyright 2012 software.coop and MJ Ray
9 #
10 # This file is part of Koha.
11 #
12 # Koha is free software; you can redistribute it and/or modify it
13 # under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # Koha is distributed in the hope that it will be useful, but
18 # WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with Koha; if not, see <http://www.gnu.org/licenses>.
24
25 # FIXME There are too many calls to Koha::Patrons->find in this script
26
27 use Modern::Perl;
28 use CGI qw ( -utf8 );
29 use DateTime;
30 use DateTime::Duration;
31 use Scalar::Util qw( looks_like_number );
32 use C4::Output qw( output_and_exit_if_error output_and_exit output_html_with_http_headers );
33 use C4::Auth qw( get_session get_template_and_user );
34 use C4::Koha;
35 use C4::Circulation qw( barcodedecode CanBookBeIssued AddIssue );
36 use C4::Utils::DataTables::Members;
37 use C4::Members;
38 use C4::Biblio qw( TransformMarcToKoha );
39 use C4::Search qw( new_record_from_zebra );
40 use C4::Reserves;
41 use Koha::Holds;
42 use C4::Context;
43 use CGI::Session;
44 use Koha::AuthorisedValues;
45 use Koha::CsvProfiles;
46 use Koha::Patrons;
47 use Koha::Patron::Debarments qw( GetDebarments );
48 use Koha::DateUtils qw( dt_from_string output_pref );
49 use Koha::Plugins;
50 use Koha::Database;
51 use Koha::BiblioFrameworks;
52 use Koha::Items;
53 use Koha::Patron::Messages;
54 use Koha::SearchEngine;
55 use Koha::SearchEngine::Search;
56 use Koha::Patron::Modifications;
57
58 use List::MoreUtils qw( uniq );
59
60 #
61 # PARAMETERS READING
62 #
63 my $query = CGI->new;
64
65 my $override_high_holds     = $query->param('override_high_holds');
66 my $override_high_holds_tmp = $query->param('override_high_holds_tmp');
67
68 my $sessionID = $query->cookie("CGISESSID") ;
69 my $session = get_session($sessionID);
70
71 my $barcodes = [];
72 my $barcode =  $query->param('barcode');
73 my $findborrower;
74 my $autoswitched;
75 my $borrowernumber = $query->param('borrowernumber');
76
77 if (C4::Context->preference("AutoSwitchPatron") && $barcode) {
78     my ( $new_barcode ) = Koha::Plugins->call_recursive( 'patron_barcode_transform', $findborrower );
79     if (Koha::Patrons->search( { cardnumber => $new_barcode} )->count() > 0) {
80         $findborrower = $barcode;
81         undef $barcode;
82         undef $borrowernumber;
83         $autoswitched = 1;
84     }
85 }
86 $findborrower ||= $query->param('findborrower') || q{};
87 $findborrower =~ s|,| |g;
88
89 # Barcode given by user could be '0'
90 if ( $barcode || ( defined($barcode) && $barcode eq '0' ) ) {
91     $barcodes = [ $barcode ];
92 } else {
93     my $filefh = $query->upload('uploadfile');
94     if ( $filefh ) {
95         while ( my $content = <$filefh> ) {
96             $content =~ s/[\r\n]*$//g;
97             push @$barcodes, $content if $content;
98         }
99     } elsif ( my $list = $query->param('barcodelist') ) {
100         push @$barcodes, split( /\s\n/, $list );
101         $barcodes = [ map { $_ =~ /^\s*$/ ? () : $_ } @$barcodes ];
102     } else {
103         @$barcodes = $query->multi_param('barcodes');
104     }
105 }
106
107 $barcodes = [ uniq @$barcodes ];
108
109 my $template_name = q|circ/circulation.tt|;
110 my $patron = $borrowernumber ? Koha::Patrons->find( $borrowernumber ) : undef;
111 my $batch = $query->param('batch');
112 my $batch_allowed = 0;
113 if ( $batch && C4::Context->preference('BatchCheckouts') ) {
114     $template_name = q|circ/circulation_batch_checkouts.tt|;
115     my @batch_category_codes = split ',', C4::Context->preference('BatchCheckoutsValidCategories');
116     my $categorycode = $patron->categorycode;
117     if ( $categorycode && grep { $_ eq $categorycode } @batch_category_codes ) {
118         $batch_allowed = 1;
119     } else {
120         $barcodes = [];
121     }
122 }
123
124 my ( $template, $loggedinuser, $cookie ) = get_template_and_user (
125     {
126         template_name   => $template_name,
127         query           => $query,
128         type            => "intranet",
129         flagsrequired   => { circulate => 'circulate_remaining_permissions' },
130     }
131 );
132 my $logged_in_user = Koha::Patrons->find( $loggedinuser );
133
134 my $force_allow_issue = $query->param('forceallow') || 0;
135 if (!C4::Auth::haspermission( C4::Context->userenv->{id} , { circulate => 'force_checkout' } )) {
136     $force_allow_issue = 0;
137 }
138 my $onsite_checkout = $query->param('onsite_checkout');
139
140 if (C4::Context->preference("OnSiteCheckoutAutoCheck") && $onsite_checkout eq "on") {
141     $template->param(onsite_checkout => $onsite_checkout);
142 }
143
144 my @failedrenews = $query->multi_param('failedrenew');    # expected to be itemnumbers
145 our %renew_failed = ();
146 for (@failedrenews) { $renew_failed{$_} = 1; }
147
148 my @failedreturns = $query->multi_param('failedreturn');
149 our %return_failed = ();
150 for (@failedreturns) { $return_failed{$_} = 1; }
151
152 my $searchtype = $query->param('searchtype') || q{contain};
153
154 my $branch = C4::Context->userenv->{'branch'};
155
156 if (C4::Context->preference("DisplayClearScreenButton")) {
157     $template->param(DisplayClearScreenButton => 1);
158 }
159
160 for my $barcode ( @$barcodes ) {
161     $barcode =~ s/^\s*|\s*$//g; # remove leading/trailing whitespace
162     $barcode = barcodedecode( $barcode ) if $barcode;
163 }
164
165 my $stickyduedate  = $query->param('stickyduedate') || $session->param('stickyduedate');
166 my $duedatespec    = $query->param('duedatespec')   || $session->param('stickyduedate');
167 $duedatespec = eval { output_pref( { dt => dt_from_string( $duedatespec ), dateformat => 'iso' }); }
168     if ( $duedatespec );
169 my $restoreduedatespec  = $query->param('restoreduedatespec') || $duedatespec || $session->param('stickyduedate');
170 if ( $restoreduedatespec && $restoreduedatespec eq "highholds_empty" ) {
171     undef $restoreduedatespec;
172 }
173 my $issueconfirmed = $query->param('issueconfirmed');
174 my $cancelreserve  = $query->param('cancelreserve');
175 my $debt_confirmed = $query->param('debt_confirmed') || 0; # Don't show the debt error dialog twice
176 my $charges        = $query->param('charges') || q{};
177
178 # Check if stickyduedate is turned off
179 if ( @$barcodes ) {
180     # was stickyduedate loaded from session?
181     if ( $stickyduedate && ! $query->param("stickyduedate") ) {
182         $session->clear( 'stickyduedate' );
183         $stickyduedate  = $query->param('stickyduedate');
184         $duedatespec    = $query->param('duedatespec');
185     }
186     $session->param('auto_renew', scalar $query->param('auto_renew'));
187 }
188 else {
189     $session->clear('auto_renew');
190 }
191
192 $template->param( auto_renew => $session->param('auto_renew') );
193
194 my ($datedue,$invalidduedate);
195
196 my $duedatespec_allow = C4::Context->preference('SpecifyDueDate');
197 if( $onsite_checkout && !$duedatespec_allow ) {
198     $datedue = output_pref({ dt => dt_from_string, dateonly => 1, dateformat => 'iso' });
199     $datedue .= ' 23:59:00';
200 } elsif( $duedatespec_allow ) {
201     if ( $duedatespec ) {
202         $datedue = eval { dt_from_string( $duedatespec ) };
203         if (! $datedue ) {
204             $invalidduedate = 1;
205             $template->param( IMPOSSIBLE=>1, INVALID_DATE=>$duedatespec );
206         }
207     }
208 }
209
210 my $inprocess = (@$barcodes == 0) ? '' : $query->param('inprocess');
211 if ( @$barcodes == 0 && $charges eq 'yes' ) {
212     $template->param(
213         PAYCHARGES     => 'yes',
214         borrowernumber => $borrowernumber
215     );
216 }
217
218 #
219 # STEP 2 : FIND BORROWER
220 # if there is a list of find borrowers....
221 #
222 my $message;
223 if ($findborrower) {
224     ( $findborrower ) = Koha::Plugins->call_recursive( 'patron_barcode_transform', $findborrower );
225     my $patron = Koha::Patrons->find( { cardnumber => $findborrower } );
226     if ( $patron ) {
227         $borrowernumber = $patron->borrowernumber;
228     } else {
229         my $dt_params = { iDisplayLength => -1 };
230         my $results = C4::Utils::DataTables::Members::search(
231             {
232                 searchmember => $findborrower,
233                 searchtype   => $searchtype,
234                 dt_params    => $dt_params,
235             }
236         );
237         my $borrowers = $results->{patrons};
238         if ( scalar @$borrowers == 1 ) {
239             $borrowernumber = $borrowers->[0]->{borrowernumber};
240             $query->param( 'borrowernumber', $borrowernumber );
241             $query->param( 'barcode',           '' );
242         } elsif ( @$borrowers ) {
243             $template->param( borrowers => $borrowers );
244         } else {
245             $query->param( 'findborrower', '' );
246             $message = "'$findborrower'";
247         }
248     }
249 }
250
251 # get the borrower information.....
252 my $balance = 0;
253 $patron ||= Koha::Patrons->find( $borrowernumber ) if $borrowernumber;
254 if ($patron) {
255
256     $template->param( borrowernumber => $patron->borrowernumber );
257     output_and_exit_if_error( $query, $cookie, $template, { module => 'members', logged_in_user => $logged_in_user, current_patron => $patron } );
258
259     my $overdues = $patron->get_overdues;
260     my $issues = $patron->checkouts;
261     $balance = $patron->account->balance;
262
263
264     # if the expiry date is before today ie they have expired
265     if ( $patron->is_expired ) {
266         #borrowercard expired, no issues
267         $template->param(
268             noissues => ($force_allow_issue) ? 0 : "1",
269             forceallow => $force_allow_issue,
270             expired => "1",
271         );
272     }
273     # check for NotifyBorrowerDeparture
274     elsif ( $patron->is_going_to_expire ) {
275         # borrower card soon to expire warn librarian
276         $template->param( "warndeparture" => $patron->dateexpiry ,
277                         );
278         if (C4::Context->preference('ReturnBeforeExpiry')){
279             $template->param("returnbeforeexpiry" => 1);
280         }
281     }
282     $template->param(
283         overduecount => $overdues->count,
284         issuecount   => $issues->count,
285         finetotal    => $balance,
286     );
287
288     if ( $patron and $patron->is_debarred ) {
289         $template->param(
290             'userdebarred'    => $patron->debarred,
291             'debarredcomment' => $patron->debarredcomment,
292         );
293
294         if ( $patron->debarred ne "9999-12-31" ) {
295             $template->param( 'userdebarreddate' => $patron->debarred );
296         }
297     }
298
299     # Calculate and display patron's age
300     if ( !$patron->is_valid_age ) {
301         $template->param( age_limitations => 1 );
302         $template->param( age_low => $patron->category->dateofbirthrequired );
303         $template->param( age_high => $patron->category->upperagelimit );
304     }
305
306 }
307
308 #
309 # STEP 3 : ISSUING
310 #
311 #
312 if (@$barcodes) {
313   my $checkout_infos;
314   for my $barcode ( @$barcodes ) {
315
316     my $template_params = {
317         barcode         => $barcode,
318         onsite_checkout => $onsite_checkout,
319     };
320
321     # always check for blockers on issuing
322     my ( $error, $question, $alerts, $messages ) = CanBookBeIssued(
323         $patron,
324         $barcode, $datedue,
325         $inprocess,
326         undef,
327         {
328             onsite_checkout     => $onsite_checkout,
329             override_high_holds => $override_high_holds || $override_high_holds_tmp || 0,
330         }
331     );
332
333     my $blocker = $invalidduedate ? 1 : 0;
334
335     $template_params->{alert} = $alerts;
336     $template_params->{messages} = $messages;
337
338     my $item = Koha::Items->find({ barcode => $barcode });
339
340     my $biblio;
341     if ( $item ) {
342         $biblio = $item->biblio;
343     }
344
345     # Fix for bug 7494: optional checkout-time fallback search for a book
346
347     if ( $error->{'UNKNOWN_BARCODE'}
348         && C4::Context->preference("itemBarcodeFallbackSearch")
349         && not $batch
350     )
351     {
352      $template_params->{FALLBACK} = 1;
353
354         my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
355         my $query = "kw=" . $barcode;
356         my ( $searcherror, $results, $total_hits ) = $searcher->simple_search_compat($query, 0, 10);
357
358         # if multiple hits, offer options to librarian
359         if ( $total_hits > 0 ) {
360             my @barcodes;
361             foreach my $hit ( @{$results} ) {
362                 my $chosen = # Maybe easier to retrieve the itemnumber from $hit?
363                   TransformMarcToKoha( C4::Search::new_record_from_zebra('biblioserver',$hit) );
364
365                 # offer all barcodes individually
366                 if ( $chosen->{barcode} ) {
367                     push @barcodes, sort split(/\s*\|\s*/, $chosen->{barcode});
368                 }
369             }
370             my $items = Koha::Items->search({ barcode => {-in => \@barcodes}});
371             $template_params->{options} = $items;
372         }
373     }
374
375     if ( $error->{DEBT_GUARANTORS} ) {
376         $template_params->{DEBT_GUARANTORS} = $error->{DEBT_GUARANTORS};
377         $template_params->{IMPOSSIBLE} = 1;
378         $blocker = 1;
379     }
380
381     if ( $error->{UNKNOWN_BARCODE} or not $onsite_checkout or not C4::Context->preference("OnSiteCheckoutsForce") ) {
382         delete $question->{'DEBT'} if ($debt_confirmed);
383         foreach my $impossible ( keys %$error ) {
384             $template_params->{$impossible} = $$error{$impossible};
385             $template_params->{IMPOSSIBLE} = 1;
386             $blocker = 1;
387         }
388     }
389
390     if( $item and ( !$blocker or $force_allow_issue ) ){
391         my $confirm_required = 0;
392         unless($issueconfirmed){
393             #  Get the item title for more information
394             my $materials = $item->materials;
395             my $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({ frameworkcode => $biblio->frameworkcode, kohafield => 'items.materials', authorised_value => $materials });
396             $materials = $descriptions->{lib} // $materials;
397             $template_params->{ADDITIONAL_MATERIALS} = $materials;
398             $template_params->{itemhomebranch} = $item->homebranch;
399
400             # pass needsconfirmation to template if issuing is possible and user hasn't yet confirmed.
401             foreach my $needsconfirmation ( keys %$question ) {
402                 $template_params->{$needsconfirmation} = $$question{$needsconfirmation};
403                 $template_params->{getTitleMessageIteminfo} = $biblio->title;
404                 $template_params->{getBarcodeMessageIteminfo} = $item->barcode;
405                 $template_params->{NEEDSCONFIRMATION} = 1;
406                 $confirm_required = 1;
407             }
408         }
409         unless($confirm_required) {
410             my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED};
411             my $issue = AddIssue( $patron->unblessed, $barcode, $datedue, $cancelreserve, undef, undef, { onsite_checkout => $onsite_checkout, auto_renew => $session->param('auto_renew'), switch_onsite_checkout => $switch_onsite_checkout, } );
412             $template_params->{issue} = $issue;
413             $session->clear('auto_renew');
414             $inprocess = 1;
415         }
416     }
417
418     if ($question->{RESERVE_WAITING} or $question->{RESERVED} or $question->{TRANSFERRED} or $question->{PROCESSING}){
419         $template->param(
420             reserveborrowernumber => $question->{'resborrowernumber'},
421             reserve_id => $question->{reserve_id},
422         );
423     }
424
425
426     # FIXME If the issue is confirmed, we launch another time checkouts->count, now display the issue count after issue
427     $patron = Koha::Patrons->find( $borrowernumber );
428     $template_params->{issuecount} = $patron->checkouts->count;
429
430     if ( $item ) {
431         $template_params->{item} = $item;
432         $template_params->{biblio} = $biblio;
433         $template_params->{itembiblionumber} = $biblio->biblionumber;
434     }
435     push @$checkout_infos, $template_params;
436   }
437   unless ( $batch ) {
438     $template->param( %{$checkout_infos->[0]} );
439     $template->param( barcode => $barcodes->[0] );
440   } else {
441     my $confirmation_needed = grep { $_->{NEEDSCONFIRMATION} } @$checkout_infos;
442     $template->param(
443         checkout_infos => $checkout_infos,
444         confirmation_needed => $confirmation_needed,
445     );
446   }
447 }
448
449 ##################################################################################
450 # BUILD HTML
451 # show all reserves of this borrower, and the position of the reservation ....
452 if ($patron) {
453     my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } ); # FIXME must be Koha::Patron->holds
454     my $waiting_holds = $holds->waiting;
455     $template->param(
456         holds_count  => $holds->count(),
457         WaitingHolds => $waiting_holds,
458     );
459 }
460
461 if ( $patron ) {
462     my $noissues;
463     if ( $patron->gonenoaddress ) {
464         $template->param( gonenoaddress => 1 );
465         $noissues = 1;
466     }
467     if ( $patron->lost ) {
468         $template->param( lost=> 1 );
469         $noissues = 1;
470     }
471     if ( $patron->is_debarred ) {
472         $template->param( is_debarred=> 1 );
473         $noissues = 1;
474     }
475     my $account = $patron->account;
476     if( ( my $owing = $account->non_issues_charges ) > 0 ) {
477         my $noissuescharge = C4::Context->preference("noissuescharge") || 5; # FIXME If noissuescharge == 0 then 5, why??
478         $noissues ||= ( not C4::Context->preference("AllowFineOverride") and ( $owing > $noissuescharge ) );
479         $template->param(
480             charges => 1,
481             chargesamount => $owing,
482         )
483     } elsif ( $balance < 0 ) {
484         $template->param(
485             credits => 1,
486             creditsamount => -$balance,
487         );
488     }
489
490     # Check the debt of this patrons guarantors *and* the guarantees of those guarantors
491     my $no_issues_charge_guarantors = C4::Context->preference("NoIssuesChargeGuarantorsWithGuarantees");
492     if ( $no_issues_charge_guarantors ) {
493         my $guarantors_non_issues_charges += $patron->relationships_debt({ include_guarantors => 1, only_this_guarantor => 0, include_this_patron => 1 });
494
495         if ( $guarantors_non_issues_charges > $no_issues_charge_guarantors ) {
496             $template->param(
497                 charges_guarantors_guarantees => $guarantors_non_issues_charges
498             );
499             $noissues = 1 unless C4::Context->preference("allowfineoverride");
500         }
501     }
502
503     my $no_issues_charge_guarantees = C4::Context->preference("NoIssuesChargeGuarantees");
504     $no_issues_charge_guarantees = undef unless looks_like_number( $no_issues_charge_guarantees );
505     if ( defined $no_issues_charge_guarantees ) {
506         my $guarantees_non_issues_charges = 0;
507         my $guarantees = $patron->guarantee_relationships->guarantees;
508         while ( my $g = $guarantees->next ) {
509             $guarantees_non_issues_charges += $g->account->non_issues_charges;
510         }
511         if ( $guarantees_non_issues_charges > $no_issues_charge_guarantees ) {
512             $template->param(
513                 charges_guarantees    => 1,
514                 chargesamount_guarantees => $guarantees_non_issues_charges,
515             );
516             $noissues = 1 unless C4::Context->preference("allowfineoverride");
517         }
518     }
519
520     if ( $patron->has_overdues ) {
521         $template->param( odues => 1 );
522     }
523
524     if ( $patron->borrowernotes ) {
525         my $borrowernotes = $patron->borrowernotes;
526         $borrowernotes =~ s#\n#<br />#g;
527         $template->param(
528             notes =>1,
529             notesmsg => $borrowernotes,
530         )
531     }
532
533     if ( $noissues ) {
534         $template->param(
535             noissues => ($force_allow_issue) ? 0 : 'true',
536             forceallow => $force_allow_issue,
537         );
538     }
539 }
540
541 my $patron_messages = Koha::Patron::Messages->search(
542     {
543         'me.borrowernumber' => $borrowernumber,
544     },
545     {
546        join => 'manager',
547        '+select' => ['manager.surname', 'manager.firstname' ],
548        '+as' => ['manager_surname', 'manager_firstname'],
549     }
550 );
551
552 my $fast_cataloging = 0;
553 if ( Koha::BiblioFrameworks->find('FA') ) {
554     $fast_cataloging = 1 
555 }
556
557 my $view = $batch
558     ?'batch_checkout_view'
559     : 'circview';
560
561 my @relatives;
562 if ( $patron ) {
563     if ( my @guarantors = $patron->guarantor_relationships()->guarantors() ) {
564         push( @relatives, $_->id ) for @guarantors;
565         push( @relatives, $_->id ) for $patron->siblings();
566     } else {
567         push( @relatives, $_->id ) for $patron->guarantee_relationships()->guarantees();
568     }
569 }
570 my $relatives_issues_count =
571   Koha::Database->new()->schema()->resultset('Issue')
572   ->count( { borrowernumber => \@relatives } );
573
574 if ( $patron ) {
575     my $av = Koha::AuthorisedValues->search({ category => 'ROADTYPE', authorised_value => $patron->streettype });
576     my $roadtype = $av->count ? $av->next->lib : '';
577     $template->param(
578         roadtype          => $roadtype,
579         patron            => $patron,
580         categoryname      => $patron->category->description,
581         expiry            => $patron->dateexpiry,
582     );
583 }
584
585 # Restore date if changed by holds and/or save stickyduedate to session
586 if ($restoreduedatespec || $stickyduedate) {
587     $duedatespec = $restoreduedatespec || $duedatespec;
588
589     if ($stickyduedate) {
590         $session->param( 'stickyduedate', $duedatespec );
591     }
592 } elsif (defined($duedatespec) && !defined($restoreduedatespec)) {
593     undef $duedatespec;
594 }
595
596 $template->param(
597     patron_messages           => $patron_messages,
598     borrowernumber    => $borrowernumber,
599     branch            => $branch,
600     was_renewed       => scalar $query->param('was_renewed') ? 1 : 0,
601     barcodes          => $barcodes,
602     stickyduedate     => $stickyduedate,
603     duedatespec       => $duedatespec,
604     restoreduedatespec => $restoreduedatespec,
605     message           => $message,
606     totaldue          => sprintf('%.2f', $balance), # FIXME not used in template?
607     inprocess         => $inprocess,
608     $view             => 1,
609     batch_allowed     => $batch_allowed,
610     batch             => $batch,
611     AudioAlerts           => C4::Context->preference("AudioAlerts"),
612     fast_cataloging   => $fast_cataloging,
613     CircAutoPrintQuickSlip   => C4::Context->preference("CircAutoPrintQuickSlip"),
614     RoutingSerials => C4::Context->preference('RoutingSerials'),
615     relatives_issues_count => $relatives_issues_count,
616     relatives_borrowernumbers => \@relatives,
617 );
618
619
620 if ( C4::Context->preference("ExportCircHistory") ) {
621     $template->param(csv_profiles => [ Koha::CsvProfiles->search({ type => 'marc' }) ]);
622 }
623
624 my $has_modifications = Koha::Patron::Modifications->search( { borrowernumber => $borrowernumber } )->count;
625 $template->param(
626     debt_confirmed            => $debt_confirmed,
627     SpecifyDueDate            => $duedatespec_allow,
628     PatronAutoComplete      => C4::Context->preference("PatronAutoComplete"),
629     debarments                => scalar GetDebarments({ borrowernumber => $borrowernumber }),
630     todaysdate                => output_pref( { dt => dt_from_string()->set(hour => 23)->set(minute => 59), dateformat => 'sql' } ),
631     has_modifications         => $has_modifications,
632     override_high_holds       => $override_high_holds,
633     nopermission              => scalar $query->param('nopermission'),
634     autoswitched              => $autoswitched,
635     logged_in_user            => $logged_in_user,
636 );
637
638 output_html_with_http_headers $query, $cookie, $template->output;