]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/SIP/Patron.pm
Silence warning
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / SIP / Patron.pm
1 #
2
3 # A Class for hiding the ILS's concept of the patron from the OpenSIP
4 # system
5 #
6
7 package OpenILS::SIP::Patron;
8
9 use strict;
10 use warnings;
11 use Exporter;
12
13 use Sys::Syslog qw(syslog);
14 use Data::Dumper;
15 use Digest::MD5 qw(md5_hex);
16
17 use OpenILS::SIP;
18 use OpenILS::Application::AppUtils;
19 use OpenILS::Application::Actor;
20 use OpenSRF::Utils qw/:datetime/;
21 use DateTime::Format::ISO8601;
22 my $U = 'OpenILS::Application::AppUtils';
23
24 our (@ISA, @EXPORT_OK);
25
26 @ISA = qw(Exporter);
27
28 @EXPORT_OK = qw(invalid_patron);
29
30 my $INET_PRIVS;
31
32 #
33 # OpenILS::SIP::Patron->new($barcode);
34 # OpenILS::SIP::Patron->new(barcode => $barcode);   # same as above
35 # OpenILS::SIP::Patron->new(    usr => $id);       
36
37 sub new {
38     my $class = shift;
39     my $key   = (@_ > 1) ? shift : 'barcode';  # if we have multiple args, the first is the key index (default barcode)
40     my $patron_id = shift;
41
42     if ($key ne 'usr' and $key ne 'barcode') {
43         syslog("LOG_ERROR", "Patron (card) lookup requested by illegeal key '$key'");
44         return undef;
45     }
46
47     unless(defined $patron_id) {
48         syslog("LOG_WARNING", "No patron ID provided to ILS::Patron->new");
49         return undef;
50     }
51
52     my $type = ref($class) || $class;
53     my $self = {};
54
55     syslog("LOG_DEBUG", "OILS: new OpenILS Patron(%s => %s): searching...", $key, $patron_id);
56
57     my $e = OpenILS::SIP->editor();
58     
59     my $user_id = $patron_id;
60     if($key eq 'barcode') {
61         my $card = $e->search_actor_card({barcode => $patron_id})->[0];
62         unless($card) {
63             syslog("LOG_WARNING", "No such patron barcode: $patron_id");
64             return undef;
65         }
66         $user_id = $card->usr;
67     }
68
69         my $user = $e->retrieve_actor_user([
70         $user_id,
71         {
72             flesh => 2,
73             flesh_fields => {
74                 au => [
75                     "card",
76                     "standing_penalties",
77                     "addresses",
78                     "billing_address",
79                     "mailing_address",
80                     'profile',
81                 ],
82                 ausp => ['standing_penalty']
83             }
84         }
85     ]);
86
87     if(!$user) {
88         syslog("LOG_WARNING", "OILS: Unable to find patron %s => %s", $key, $patron_id);
89         return undef;
90     }
91
92     $self->{editor} = $e;
93     $self->{user}   = $user;
94     $self->{id}     = ($key eq 'barcode') ? $patron_id : $user->card->barcode;   # The barcode IS the ID to SIP.  
95     # We give back the passed barcode if the key was indeed a barcode, just to be safe.  Otherwise pull it from the card.
96
97     syslog("LOG_DEBUG", "OILS: new OpenILS Patron(%s => %s): found patron : barred=%s, card:active=%s", 
98         $key, $patron_id, $user->barred, $user->card->active );
99
100
101     bless $self, $type;
102     return $self;
103 }
104
105 sub id {
106     my $self = shift;
107     return $self->{id};
108 }
109
110 sub name {
111     my $self = shift;
112     my $u = $self->{user};
113     return OpenILS::SIP::clean_text(
114         sprintf('%s %s %s', 
115             ($u->first_given_name || ''),
116             ($u->second_given_name || ''),
117             ($u->family_name || '')));
118    
119 }
120
121 sub home_library {
122     my $self = shift;
123     my $lib = $self->{editor}->retrieve_actor_org_unit($self->{user}->home_ou)->shortname;
124         syslog('LOG_DEBUG', "OILS: Patron->home_library() = $lib");
125     return $lib;
126 }
127
128 sub __addr_string {
129     my $addr = shift;
130     return "" unless $addr;
131     my $return = OpenILS::SIP::clean_text(
132         join( ' ', map {$_ || ''} (
133             $addr->street1,
134             $addr->street2,
135             $addr->city . ',',
136             $addr->county,
137             $addr->state,
138             $addr->country,
139             $addr->post_code
140             )
141         )
142     );
143     $return =~ s/\s+/ /sg;     # Compress any run of of whitespace to one space
144     return $return;
145 }
146
147 sub internal_id {
148     my $self = shift;
149     return $self->{user}->id;
150 }
151
152 sub address {
153         my $self = shift;
154         my $u    = $self->{user};
155         my $str  = __addr_string($u->billing_address || $u->mailing_address);
156         syslog('LOG_DEBUG', "OILS: Patron address: $str");
157         return $str;
158 }
159
160 sub email_addr {
161     my $self = shift;
162     return OpenILS::SIP::clean_text($self->{user}->email);
163 }
164
165 sub home_phone {
166     my $self = shift;
167     return $self->{user}->day_phone;
168 }
169
170 sub sip_birthdate {
171         my $self = shift;
172         my $dob = OpenILS::SIP->format_date($self->{user}->dob);
173         syslog('LOG_DEBUG', "OILS: Patron DOB = $dob");
174         return $dob;
175 }
176
177 sub ptype {
178     my $self = shift;
179
180         my $use_code = OpenILS::SIP->get_option_value('patron_type_uses_code') || '';
181
182     # should we use the no_i18n version of patron profile name (as a 'code')?
183     return $self->{editor}->retrieve_permission_grp_tree(
184         [$self->{user}->profile->id, {no_i18n => 1}])->name
185         if $use_code =~ /true/io;
186
187     return OpenILS::SIP::clean_text($self->{user}->profile->name);
188 }
189
190 sub language {
191     my $self = shift;
192     return '000'; # Unspecified
193 }
194
195 # How much more detail do we need to check here?
196 sub charge_ok {
197     my $self = shift;
198     my $u = $self->{user};
199     return (($u->barred eq 'f') and ($u->card->active eq 't'));
200 }
201
202 # How much more detail do we need to check here?
203 sub renew_ok {
204     my $self = shift;
205     return $self->charge_ok;
206 }
207
208 sub recall_ok {
209     my $self = shift;
210     return 0;
211 }
212
213 sub hold_ok {
214     my $self = shift;
215     return $self->charge_ok;
216 }
217
218 # return true if the card provided is marked as lost
219 sub card_lost {
220     my $self = shift;
221     return $self->{user}->card->active eq 'f';
222 }
223
224 sub recall_overdue {        # not implemented
225     my $self = shift;
226     return 0;
227 }
228
229 sub check_password {
230         my ($self, $pwd) = @_;
231         syslog('LOG_DEBUG', 'OILS: Patron->check_password()');
232     return 0 unless (defined $pwd and $self->{user});
233         return md5_hex($pwd) eq $self->{user}->passwd;
234 }
235
236 sub currency {              # not really implemented
237         my $self = shift;
238         syslog('LOG_DEBUG', 'OILS: Patron->currency()');
239         return 'USD';
240 }
241
242 sub fee_amount {
243         my $self = shift;
244         syslog('LOG_DEBUG', 'OILS: Patron->fee_amount()');
245     my $user_id = $self->{user}->id;
246
247     my $e = $self->{editor};
248     $e->xact_begin;
249     my $summary = $e->retrieve_money_open_user_summary($user_id);
250     $e->rollback; # xact_rollback + disconnect
251
252     my $total = ($summary) ? $summary->balance_owed : 0;
253         syslog('LOG_INFO', "User ".$self->{id} .":$user_id has a fee amount of \$$total");
254         return $total;
255 }
256
257 sub screen_msg {
258         my $self = shift;
259         my $u = $self->{user};
260         return 'barred' if $u->barred eq 't';
261
262         my $b = 'blocked';
263
264         return $b if $u->active eq 'f';
265         return $b if $u->card->active eq 'f';
266
267         if( $u->standing_penalties ) {
268                 return $b if 
269                         grep { $_->standing_penalty->name eq 'PATRON_EXCEEDS_OVERDUE_COUNT' } 
270                                 @{$u->standing_penalties};
271
272                 return $b if 
273                         grep { $_->standing_penalty->name eq 'PATRON_EXCEEDS_FINES' } 
274                                 @{$u->standing_penalties};
275         }
276
277         my $expire = DateTime::Format::ISO8601->new->parse_datetime(
278                 cleanse_ISO8601($u->expire_date));
279
280         return $b if CORE::time > $expire->epoch;
281
282         return 'OK';
283 }
284
285 sub print_line {            # not implemented
286     my $self = shift;
287         return '';
288 }
289
290 sub too_many_charged {      # not implemented
291     my $self = shift;
292         return 0;
293 }
294
295 sub too_many_overdue {
296         my $self = shift;
297         if( $self->{user}->standing_penalties ) {
298                 return grep { $_->standing_penalty->name eq 'PATRON_EXCEEDS_OVERDUE_COUNT' } 
299                         @{$self->{user}->standing_penalties};
300         }
301         return 0;
302 }
303
304 # not completely sure what this means
305 sub too_many_renewal {
306     my $self = shift;
307     return 0;
308 }
309
310 # not relevant, handled by fines/fees
311 sub too_many_claim_return {
312     my $self = shift;
313     return 0;
314 }
315
316 # not relevant, handled by fines/fees
317 sub too_many_lost {
318     my $self = shift;
319     return 0;
320 }
321
322 sub excessive_fines {
323     my $self = shift;
324         syslog('LOG_DEBUG', 'OILS: Patron->excessive_fines()');
325         if( $self->{user}->standing_penalties ) {
326                 return grep { $_->standing_penalty->name eq 'PATRON_EXCEEDS_FINES' } 
327                         @{$self->{user}->standing_penalties};
328         }
329         return 0;
330 }
331
332
333 # Until someone suggests otherwise, fees and fines are the same
334
335 sub excessive_fees {
336         my $self = shift;
337         syslog('LOG_DEBUG', 'OILS: Patron->excessive_fees()');
338         if( $self->{user}->standing_penalties ) {
339                 return grep { $_->standing_penalty->name eq 'PATRON_EXCEEDS_FINES' } 
340                         @{$self->{user}->standing_penalties};
341         }
342         return 0;
343 }
344
345 # not relevant, handled by fines/fees
346 sub too_many_billed {
347     my $self = shift;
348         return 0;
349 }
350
351
352
353 #
354 # List of outstanding holds placed
355 #
356 sub hold_items {
357     my ($self, $start, $end) = @_;
358         syslog('LOG_DEBUG', 'OILS: Patron->hold_items()');
359
360          my $holds = $self->{editor}->search_action_hold_request(
361                 { usr => $self->{user}->id, fulfillment_time => undef, cancel_time => undef }
362          );
363
364         my @holds;
365         push( @holds, OpenILS::SIP::clean_text($self->__hold_to_title($_)) ) for @$holds;
366
367         return (defined $start and defined $end) ? 
368                 [ $holds[($start-1)..($end-1)] ] : 
369                 \@holds;
370 }
371
372 sub __hold_to_title {
373         my $self = shift;
374         my $hold = shift;
375         my $e = $self->{editor};
376
377         my( $id, $mods, $title, $volume, $copy );
378
379         return __copy_to_title($e, 
380                 $e->retrieve_asset_copy($hold->target)) 
381                 if $hold->hold_type eq 'C';
382
383         return __volume_to_title($e, 
384                 $e->retrieve_asset_call_number($hold->target))
385                 if $hold->hold_type eq 'V';
386
387         return __record_to_title(
388                 $e, $hold->target) if $hold->hold_type eq 'T';
389
390         return __metarecord_to_title(
391                 $e, $hold->target) if $hold->hold_type eq 'M';
392 }
393
394 sub __copy_to_title {
395         my( $e, $copy ) = @_;
396         #syslog('LOG_DEBUG', "OILS: copy_to_title(%s)", $copy->id);
397         return $copy->dummy_title if $copy->call_number == -1;  
398
399         my $vol = (ref $copy->call_number) ?
400                 $copy->call_number :
401                 $e->retrieve_asset_call_number($copy->call_number);
402
403         return __volume_to_title($e, $vol);
404 }
405
406
407 sub __volume_to_title {
408         my( $e, $volume ) = @_;
409         #syslog('LOG_DEBUG', "OILS: volume_to_title(%s)", $volume->id);
410         return __record_to_title($e, $volume->record);
411 }
412
413
414 sub __record_to_title {
415         my( $e, $title_id ) = @_;
416         #syslog('LOG_DEBUG', "OILS: record_to_title($title_id)");
417         my $mods = $U->simplereq(
418                 'open-ils.search',
419                 'open-ils.search.biblio.record.mods_slim.retrieve', $title_id );
420         return ($mods) ? $mods->title : "";
421 }
422
423 sub __metarecord_to_title {
424         my( $e, $m_id ) = @_;
425         #syslog('LOG_DEBUG', "OILS: metarecord_to_title($m_id)");
426         my $mods = $U->simplereq(
427                 'open-ils.search',
428                 'open-ils.search.biblio.metarecord.mods_slim.retrieve', $m_id);
429         return ($U->event_code($mods)) ? "<unknown>" : $mods->title;
430 }
431
432
433 #
434 # remove the hold on item item_id from my hold queue.
435 # return true if I was holding the item, false otherwise.
436
437 sub drop_hold {
438     my ($self, $item_id) = @_;
439     return 0;
440 }
441
442 sub __patron_items_info {
443         my $self = shift;
444         return if $self->{item_info};
445         $self->{item_info} = 
446                 OpenILS::Application::Actor::_checked_out(
447                         0, $self->{editor}, $self->{user}->id);;
448 }
449
450
451
452 sub overdue_items {
453         my ($self, $start, $end) = @_;
454
455         $self->__patron_items_info();
456         my @overdues = @{$self->{item_info}->{overdue}};
457         #$overdues[$_] = __circ_to_title($self->{editor}, $overdues[$_]) for @overdues;
458
459         my @o;
460         syslog('LOG_DEBUG', "OILS: overdue_items() fleshing circs @overdues");
461
462         my $return_datatype = OpenILS::SIP->get_option_value('msg64_summary_datatype') || '';
463         
464         for my $circid (@overdues) {
465                 next unless $circid;
466                 if($return_datatype eq 'barcode') {
467                         push( @o, __circ_to_barcode($self->{editor}, $circid));
468                 } else {
469                         push( @o, OpenILS::SIP::clean_text(__circ_to_title($self->{editor}, $circid)));
470                 }
471         }
472         @overdues = @o;
473
474         return (defined $start and defined $end) ? 
475                 [ $overdues[($start-1)..($end-1)] ] : \@overdues;
476 }
477
478 sub __circ_to_barcode {
479         my ($e, $circ) = @_;
480         return unless $circ;
481         $circ = $e->retrieve_action_circulation($circ);
482         my $copy = $e->retrieve_asset_copy($circ->target_copy);
483         return $copy->barcode;
484 }
485
486 sub __circ_to_title {
487         my( $e, $circ ) = @_;
488         return unless $circ;
489         $circ = $e->retrieve_action_circulation($circ);
490         return __copy_to_title( $e, 
491                 $e->retrieve_asset_copy($circ->target_copy) );
492 }
493
494 sub charged_items {
495         my ($self, $start, $end) = shift;
496
497         $self->__patron_items_info();
498
499         my @charges = (
500                 @{$self->{item_info}->{out}},
501                 @{$self->{item_info}->{overdue}}
502                 );
503
504         #$charges[$_] = __circ_to_title($self->{editor}, $charges[$_]) for @charges;
505
506         my @c;
507         syslog('LOG_DEBUG', "OILS: charged_items() fleshing circs @charges");
508
509         my $return_datatype = OpenILS::SIP->get_option_value('msg64_summary_datatype') || '';
510
511         for my $circid (@charges) {
512                 next unless $circid;
513                 if($return_datatype eq 'barcode') {
514                         push( @c, __circ_to_barcode($self->{editor}, $circid));
515                 } else {
516                         push( @c, OpenILS::SIP::clean_text(__circ_to_title($self->{editor}, $circid)));
517                 }
518         }
519
520         @charges = @c;
521
522         return (defined $start and defined $end) ? 
523                 [ $charges[($start-1)..($end-1)] ] : 
524                 \@charges;
525 }
526
527 sub fine_items {
528         my ($self, $start, $end) = @_;
529         my @fines;
530         syslog('LOG_DEBUG', 'OILS: Patron->fine_items()');
531         return (defined $start and defined $end) ? 
532                 [ $fines[($start-1)..($end-1)] ] : \@fines;
533 }
534
535 # not currently supported
536 sub recall_items {
537     my ($self, $start, $end) = @_;
538     return [];
539 }
540
541 sub unavail_holds {
542         my ($self, $start, $end) = @_;
543         my @holds;
544         syslog('LOG_DEBUG', 'OILS: Patron->unavail_holds()');
545         return (defined $start and defined $end) ? 
546                 [ $holds[($start-1)..($end-1)] ] : \@holds;
547 }
548
549 sub block {
550         my ($self, $card_retained, $blocked_card_msg) = @_;
551     $blocked_card_msg ||= '';
552
553     my $e = $self->{editor};
554         my $u = $self->{user};
555
556         syslog('LOG_INFO', "OILS: Blocking user %s", $u->card->barcode );
557
558         return $self if $u->card->active eq 'f';
559
560     $e->xact_begin;    # connect and start a new transaction
561
562         $u->card->active('f');
563         if( ! $e->update_actor_card($u->card) ) {
564                 syslog('LOG_ERR', "OILS: Block card update failed: %s", $e->event->{textcode});
565                 $e->rollback; # rollback + disconnect
566                 return $self;
567         }
568
569         # retrieve the un-fleshed user object for update
570         $u = $e->retrieve_actor_user($u->id);
571         my $note = OpenILS::SIP::clean_text($u->alert_message) || "";
572         $note = "<sip> CARD BLOCKED BY SELF-CHECK MACHINE. $blocked_card_msg</sip>\n$note"; # XXX Config option
573     $note =~ s/\s*$//;  # kill trailng whitespace
574         $u->alert_message($note);
575
576         if( ! $e->update_actor_user($u) ) {
577                 syslog('LOG_ERR', "OILS: Block: patron alert update failed: %s", $e->event->{textcode});
578                 $e->rollback; # rollback + disconnect
579                 return $self;
580         }
581
582         # stay in synch
583         $self->{user}->alert_message( $note );
584
585         $e->commit; # commits and disconnects
586         return $self;
587 }
588
589 # Testing purposes only
590 sub enable {
591     my ($self, $card_retained) = @_;
592     $self->{screen_msg} = "All privileges restored.";
593
594     # Un-mark card as inactive, grep out the patron alert
595     my $e = $self->{editor};
596     my $u = $self->{user};
597
598     syslog('LOG_INFO', "OILS: Unblocking user %s", $u->card->barcode );
599
600     return $self if $u->card->active eq 't';
601
602     $e->xact_begin;    # connect and start a new transaction
603
604     $u->card->active('t');
605     if( ! $e->update_actor_card($u->card) ) {
606         syslog('LOG_ERR', "OILS: Unblock card update failed: %s", $e->event->{textcode});
607         $e->rollback; # rollback + disconnect
608         return $self;
609     }
610
611     # retrieve the un-fleshed user object for update
612     $u = $e->retrieve_actor_user($u->id);
613     my $note = OpenILS::SIP::clean_text($u->alert_message) || "";
614     $note =~ s#<sip>.*</sip>##;
615     $note =~ s/^\s*//;  # kill leading whitespace
616     $note =~ s/\s*$//;  # kill trailng whitespace
617     $u->alert_message($note);
618
619     if( ! $e->update_actor_user($u) ) {
620         syslog('LOG_ERR', "OILS: Unblock: patron alert update failed: %s", $e->event->{textcode});
621         $e->rollback; # rollback + disconnect
622         return $self;
623     }
624
625     # stay in synch
626     $self->{user}->alert_message( $note );
627
628     $e->commit; # commits and disconnects
629     return $self;
630 }
631
632 #
633 # Messages
634 #
635
636 sub invalid_patron {
637     return "Please contact library staff";
638 }
639
640 sub charge_denied {
641     return "Please contact library staff";
642 }
643
644 sub inet_privileges {
645     my( $self ) = @_;
646     my $e = OpenILS::SIP->editor();
647     $INET_PRIVS = $e->retrieve_all_config_net_access_level() unless $INET_PRIVS;
648     my ($level) = grep { $_->id eq $self->{user}->net_access_level } @$INET_PRIVS;
649     my $name = OpenILS::SIP::clean_text($level->name);
650     syslog('LOG_DEBUG', "OILS: Patron inet_privs = $name");
651     return $name;
652 }
653
654
655 1;