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