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