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