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