SIP option to use SIP date format for due dates
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / SIP.pm
1 #
2 # ILS.pm: Test ILS interface module
3 #
4
5 package OpenILS::SIP;
6 use warnings; use strict;
7
8 use Sys::Syslog qw(syslog);
9 use Time::HiRes q/time/;
10
11 use OpenILS::SIP::Item;
12 use OpenILS::SIP::Patron;
13 use OpenILS::SIP::Transaction;
14 use OpenILS::SIP::Transaction::Checkout;
15 use OpenILS::SIP::Transaction::Checkin;
16 use OpenILS::SIP::Transaction::Renew;
17 use OpenILS::SIP::Transaction::FeePayment;
18
19 use OpenSRF::System;
20 use OpenSRF::AppSession;
21 use OpenILS::Utils::Fieldmapper;
22 use OpenSRF::Utils::SettingsClient;
23 use OpenILS::Application::AppUtils;
24 use OpenSRF::Utils qw/:datetime/;
25 use DateTime::Format::ISO8601;
26 use Encode;
27 use Unicode::Normalize;
28 use Sip::Constants qw(SIP_DATETIME);
29
30 my $U = 'OpenILS::Application::AppUtils';
31
32 my $editor;
33 my $config;
34 my $login_account;
35 my $target_encoding;    # FIXME: this is configured at the institution level. 
36
37 use Digest::MD5 qw(md5_hex);
38
39 sub new {
40     my ($class, $institution, $login) = @_;
41     my $type = ref($class) || $class;
42     my $self = {};
43
44     $self->{login} = $login_account = $login;
45
46     $config = $institution;
47     syslog("LOG_DEBUG", "OILS: new ILS '%s'", $institution->{id});
48     $self->{institution} = $institution;
49
50     my $bsconfig     = $institution->{implementation_config}->{bootstrap};
51     $target_encoding = $institution->{implementation_config}->{encoding} || 'ascii';
52
53     syslog('LOG_DEBUG', "OILS: loading bootstrap config: $bsconfig");
54
55     # ingress will persist throughout
56     OpenSRF::AppSession->ingress('sip2');
57     
58     local $/ = "\n";    # why?
59     OpenSRF::System->bootstrap_client(config_file => $bsconfig);
60     syslog('LOG_DEBUG', "OILS: bootstrap loaded..");
61
62     $self->{osrf_config} = OpenSRF::Utils::SettingsClient->new;
63
64     Fieldmapper->import($self->{osrf_config}->config_value('IDL'));
65
66     bless( $self, $type );
67
68     return undef unless 
69         $self->login( $login->{id}, $login->{password} );
70
71     return $self;
72 }
73
74 sub fetch_session {
75     my $self = shift;
76
77     my $ses = $U->simplereq( 
78         'open-ils.auth',
79         'open-ils.auth.session.retrieve',  $self->{authtoken});
80
81     return undef if $U->event_code($ses); # auth timed out
82     return $self->{login_session} = $ses;
83 }
84
85 sub verify_session {
86     my $self = shift;
87
88     return 1 if $self->fetch_session;
89
90     syslog('LOG_INFO', "OILS: Logging back after session timeout as user ".$self->{login}->{id});
91     return $self->login( $self->{login}->{id}, $self->{login}->{password} );
92 }
93
94 sub editor {
95     return $editor = make_editor();
96 }
97
98 sub config {
99     return $config;
100 }
101 sub login_account {
102     return $login_account;
103 }
104
105 sub get_option_value {
106     my($self, $option) = @_;
107     my $ops = $config->{implementation_config}->{options}->{option};
108     $ops = [$ops] unless ref $ops eq 'ARRAY';
109     my @vals = grep { $_->{name} eq $option } @$ops;
110     return @vals ? $vals[0]->{value} : undef;
111 }
112
113
114 # Creates the global editor object
115 my $cstore_init = 1; # call init on first use
116 sub make_editor {
117     OpenILS::Utils::CStoreEditor::init() if $cstore_init;
118     $cstore_init = 0;
119     return OpenILS::Utils::CStoreEditor->new;
120 }
121
122 =head2 clean_text(scalar)
123
124 Evergreen uses the UTF8 encoding for everything from the database up. Perl
125 doesn't know this, however, so we have to convince it to treat our UTF8 strings
126 as UTF8 strings. This may enable OpenNCIP to correctly calculate the checksums
127 for UTF8 text for SIP clients that support such modern options.
128
129 The target encoding is set in the <encoding> element of the SIPServer.pm
130 configuration file.
131
132 =cut
133
134 sub clean_text {
135     my $text = shift || '';
136
137     # Convert our incoming UTF8 data into Perl's internal string format
138
139     # Also convert to Normalization Form D, as the ASCII, iso-8859-1,
140     # and latin-1 encodings (at least) require this to substitute
141     # characters rather than simply returning a string truncated
142     # after the first non-ASCII character
143     $text = NFD(decode_utf8($text));
144
145     if ($target_encoding eq 'ascii') {
146
147         # Try to maintain a reasonable version of the content by
148         # stripping diacritics from the text, given that the SIP client
149         # wants just plain ASCII. This is the base requirement according
150         # to the SIP2 specification.
151
152         # Stripping the combining characters converts ""béè♁ts"
153         # into "bee?ts" instead of "b???ts" - better, eh?
154         $text =~ s/\pM+//og;
155     }
156
157     # Characters that cannot be represented in the target encoding will
158     # generally be replaced with a question mark (?) character.
159     $text = encode($target_encoding, $text);
160
161     return $text;
162 }
163
164 my %org_sn_cache;
165 sub shortname_from_id {
166     my $id = shift or return;
167     return $id->shortname if ref $id;
168     return $org_sn_cache{$id} if $org_sn_cache{$id};
169     return $org_sn_cache{$id} = editor()->retrieve_actor_org_unit($id)->shortname;
170 }
171 sub patron_barcode_from_id {
172     my $id = shift or return;
173     return editor()->search_actor_card({ usr => $id, active => 't' })->[0]->barcode;
174 }
175
176 sub format_date {
177     my $class = shift;
178     my $date = shift;
179     my $type = shift || 'dob';
180
181     return "" unless $date;
182
183     my $dt = DateTime::Format::ISO8601->new->
184         parse_datetime(OpenSRF::Utils::cleanse_ISO8601($date));
185     my @time = localtime($dt->epoch);
186
187     my $year   = $time[5]+1900;
188     my $mon    = $time[4]+1;
189     my $day    = $time[3];
190     my $hour   = $time[2];
191     my $minute = $time[1];
192     my $second = $time[0];
193   
194     $date = sprintf("%04d%02d%02d", $year, $mon, $day);
195
196     # Due dates need hyphen separators and time of day as well
197     if ($type eq 'due') {
198
199         my $use_sdf = $class->get_option_value('use_sip_date_format') | '';
200
201         if ($use_sdf =~ /true/i) {
202             $date = $dt->strftime(SIP_DATETIME);
203
204         } else {
205             $date = sprintf("%04d-%02d-%02d %02d:%02d:%02d", 
206                 $year, $mon, $day, $hour, $minute, $second);
207         }
208     }
209
210     syslog('LOG_DEBUG', "OILS: formatted date [type=$type]: $date");
211     return $date;
212 }
213
214
215
216 sub login {
217     my( $self, $username, $password ) = @_;
218     syslog('LOG_DEBUG', "OILS: Logging in with username $username");
219
220     my $seed = $U->simplereq( 
221         'open-ils.auth',
222         'open-ils.auth.authenticate.init', $username );
223
224     my $response = $U->simplereq(
225         'open-ils.auth', 
226         'open-ils.auth.authenticate.complete', 
227         {    
228             username => $username, 
229             password => md5_hex($seed . md5_hex($password)), 
230             type     => 'opac',
231         }
232     );
233
234     if( my $code = $U->event_code($response) ) {
235         my $txt = $response->{textcode};
236         syslog('LOG_WARNING', "OILS: Login failed for $username.  $txt:$code");
237         return undef;
238     }
239
240     my $key = $response->{payload}->{authtoken};
241     syslog('LOG_INFO', "OILS: Login succeeded for $username : authkey = $key");
242
243     $self->fetch_session; # to cache the login
244
245     return $self->{authtoken} = $key;
246 }
247
248 #
249 # find_patron($barcode);
250 # find_patron(barcode => $barcode);   # same as above
251 # find_patron(usr => $id);
252
253 sub find_patron {
254     my $self = shift;
255     my $key  =  (@_ > 1) ? shift : 'barcode';  # if we have multiple args, the first is the key index (default barcode)
256     my $patron_id = shift;
257
258     return OpenILS::SIP::Patron->new($key => $patron_id, authtoken => $self->{authtoken}, @_);
259 }
260
261
262 sub find_item {
263     my $self = shift;
264     return OpenILS::SIP::Item->new(@_);
265 }
266
267
268 sub institution {
269     my $self = shift;
270     return $self->{institution}->{id};  # consider making this return the whole institution
271 }
272
273 sub institution_id {
274     my $self = shift;
275     return $self->{institution}->{id};  # then use this for just the ID
276 }
277
278 sub supports {
279     my ($self, $op) = @_;
280     my ($i) = grep { $_->{name} eq $op }  
281         @{$config->{implementation_config}->{supports}->{item}};
282     return to_bool($i->{value});
283 }
284
285 sub check_inst_id {
286     my ($self, $id, $whence) = @_;
287     if ($id ne $self->{institution}->{id}) {
288         syslog("LOG_WARNING", "OILS: %s: received institution '%s', expected '%s'", $whence, $id, $self->{institution}->{id});
289         # Just an FYI check, we don't expect the user to change location from that in SIPconfig.xml
290     }
291 }
292
293
294 sub to_bool {
295     my $bool = shift;
296     # If it's defined, and matches a true sort of string, or is
297     # a non-zero number, then it's true.
298     defined($bool) or return;                   # false
299     ($bool =~ /true|y|yes/i) and return 1;      # true
300     return ($bool =~ /^\d+$/ and $bool != 0);   # true for non-zero numbers, false otherwise
301 }
302
303 sub checkout_ok {
304     return to_bool($config->{policy}->{checkout});
305 }
306
307 sub checkin_ok {
308     return to_bool($config->{policy}->{checkin});
309 }
310
311 sub renew_ok {
312     return to_bool($config->{policy}->{renewal});
313 }
314
315 sub status_update_ok {
316     return to_bool($config->{policy}->{status_update});
317 }
318
319 sub offline_ok {
320     return to_bool($config->{policy}->{offline});
321 }
322
323
324
325 ##
326 ## Checkout(patron_id, item_id, sc_renew, fee_ack):
327 ##    patron_id & item_id are the identifiers send by the terminal
328 ##    sc_renew is the renewal policy configured on the terminal
329 ## returns a status opject that can be queried for the various bits
330 ## of information that the protocol (SIP or NCIP) needs to generate
331 ## the response.
332 ##    fee_ack is the fee_acknowledged field (BO) sent from the sc
333 ## when doing chargeable loans.
334 ##
335
336 sub checkout {
337     my ($self, $patron_id, $item_id, $sc_renew, $fee_ack) = @_;
338     # In order to allow renewals the selfcheck AND the config have to say they are allowed
339     $sc_renew = (chr($sc_renew) eq 'Y' && $self->renew_ok());
340
341     $self->verify_session;
342
343     syslog('LOG_DEBUG', "OILS: OpenILS::Checkout attempt: patron=$patron_id, item=$item_id");
344
345     my $xact   = OpenILS::SIP::Transaction::Checkout->new( authtoken => $self->{authtoken} );
346     my $patron = $self->find_patron($patron_id);
347     my $item   = $self->find_item($item_id);
348
349     $xact->patron($patron);
350     $xact->item($item);
351
352     if (!$patron) {
353         $xact->screen_msg("Invalid Patron Barcode '$patron_id'");
354         return $xact;
355     }
356
357     if (!$patron->charge_ok) {
358         $xact->screen_msg("Patron Blocked");
359         return $xact;
360     }
361
362     if( !$item ) {
363         $xact->screen_msg("Invalid Item Barcode: '$item_id'");
364         return $xact;
365     }
366
367     syslog('LOG_DEBUG', "OILS: OpenILS::Checkout data loaded OK, checking out...");
368
369     if ($item->{patron} && ($item->{patron} eq $patron_id)) {
370         $xact->renew_ok(1); # So that accept/reject responses have the correct value later
371         if($sc_renew) {
372             syslog('LOG_INFO', "OILS: OpenILS::Checkout data loaded OK, doing renew...");
373         } else {
374             syslog('LOG_INFO', "OILS: OpenILS::Checkout appears to be renew, but renewal disallowed...");
375             $xact->screen_msg("Renewals not permitted");
376             $xact->ok(0);
377             return $xact; # Don't attempt later
378         }
379     } elsif ($item->{patron} && ($item->{patron} ne $patron_id)) {
380         # I can't deal with this right now
381         # XXX check in then check out?
382         $xact->screen_msg("Item checked out to another patron");
383         $xact->ok(0);
384         return $xact; # Don't wipe out the screen message later
385     } else {
386         $sc_renew = 0;
387     } 
388
389     # Check for fee and $fee_ack. If there is a fee, and $fee_ack
390     # is 'Y', we proceed, otherwise we reject the checkout.
391     if ($item->fee > 0.0) {
392         $xact->fee_amount($item->fee);
393         $xact->sip_fee_type($item->sip_fee_type);
394         $xact->sip_currency($item->fee_currency);
395         if ($fee_ack && $fee_ack eq 'Y') {
396             $xact->fee_ack(1);
397         } else {
398             $xact->screen_msg('Fee required');
399             $xact->ok(0);
400             return $xact;
401         }
402     }
403
404     $xact->do_checkout($sc_renew);
405     $xact->desensitize(!$item->magnetic);
406
407     if( $xact->ok ) {
408         #editor()->commit;
409         syslog("LOG_DEBUG", "OILS: OpenILS::Checkout: " .
410             "patron %s checkout %s succeeded", $patron_id, $item_id);
411     } else {
412         #editor()->xact_rollback;
413         syslog("LOG_DEBUG", "OILS: OpenILS::Checkout: " .
414             "patron %s checkout %s FAILED, rolling back xact...", $patron_id, $item_id);
415     }
416
417     return $xact;
418 }
419
420
421 sub checkin {
422     my ($self, $item_id, $inst_id, $trans_date, $return_date,
423         $current_loc, $item_props, $cancel) = @_;
424
425     my $start_time = time();
426
427     $self->verify_session;
428
429     syslog('LOG_DEBUG', "OILS: OpenILS::Checkin of item=$item_id (to $inst_id)");
430     
431     my $xact = OpenILS::SIP::Transaction::Checkin->new(authtoken => $self->{authtoken});
432     my $item = OpenILS::SIP::Item->new($item_id);
433
434     unless ( $xact->item($item) ) {
435         $xact->ok(0);
436         # $circ->alert(1); $circ->alert_type(99);
437         $xact->screen_msg("Invalid Item Barcode: '$item_id'");
438         syslog('LOG_INFO', "OILS: Checkin failed.  " . $xact->screen_msg() );
439         return $xact;
440     }
441
442     $xact->do_checkin( $self, $inst_id, $trans_date, $return_date, $current_loc, $item_props );
443     
444     if ($xact->ok) {
445         $xact->patron($self->find_patron(usr => $xact->{circ_user_id}, slim_user => 1)) if $xact->{circ_user_id};
446         delete $item->{patron};
447         delete $item->{due_date};
448         syslog('LOG_INFO', "OILS: Checkin succeeded");
449     } else {
450         syslog('LOG_WARNING', "OILS: Checkin failed");
451     }
452
453     syslog('LOG_INFO', "OILS: SIP Checkin request took %0.3f seconds", (time() - $start_time));
454     return $xact;
455 }
456
457 ## If the ILS caches patron information, this lets it free it up.
458 ## Also, this could be used for centrally logging session duration.
459 ## We don't do anything with it.
460 sub end_patron_session {
461     my ($self, $patron_id) = @_;
462     return (1, 'Thank you!', '');
463 }
464
465
466 sub pay_fee {
467     my ($self, $patron_id, $patron_pwd, $fee_amt, $fee_type,
468     $pay_type, $fee_id, $trans_id, $currency) = @_;
469
470     $self->verify_session;
471
472     my $xact = OpenILS::SIP::Transaction::FeePayment->new(authtoken => $self->{authtoken});
473     my $patron = $self->find_patron($patron_id);
474
475     if (!$patron) {
476         $xact->screen_msg("Invalid Patron Barcode '$patron_id'");
477         $xact->ok(0);
478         return $xact;
479     }
480
481     $xact->patron($patron);
482     $xact->sip_currency($currency);
483     $xact->fee_amount($fee_amt);
484     $xact->sip_fee_type($fee_type);
485     $xact->transaction_id($trans_id);
486     $xact->fee_id($fee_id);
487     $xact->sip_payment_type($pay_type);
488     # We don't presently use this, but we might in the future.
489     $xact->patron_password($patron_pwd);
490
491     $xact->do_fee_payment();
492
493     return $xact;
494 }
495
496 #sub add_hold {
497 #    my ($self, $patron_id, $patron_pwd, $item_id, $title_id,
498 #    $expiry_date, $pickup_location, $hold_type, $fee_ack) = @_;
499 #    my ($patron, $item);
500 #    my $hold;
501 #    my $trans;
502 #
503 #
504 #    $trans = new ILS::Transaction::Hold;
505 #
506 #    # BEGIN TRANSACTION
507 #    $patron = new ILS::Patron $patron_id;
508 #    if (!$patron
509 #    || (defined($patron_pwd) && !$patron->check_password($patron_pwd))) {
510 #    $trans->screen_msg("Invalid Patron.");
511 #
512 #    return $trans;
513 #    }
514 #
515 #    $item = new ILS::Item ($item_id || $title_id);
516 #    if (!$item) {
517 #    $trans->screen_msg("No such item.");
518 #
519 #    # END TRANSACTION (conditionally)
520 #    return $trans;
521 #    } elsif ($item->fee && ($fee_ack ne 'Y')) {
522 #    $trans->screen_msg = "Fee required to place hold.";
523 #
524 #    # END TRANSACTION (conditionally)
525 #    return $trans;
526 #    }
527 #
528 #    $hold = {
529 #    item_id         => $item->id,
530 #    patron_id       => $patron->id,
531 #    expiration_date => $expiry_date,
532 #    pickup_location => $pickup_location,
533 #    hold_type       => $hold_type,
534 #    };
535 #
536 #    $trans->ok(1);
537 #    $trans->patron($patron);
538 #    $trans->item($item);
539 #    $trans->pickup_location($pickup_location);
540 #
541 #    push(@{$item->hold_queue}, $hold);
542 #    push(@{$patron->{hold_items}}, $hold);
543 #
544 #
545 #    # END TRANSACTION
546 #    return $trans;
547 #}
548 #
549 #sub cancel_hold {
550 #    my ($self, $patron_id, $patron_pwd, $item_id, $title_id) = @_;
551 #    my ($patron, $item, $hold);
552 #    my $trans;
553 #
554 #    $trans = new ILS::Transaction::Hold;
555 #
556 #    # BEGIN TRANSACTION
557 #    $patron = new ILS::Patron $patron_id;
558 #    if (!$patron) {
559 #    $trans->screen_msg("Invalid patron barcode.");
560 #
561 #    return $trans;
562 #    } elsif (defined($patron_pwd) && !$patron->check_password($patron_pwd)) {
563 #    $trans->screen_msg('Invalid patron password.');
564 #
565 #    return $trans;
566 #    }
567 #
568 #    $item = new ILS::Item ($item_id || $title_id);
569 #    if (!$item) {
570 #    $trans->screen_msg("No such item.");
571 #
572 #    # END TRANSACTION (conditionally)
573 #    return $trans;
574 #    }
575 #
576 #    # Remove the hold from the patron's record first
577 #    $trans->ok($patron->drop_hold($item_id));
578 #
579 #    if (!$trans->ok) {
580 #    # We didn't find it on the patron record
581 #    $trans->screen_msg("No such hold on patron record.");
582 #
583 #    # END TRANSACTION (conditionally)
584 #    return $trans;
585 #    }
586 #
587 #    # Now, remove it from the item record.  If it was on the patron
588 #    # record but not on the item record, we'll treat that as success.
589 #    foreach my $i (0 .. scalar @{$item->hold_queue}) {
590 #    $hold = $item->hold_queue->[$i];
591 #
592 #    if ($hold->{patron_id} eq $patron->id) {
593 #        # found it: delete it.
594 #        splice @{$item->hold_queue}, $i, 1;
595 #        last;
596 #    }
597 #    }
598 #
599 #    $trans->screen_msg("Hold Cancelled.");
600 #    $trans->patron($patron);
601 #    $trans->item($item);
602 #
603 #    return $trans;
604 #}
605 #
606 #
607 ## The patron and item id's can't be altered, but the
608 ## date, location, and type can.
609 #sub alter_hold {
610 #    my ($self, $patron_id, $patron_pwd, $item_id, $title_id,
611 #    $expiry_date, $pickup_location, $hold_type, $fee_ack) = @_;
612 #    my ($patron, $item);
613 #    my $hold;
614 #    my $trans;
615 #
616 #    $trans = new ILS::Transaction::Hold;
617 #
618 #    # BEGIN TRANSACTION
619 #    $patron = new ILS::Patron $patron_id;
620 #    if (!$patron) {
621 #    $trans->screen_msg("Invalid patron barcode.");
622 #
623 #    return $trans;
624 #    }
625 #
626 #    foreach my $i (0 .. scalar @{$patron->{hold_items}}) {
627 #    $hold = $patron->{hold_items}[$i];
628 #
629 #    if ($hold->{item_id} eq $item_id) {
630 #        # Found it.  So fix it.
631 #        $hold->{expiration_date} = $expiry_date if $expiry_date;
632 #        $hold->{pickup_location} = $pickup_location if $pickup_location;
633 #        $hold->{hold_type} = $hold_type if $hold_type;
634 #
635 #        $trans->ok(1);
636 #        $trans->screen_msg("Hold updated.");
637 #        $trans->patron($patron);
638 #        $trans->item(new ILS::Item $hold->{item_id});
639 #        last;
640 #    }
641 #    }
642 #
643 #    # The same hold structure is linked into both the patron's
644 #    # list of hold items and into the queue of outstanding holds
645 #    # for the item, so we don't need to search the hold queue for
646 #    # the item, since it's already been updated by the patron code.
647 #
648 #    if (!$trans->ok) {
649 #    $trans->screen_msg("No such outstanding hold.");
650 #    }
651 #
652 #    return $trans;
653 #}
654
655
656 sub renew {
657     my ($self, $patron_id, $patron_pwd, $item_id, $title_id,
658         $no_block, $nb_due_date, $third_party, $item_props, $fee_ack) = @_;
659
660     $self->verify_session;
661
662     my $trans = OpenILS::SIP::Transaction::Renew->new( authtoken => $self->{authtoken} );
663     $trans->patron($self->find_patron($patron_id));
664     $trans->item($self->find_item($item_id));
665
666     if(!$trans->patron) {
667         $trans->screen_msg("Invalid patron barcode.");
668         $trans->ok(0);
669         return $trans;
670     }
671
672     if(!$trans->patron->renew_ok) {
673         $trans->screen_msg("Renewals not allowed.");
674         $trans->ok(0);
675         return $trans;
676     }
677
678     if(!$trans->item) {
679         if( $title_id ) {
680             $trans->screen_msg("Title ID renewal not supported.  Use item barcode.");
681         } else {
682             $trans->screen_msg("Invalid item barcode.");
683         }
684         $trans->ok(0);
685         return $trans;
686     }
687
688     if(!$trans->item->{patron} or 
689             $trans->item->{patron} ne $patron_id) {
690         $trans->screen_msg("Item not checked out to " . $trans->patron->name);
691         $trans->ok(0);
692         return $trans;
693     }
694
695     # Perform the renewal
696     $trans->do_renew();
697
698     $trans->desensitize(0);    # It's already checked out
699     $trans->item->{due_date} = $nb_due_date if $no_block eq 'Y';
700     $trans->item->{sip_item_properties} = $item_props if $item_props;
701
702     return $trans;
703 }
704
705
706
707
708
709 #
710 #sub renew_all {
711 #    my ($self, $patron_id, $patron_pwd, $fee_ack) = @_;
712 #    my ($patron, $item_id);
713 #    my $trans;
714 #
715 #    $trans = new ILS::Transaction::RenewAll;
716 #
717 #    $trans->patron($patron = new ILS::Patron $patron_id);
718 #    if (defined $patron) {
719 #    syslog("LOG_DEBUG", "ILS::renew_all: patron '%s': renew_ok: %s",
720 #           $patron->name, $patron->renew_ok);
721 #    } else {
722 #    syslog("LOG_DEBUG", "ILS::renew_all: Invalid patron id: '%s'",
723 #           $patron_id);
724 #    }
725 #
726 #    if (!defined($patron)) {
727 #    $trans->screen_msg("Invalid patron barcode.");
728 #    return $trans;
729 #    } elsif (!$patron->renew_ok) {
730 #    $trans->screen_msg("Renewals not allowed.");
731 #    return $trans;
732 #    } elsif (defined($patron_pwd) && !$patron->check_password($patron_pwd)) {
733 #    $trans->screen_msg("Invalid patron password.");
734 #    return $trans;
735 #    }
736 #
737 #    foreach $item_id (@{$patron->{items}}) {
738 #    my $item = new ILS::Item $item_id;
739 #
740 #    if (!defined($item)) {
741 #        syslog("LOG_WARNING",
742 #           "renew_all: Invalid item id associated with patron '%s'",
743 #           $patron->id);
744 #        next;
745 #    }
746 #
747 #    if (@{$item->hold_queue}) {
748 #        # Can't renew if there are outstanding holds
749 #        push @{$trans->unrenewed}, $item_id;
750 #    } else {
751 #        $item->{due_date} = time + (14*24*60*60); # two weeks hence
752 #        push @{$trans->renewed}, $item_id;
753 #    }
754 #    }
755 #
756 #    $trans->ok(1);
757 #
758 #    return $trans;
759 #}
760
761 1;