]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/HoldNotify.pm
LP#1661688: Add a link and other tweaks to alternate hold pickup feature
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Circ / HoldNotify.pm
1 # ---------------------------------------------------------------
2 # Copyright (C) 2005  Georgia Public Library Service 
3 # Bill Erickson <billserickson@gmail.com>
4
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
15
16
17 package OpenILS::Application::Circ::HoldNotify;
18 use base qw/OpenILS::Application/;
19 use strict; use warnings;
20 use OpenSRF::EX qw(:try);
21 use vars q/$AUTOLOAD/;
22 use OpenILS::Event;
23 use OpenSRF::Utils::JSON;
24 use OpenSRF::Utils::Logger qw(:logger);
25 use OpenILS::Utils::CStoreEditor q/:funcs/;
26 use OpenSRF::Utils::SettingsClient;
27 use OpenILS::Application::AppUtils;
28 use OpenILS::Const qw/:const/;
29 use OpenILS::Utils::Fieldmapper;
30 use Email::Send;
31 use Data::Dumper;
32 use OpenSRF::EX qw/:try/;
33 my $U = 'OpenILS::Application::AppUtils';
34
35 use open ':utf8';
36
37
38 __PACKAGE__->register_method(
39     method => 'send_email_notify_pub',
40     api_name => 'open-ils.circ.send_hold_notify.email',
41 );
42
43
44 sub send_email_notify_pub {
45     my( $self, $conn, $auth, $hold_id ) = @_;
46     my $e = new_editor(authtoken => $auth);
47     return $e->event unless $e->checkauth;
48     return $e->event unless $e->allowed('CREATE_HOLD_NOTIFICATION');
49     my $notifier = __PACKAGE__->new(requestor => $e->requestor, hold_id => $hold_id);
50     return $notifier->event if $notifier->event;
51     my $stat = $notifier->send_email_notify;
52 #   $e->commit if $stat == '1';
53     return $stat;
54 }
55
56
57
58
59
60 # ---------------------------------------------------------------
61 # Define the notifier object
62 # ---------------------------------------------------------------
63
64 my @AUTOLOAD_FIELDS = qw/
65     hold
66     copy
67     volume
68     title
69     editor
70     patron
71     event
72     pickup_lib
73     smtp_server
74     settings_client
75 /;
76
77 sub AUTOLOAD {
78     my $self = shift;
79     my $type = ref($self) or die "$self is not an object";
80     my $data = shift;
81     my $name = $AUTOLOAD;
82     $name =~ s/.*://o;   
83
84     unless (grep { $_ eq $name } @AUTOLOAD_FIELDS) {
85         $logger->error("hold_notify: $type: invalid autoload field: $name");
86         die "$type: invalid autoload field: $name\n" 
87     }
88
89     {
90         no strict 'refs';
91         *{"${type}::${name}"} = sub {
92             my $s = shift;
93             my $v = shift;
94             $s->{$name} = $v if defined $v;
95             return $s->{$name};
96         }
97     }
98     return $self->$name($data);
99 }
100
101
102 sub new {
103     my( $class, %args ) = @_;
104     $class = ref($class) || $class;
105     my $self = bless( {}, $class );
106     $self->editor( new_editor( xact => 1, requestor => $args{requestor} ));
107     $logger->debug("circulator: creating new hold-notifier with requestor ".
108         $self->editor->requestor->id);
109     $self->fetch_data($args{hold_id});
110     return $self;
111 }
112
113 sub send_email_notify {
114     my $self = shift;
115
116     my $sc = OpenSRF::Utils::SettingsClient->new;
117     my $setting = $sc->config_value(
118         qw/ apps open-ils.circ app_settings notify_hold email / );
119
120     $logger->debug("hold_notify: email enabled setting = $setting");
121
122     if( !$setting or $setting ne 'true' ) {
123       $self->editor->rollback;
124         $logger->info("hold_notify: not sending hold notify - email notifications disabled");
125         return 0;
126     }
127
128     unless ($U->is_true($self->hold->email_notify)) {
129       $self->editor->rollback;
130         $logger->info("hold_notify: not sending hold notification because email_notify is false");
131         return 0;
132     }
133
134     unless( $self->patron->email and $self->patron->email =~ /.+\@.+/ ) { # see if it's remotely email-esque
135       $self->editor->rollback;
136        return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
137    }
138
139     $logger->info("hold_notify: attempting email notify on hold ".$self->hold->id);
140
141     my $sclient = OpenSRF::Utils::SettingsClient->new;
142     $self->settings_client($sclient);
143     my $template = $sclient->config_value('email_notify', 'template');
144     my $str = $self->flesh_template($self->load_template($template));
145
146     unless( $str ) {
147       $self->editor->rollback;
148         $logger->error("hold_notify: No email notify template found - cannot notify");
149         return 0;
150     }
151
152    my $reqr = $self->editor->requestor;
153    $self->editor->rollback; # we're done with this transaction
154
155     return 0 unless $self->send_email($str);
156
157     # ------------------------------------------------------------------
158     # If the hold email takes too long to send, the existing editor 
159     # transaction may have timed out.  Create a one-off editor to write 
160     # the notification to the DB.
161     # ------------------------------------------------------------------
162     my $we = new_editor(xact=>1, requestor=>$reqr);
163
164     my $notify = Fieldmapper::action::hold_notification->new;
165     $notify->hold($self->hold->id);
166     $notify->notify_staff($we->requestor->id);
167     $notify->notify_time('now');
168     $notify->method('email');
169     
170     $we->create_action_hold_notification($notify)
171         or return $we->die_event;
172     $we->commit;
173
174     return 1;
175 }
176
177 sub send_email {
178     my( $self, $text ) = @_;
179
180    # !!! $self->editor xact has been rolled back before we get here
181
182     my $smtp = $self->settings_client->config_value('email_notify', 'smtp_server');
183
184     $logger->info("hold_notify: sending email notice to ".
185         $self->patron->email." with SMTP server $smtp");
186
187     my $sender = Email::Send->new({mailer => 'SMTP'});
188     $sender->mailer_args([Host => $smtp]);
189
190     my $stat;
191     my $err;
192
193     try {
194         $stat = $sender->send($text);
195     } catch Error with {
196         $err = $stat = shift;
197         $logger->error("hold_notify: Email notify failed with error: $err");
198     };
199
200     if( !$err and $stat and $stat->type eq 'success' ) {
201         $logger->info("hold_notify: successfully sent hold notification");
202         return 1;
203     } else {
204         $logger->warn("hold_notify: unable to send hold notification: ".Dumper($stat));
205         return 0;
206     }
207
208     return undef;
209 }
210
211
212 # -------------------------------------------------------------------------
213 # Fetches all of the hold-related data
214 # -------------------------------------------------------------------------
215 sub fetch_data {
216     my $self        = shift;
217     my $holdid  = shift;
218     my $e           = $self->editor;
219
220     $logger->debug("circulator: fetching hold notify data");
221
222     $self->hold($e->retrieve_action_hold_request($holdid)) or return $self->event($e->event);
223     $self->copy($e->retrieve_asset_copy($self->hold->current_copy)) or return $self->event($e->event);
224     $self->volume($e->retrieve_asset_call_number($self->copy->call_number)) or return $self->event($e->event);
225     $self->title($e->retrieve_biblio_record_entry($self->volume->record)) or return $self->event($e->event);
226     $self->patron($e->retrieve_actor_user($self->hold->usr)) or return $self->event($e->event);
227     $self->pickup_lib($e->retrieve_actor_org_unit($self->hold->pickup_lib)) or return $self->event($e->event);
228 }
229
230
231 sub extract_data {
232     my $self = shift;
233     my $e = $self->editor;
234
235     my $patron = $self->patron;
236     my $o_name = $self->pickup_lib->name;
237     my $p_name = $patron->first_given_name .' '.$patron->family_name;
238
239     # try to find a suitable address for the patron
240     my $p_addr;
241     my $p_addrs;
242     unless( $p_addr = 
243             $e->retrieve_actor_user_address($patron->billing_address)) {
244         unless( $p_addr = 
245                 $e->retrieve_actor_user_address($patron->mailing_address)) {
246             $logger->warn("hold_notify: No address for user ".$patron->id);
247             $p_addrs = "";
248         }
249     }
250
251     unless( defined $p_addrs ) {
252         $p_addrs = 
253             $p_addr->street1." ".
254             $p_addr->street2." ".
255             $p_addr->city." ".
256             $p_addr->state." ".
257             $p_addr->post_code;
258     }
259
260     my $l_addr = $e->retrieve_actor_org_address($self->pickup_lib->holds_address);
261     my $l_addrs = (!$l_addr) ? "" : 
262             $l_addr->street1." ".
263             $l_addr->street2." ".
264             $l_addr->city." ".
265             $l_addr->state." ".
266             $l_addr->post_code;
267
268     my $title;  
269     my $author;
270
271     if( $self->title->id == OILS_PRECAT_RECORD ) {
272         $title = ($self->copy->dummy_title) ? 
273             $self->copy->dummy_title : "";
274         $author = ($self->copy->dummy_author) ? 
275             $self->copy->dummy_author : "";
276     } else {
277         my $mods    = $U->record_to_mvr($self->title);
278         $title  = ($mods->title) ? $mods->title : "";
279         $author = ($mods->author) ? $mods->author : "";
280     }
281
282
283     return { 
284         patron_email => $self->patron->email,
285         pickup_lib_name => $o_name,
286         pickup_lib_addr => $l_addrs,
287         patron_name => $p_name, 
288         patron_addr => $p_addrs, 
289         title => $title, 
290         author => $author, 
291         call_number => $self->volume->label,
292         copy_barcode => $self->copy->barcode,
293         copy_number => $self->copy->copy_number,
294     };
295 }
296
297
298
299 sub load_template {
300     my $self = shift;
301     my $template = shift;
302
303     unless( open(F, $template) ) {
304         $logger->error("hold_notify: Unable to open hold notification template file: $template");
305         return undef;
306     }
307
308     # load the template, strip comments
309     my @lines = <F>;
310     close(F);
311
312     my $str = '';
313     for(@lines) {
314     chomp $_;
315     next if $_ =~ /^\s*\#/o;
316     $_ =~ s/\#.*//og;
317     $str .= "$_\n";
318     }
319
320     return $str;
321 }
322
323 sub flesh_template {
324     my( $self, $str ) = @_;
325     return undef unless $str;
326
327     my @time    = CORE::localtime();
328     my $day         = $time[3];
329     my $month   = $time[4] + 1;
330     my $year    = $time[5] + 1900;
331
332     my $data = $self->extract_data;
333
334     my $email       = $$data{patron_email};
335     my $p_name      = $$data{patron_name};
336     my $p_addr      = $$data{patron_addr};
337     my $o_name      = $$data{pickup_lib_name};
338     my $o_addr      = $$data{pickup_lib_addr};
339     my $title       = $$data{title};
340     my $author      = $$data{author};
341     my $cn          = $$data{call_number};
342     my $barcode     = $$data{copy_barcode};
343     my $copy_number = $$data{copy_number};
344
345     my $sender = $self->settings_client->config_value('email_notify', 'sender_address');
346     my $reply_to = $self->pickup_lib->email;
347     $reply_to ||= $sender; 
348
349    # if they have an org setting for bounced emails, use that as the sender address
350    if( my $set = $self->editor->search_actor_org_unit_setting(
351          {  name => OILS_SETTING_ORG_BOUNCED_EMAIL, 
352             org_unit => $self->pickup_lib->id } )->[0] ) {
353
354       my $bemail = OpenSRF::Utils::JSON->JSON2perl($set->value);
355       $sender = $bemail if $bemail;
356    }
357
358    $str =~ s/\${EMAIL_SENDER}/$sender/;
359    $str =~ s/\${EMAIL_RECIPIENT}/$email/;
360    $str =~ s/\${EMAIL_REPLY_TO}/$reply_to/;
361    $str =~ s/\${EMAIL_HEADERS}//;
362
363    $str =~ s/\${DATE}/$year-$month-$day/;
364    $str =~ s/\${LIBRARY}/$o_name/;
365    $str =~ s/\${LIBRARY_ADDRESS}/$o_addr/;
366    $str =~ s/\${PATRON_NAME}/$p_name/;
367    $str =~ s/\${PATRON_ADDRESS}/$p_addr/;
368
369    $str =~ s/\${TITLE}/$title/;
370    $str =~ s/\${AUTHOR}/$author/;
371    $str =~ s/\${CALL_NUMBER}/$cn/;
372    $str =~ s/\${COPY_BARCODE}/$barcode/;
373    $str =~ s/\${COPY_NUMBER}/$copy_number/;
374
375     return $str;
376 }
377
378
379
380
381
382 1;