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