]> git.evergreen-ils.org Git - Evergreen.git/blob - Evergreen/src/support-scripts/eg_gen_overdue.pl
changed date format in the emails
[Evergreen.git] / Evergreen / src / support-scripts / eg_gen_overdue.pl
1 #!/usr/bin/perl
2 # ---------------------------------------------------------------
3 # Generates the overdue notices XML file
4 # ./eg_gen_overdue.pl <bootstap> 0
5 #               generates today's notices
6 # ./eg_gen_overdue.pl <bootstap> 1 0
7 #               generates notices for today - 1 and today
8 # ./eg_gen_overdue.pl <bootstap> 2 1 0  
9 # ./eg_gen_overdue.pl <bootstap> 3 2 1 0  etc...
10 # ---------------------------------------------------------------
11
12
13
14 use strict; use warnings;
15 require '../../../Open-ILS/src/support-scripts/oils_header.pl';
16 use vars qw/$logger $apputils/;
17 use Data::Dumper;
18 use OpenILS::Const qw/:const/;
19 use DateTime;
20 use Email::Send;
21 use DateTime::Format::ISO8601;
22 use OpenSRF::Utils qw/:datetime/;
23 use Unicode::Normalize;
24 use OpenILS::Const qw/:const/;
25
26 my $SEND_EMAILS = 1;
27
28
29 my $bsconfig = shift || die "usage: $0 <bootstrap_config>\n";
30 my @goback = @ARGV;
31 @goback = (0) unless @goback;
32 osrf_connect($bsconfig);
33 my $e = OpenILS::Utils::CStoreEditor->new;
34
35 my $smtp = $ENV{EG_OVERDUE_SMTP_HOST};
36 my $mail_sender = $ENV{EG_OVERDUE_EMAIL_SENDER};
37
38 # ---------------------------------------------------------------
39 # Set up the email template
40 my $etmpl = $ENV{EG_OVERDUE_EMAIL_TEMPLATE};
41 my $email_template;
42 if( open(F,"$etmpl") ) {
43         my @etmpl = <F>;
44         $email_template = join('',@etmpl);
45         close(F);
46 }
47 # ---------------------------------------------------------------
48
49
50
51 my @date = CORE::localtime;
52 my $sec  = $date[0];
53 my $min  = $date[1];
54 my $hour = $date[2];
55 my $day  = $date[3];
56 my $mon  = $date[4] + 1;
57 my $year = $date[5] + 1900;
58
59 my %USER_CACHE;
60 my %ORG_CACHE;
61
62
63 print <<XML;
64 <?xml version='1.0' encoding='UTF-8'?>
65 <file type="notice" date="$mon/$day/$year" time="$hour:$min:$sec">
66         <agency name="PINES">
67 XML
68
69 print_notices($_) for @goback;
70
71 print <<XML;
72         </agency>
73 </file>
74 XML
75
76
77 # -----------------------------------------------------------------------
78 # -----------------------------------------------------------------------
79
80
81 sub print_notices {
82         my $goback = shift || 0;
83
84         for my $day ( qw/ 7 14 30 / ) {
85                 my ($start, $end) = make_date_range($day + $goback);
86
87                 $logger->info("OD_notice: process date range $start -> $end");
88
89                 my $query = [
90                         {
91                                 checkin_time => undef,
92                                 due_date => { between => [ $start, $end ] },
93                         },
94                         { order_by => { circ => 'usr, circ_lib' } }
95                 ];
96                 my $circs = $e->search_action_circulation($query, {idlist=>1});
97
98                 process_circs( $circs, "${day}day" );
99         }
100 }
101
102
103 sub process_circs {
104         my $circs = shift;
105         my $range = shift;
106
107         return unless @$circs;
108
109         $logger->info("OD_notice: processing range $range and ".scalar(@$circs)." potential circs");
110
111         my $org; 
112         my $patron;
113         my @current;
114
115         my $x = 0;
116         for my $circ (@$circs) {
117                 $circ = $e->retrieve_action_circulation($circ);
118
119                 if( !defined $org or 
120                                 $circ->circ_lib != $org  or $circ->usr ne $patron ) {
121                         $org = $circ->circ_lib;
122                         $patron = $circ->usr;
123                         print_notice( $range, \@current ) if @current;
124                         @current = ();
125                 }
126
127                 push( @current, $circ );
128                 $x++;
129         }
130
131         $logger->info("OD_notice: processed $x circs");
132         print_notice( $range, \@current );
133 }
134
135 sub make_date_range {
136         my $daysback = shift;
137
138         my $epoch = CORE::time - ($daysback * 24 * 60 * 60);
139         my $date = DateTime->from_epoch( epoch => $epoch, time_zone => 'local');
140
141         $date->set_hour(0);
142         $date->set_minute(0);
143         $date->set_second(0);
144         my $start = "$date";
145         
146         $date->set_hour(23);
147         $date->set_minute(59);
148         $date->set_second(59);
149
150         return ($start, "$date");
151 }
152
153
154 sub print_notice {
155         my( $range, $circs ) = @_;
156         return unless @$circs;
157
158         my $s1 = scalar(@$circs);
159         
160         # we don't charge for lost or claimsreturned
161         $circs = [ 
162                 grep {
163                         !$_->stop_fines or (
164                                 $_->stop_fines ne OILS_STOP_FINES_LOST and
165                                 $_->stop_fines ne OILS_STOP_FINES_CLAIMSRETURNED 
166                         )
167                 } @$circs 
168         ];
169
170         return unless @$circs;
171
172         my $s2 = $s1 - scalar(@$circs);
173         $logger->info("OD_notice: dropped $s2 lost/CR from processing...") if $s2;
174
175         my $org = $circs->[0]->circ_lib;
176         my $usr = $circs->[0]->usr;
177         $logger->debug("OD_notice: printing $range user:$usr org:$org");
178
179         my @patron_data = fetch_patron_data($usr);
180         my @org_data = fetch_org_data($org);
181
182         my $email;
183
184         if( $email = $patron_data[0]->email 
185                 and $email =~ /.+\@.+/ 
186                 and ($range eq '7day' or $range eq '14day') ) {
187
188                         send_email($range, \@patron_data, \@org_data, $circs);
189
190         } else {
191
192                 print "\t\t<notice type='overdue' count='$range'>\n";
193                 print_patron_xml_chunk(@patron_data);
194                 print_org_xml_chunk(@org_data);
195                 print_circ_chunk($_) for @$circs;
196                 print "\t\t</notice>\n";
197         }
198 }
199
200
201 sub fetch_patron_data {
202         my $user_id = shift;
203
204         my $patron = $USER_CACHE{$user_id};
205
206         if( ! $patron ) {
207                 $logger->debug("OD_notice:   fetching patron $user_id");
208
209                 $patron = $e->retrieve_actor_user(
210                         [
211                                 $user_id,
212                                 {
213                                         flesh => 1,
214                                         flesh_fields => { 
215                                                 'au' => [qw/ card billing_address mailing_address /] 
216                                         }
217                                 }
218                         ]
219                 ) or return handle_event($e->event);
220
221                 $USER_CACHE{$user_id} = $patron;
222         }
223
224         my $bc = $patron->card->barcode;
225         my $fn = $patron->first_given_name;
226         my $mn = $patron->second_given_name;
227         my $ln = $patron->family_name;
228
229         my ( $s1, $s2, $city, $state, $zip );
230         my $baddr = $patron->mailing_address || $patron->billing_address;
231         if( $baddr ) {
232                 $s1             = $baddr->street1;
233                 $s2             = $baddr->street2;
234                 $city           = $baddr->city;
235                 $state  = $baddr->state;
236                 $zip            = $baddr->post_code;
237         }
238
239         $bc = entityize($bc);
240         $fn = entityize($fn);
241         $mn = entityize($mn);
242         $ln = entityize($ln);
243         $s1 = entityize($s1);
244         $s2 = entityize($s2);
245         $city  = entityize($city);
246         $state = entityize($state);
247         $zip     = entityize($zip);
248
249         return ( $patron, $bc, $fn, $mn, $ln, $s1, $s2, $city, $state, $zip );
250 }
251
252         
253 sub print_patron_xml_chunk {
254         my( $patron, $bc, $fn, $mn, $ln, $s1, $s2, $city, $state, $zip ) = @_;
255         my $pid = $patron->id;
256         print <<"       XML";
257                         <patron>
258                                 <id type="barcode">$bc</id>
259                                 <fullname>$fn $mn $ln</fullname>
260                                 <street1>$s1 $s2</street1>
261                                 <city_state_zip>$city, $state $zip</city_state_zip>
262                                 <sys_id>$pid</sys_id>
263                         </patron>
264         XML
265 }
266
267
268 sub fetch_org_data {
269         my $org_id = shift;
270
271         my $org = $ORG_CACHE{$org_id};
272
273         if( ! $org ) {
274                 $logger->debug("OD_notice:   fetching org $org_id");
275
276                 $org = $e->retrieve_actor_org_unit(
277                         [
278                                 $org_id,
279                                 {
280                                         flesh => 1, 
281                                         flesh_fields => 
282                                                 { aou => [ qw/billing_address mailing_address/ ] }
283                                 }
284                         ]
285                 ) or return handle_event($e->event);
286
287                 $ORG_CACHE{$org_id} = $org;
288         }
289
290         my $name = $org->name;
291         my $email = $org->email;
292         my $phone = $org->phone;
293
294         my( $s1, $s2, $city, $state, $zip );
295         my $baddr = $org->billing_address || $org->mailing_address;
296         if( $baddr ) {
297                 $s1             = $baddr->street1;
298                 $s2             = $baddr->street2;
299                 $city           = $baddr->city;
300                 $state  = $baddr->state;
301                 $zip            = $baddr->post_code;
302         }
303
304         $name  = entityize($name);
305         $phone = entityize($phone);
306         $s1      = entityize($s1);
307         $s2      = entityize($s2);
308         $city  = entityize($city);
309         $state = entityize($state);
310         $zip     = entityize($zip);
311         $email = entityize($email);
312
313         return ( $org, $name, $phone, $s1, $s2, $city, $state, $zip, $email );
314 }
315
316
317 sub print_org_xml_chunk {
318         my( $org, $name, $phone, $s1, $s2, $city, $state, $zip, $email ) = @_;
319         print <<"       XML";
320                         <library>
321                                 <libname>$name</libname>
322                                 <libphone>$phone</libphone>
323                                 <libstreet1>$s1 $s2</libstreet1>
324                                 <libcity_state_zip>$city, $state $zip</libcity_state_zip>
325                         </library>
326         XML
327 }
328
329
330 sub fetch_circ_data {
331         my $circ = shift;
332
333         my $title;
334         my $author;
335         my $cn;
336
337         my $d = $circ->due_date;
338         $d =~ s/[T ].*//og; # just for logging
339         $logger->debug("OD_notice:   processing circ ".$circ->id." $d");
340
341         my $due = DateTime::Format::ISO8601->new->parse_datetime(
342                 clense_ISO8601($circ->due_date));
343
344         my $day  = $due->day;
345         my $mon  = $due->month;
346         my $year = $due->year;
347
348         my $copy = $e->retrieve_asset_copy($circ->target_copy)
349                 or return handle_event($e->event);
350
351         my $bc = $copy->barcode;
352
353         if( $copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
354                 $title = $copy->dummy_title || "";
355                 $author = $copy->dummy_author || "";
356
357         } else {
358
359                 my $volume = $e->retrieve_asset_call_number(
360                         [
361                                 $copy->call_number,
362                                 {
363                                         flesh => 1,
364                                         flesh_fields => {
365                                                 acn => [ qw/record/ ]
366                                         }
367                                 }
368                         ]
369                 ) or return handle_event($e->event);
370
371                 $cn = $volume->label;
372                 my $mods = $apputils->record_to_mvr($volume->record);
373                 if( $mods ) {
374                         $title = $mods->title || "";
375                         $author = $mods->author || "";
376                 }
377         }
378
379         $title = entityize($title);
380         $author = entityize($author);
381         $cn = entityize($cn);
382         $bc = entityize($bc);
383
384         return( $title, $author, $cn, $bc, $day, $mon, $year );
385 }
386
387
388 sub print_circ_chunk {
389         my $circ = shift;
390         my ( $title, $author, $cn, $bc, $day, $mon, $year ) = fetch_circ_data($circ);
391         my $cid = $circ->id;
392         print <<"       XML";
393                         <item>
394                                 <title>$title</title>
395                                 <author>$author</author>
396                                 <duedate>$mon/$day/$year</duedate>
397                                 <callno>$cn</callno>
398                                 <barcode>$bc</barcode>
399                                 <circ_id>$cid</circ_id>
400                         </item>
401         XML
402 }
403
404
405
406 sub send_email {
407         my( $range, $patron_data, $org_data, $circs ) = @_;
408         my( $org, $org_name, $org_phone, $org_s1, $org_s2, $org_city, $org_state, $org_zip, $org_email ) = @$org_data;
409         my( $patron, $bc, $fn, $mn, $ln, $user_s1, $user_s2, $user_city, $user_state, $user_zip ) = @$patron_data;
410
411         return unless $SEND_EMAILS;
412
413         my $pemail = $patron_data->[0]->email;
414
415         my $tmpl = $email_template;
416         my @time = localtime;
417         my $year = $time[5] + 1900;
418         my $mon  = $time[4] + 1;
419         my $day  = $time[3];
420
421         my $r = ($range eq '7day') ? 7 : 14;
422
423         $org_email ||= $mail_sender;
424
425         $tmpl =~ s/\${EMAIL_RECIPIENT}/$pemail/;
426         $tmpl =~ s/\${EMAIL_SENDER}/$mail_sender/o;
427         $tmpl =~ s/\${EMAIL_REPLY_TO}/$org_email/;
428    $tmpl =~ s/\${EMAIL_HEADERS}//;
429
430    $tmpl =~ s/\${RANGE}/$r/;
431    $tmpl =~ s/\${DATE}/$mon\/$day\/$year/;
432    $tmpl =~ s/\${FIRST_NAME}/$fn/;
433    $tmpl =~ s/\${MIDDLE_NAME}/$mn/;
434    $tmpl =~ s/\${LAST_NAME}/$ln/;
435
436         my ($itmpl) = $tmpl =~ /\${OVERDUE_ITEMS\[(.*)\]}/ms;
437
438         my $items = '';
439         for my $circ (@$circs) {
440                 my $circtmpl = $itmpl;
441                 my ( $title, $author, $cn, $bc, $due_day, $due_mon, $due_year ) = fetch_circ_data($circ);
442                 $circtmpl =~ s/\${TITLE}/$title/o;
443                 $circtmpl =~ s/\${AUTHOR}/$author/o;
444                 $circtmpl =~ s/\${CALL_NUMBER}/$cn/o;
445                 $circtmpl =~ s/\${DUE_DAY}/$due_day/o;
446                 $circtmpl =~ s/\${DUE_MONTH}/$due_mon/o;
447                 $circtmpl =~ s/\${DUE_YEAR}/$due_year/o;
448                 $circtmpl =~ s/\${ITEM_BARCODE}/$bc/o;
449                 $items .= "$circtmpl\n";
450         }
451
452         $tmpl =~ s/\${OVERDUE_ITEMS\[.*\]}/$items/ms;
453
454         my $org_addr = "$org_s1 $org_s2 $org_city, $org_state $org_zip";
455         $tmpl =~ s/\${ORG_NAME}/$org_name/o;
456         $tmpl =~ s/\${ORG_ADDRESS}/$org_addr/o;
457         $tmpl =~ s/\${ORG_PHONE}/$org_phone/o;
458
459         $logger->debug("OD_notice: sending email to $pemail: $tmpl");
460
461         my $sender = Email::Send->new({mailer => 'SMTP'});
462         $sender->mailer_args([Host => $smtp]);
463
464         my $stat = $sender->send($tmpl);
465
466         if( $stat and $stat->type eq 'success' ) {
467                 $logger->info("OD_notice:   successfully sent overdue email");
468         } else {
469                 $logger->warn("OD_notice:   unable to send hold overdue email: ".Dumper($stat));
470         }
471
472         $logger->info("OD_notice:   sending email to".$patron_data->[0]->email);
473 }
474
475 sub handle_event {
476         my $evt = shift;
477         warn "OD_notice: ".Dumper($evt) . "\n";
478         $logger->error("OD_notice: ".Dumper($evt));
479 }
480
481
482 sub entityize {
483         my $stuff = shift || return "";
484         $stuff =~ s/\</&lt;/og;
485         $stuff =~ s/\>/&gt;/og;
486         $stuff =~ s/\&/&amp;/og;
487         $stuff = NFC($stuff);
488         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
489         return $stuff;
490 }
491
492
493
494