[open-ils-commits] r351 - in grpl/trunk: . patron_notifications patron_notifications/tadl-egnotify-scripts_20090306 patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib/asterisk patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib/asterisk/agi-bin patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server patron_notifications/tadl-egnotify-scripts_20090306/notify-box patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk (dkyle)

svn at svn.open-ils.org svn at svn.open-ils.org
Fri Apr 17 15:42:06 EDT 2009


Author: dkyle
Date: 2009-04-17 15:42:02 -0400 (Fri, 17 Apr 2009)
New Revision: 351

Added:
   grpl/trunk/patron_notifications/
   grpl/trunk/patron_notifications/README
   grpl/trunk/patron_notifications/do_holdshelf.plx
   grpl/trunk/patron_notifications/do_overdues.plx
   grpl/trunk/patron_notifications/extensions.conf
   grpl/trunk/patron_notifications/grpl_courtesy.pl
   grpl/trunk/patron_notifications/grpl_courtesy.sh
   grpl/trunk/patron_notifications/grpl_courtesy_email
   grpl/trunk/patron_notifications/grpl_holdnotice.cgi
   grpl/trunk/patron_notifications/grpl_holdshelf.cgi
   grpl/trunk/patron_notifications/grpl_overdue.pl
   grpl/trunk/patron_notifications/grpl_overdue.sh
   grpl/trunk/patron_notifications/grpl_overdue_email
   grpl/trunk/patron_notifications/holdshelf-result.pl
   grpl/trunk/patron_notifications/import_holdshelf.plx
   grpl/trunk/patron_notifications/make_holdshelf_callfiles.plx
   grpl/trunk/patron_notifications/make_overdue_callfiles.plx
   grpl/trunk/patron_notifications/overdue-result.pl
   grpl/trunk/patron_notifications/send_holdshelf_emails.plx
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/README-TADL.txt
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/asterisk.conf
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/cdr.conf
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/cdr_pgsql.conf
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/extensions.conf
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/features.conf
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/modules.conf
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/sip.conf
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/queue-feeder.pl
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib/asterisk/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib/asterisk/agi-bin/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib/asterisk/agi-bin/holdshelf-result.pl
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server/tadl_holdnotice.cgi
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server/tadl_holdshelf_all.cgi
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/holdshelf-schema.mysqldump
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/email-overdues.py
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/import-notices.py
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/xml2obj.py
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/noticeitems-schema.txt
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root/import_holdshelf_all.plx
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root/send_holdshelf_emails.plx
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/holdshelf_template
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/make_holdshelf_callfiles.plx
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/tmp/
   grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/overdues-sample.xml
Log:
patron notifications

Added: grpl/trunk/patron_notifications/README
===================================================================
--- grpl/trunk/patron_notifications/README	                        (rev 0)
+++ grpl/trunk/patron_notifications/README	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,81 @@
+The basic idea when I started this project was to run a VM as an Evergreen notification server, seperate from our production Evergreen cluster.
+I planned to originate all email and phone notifications from this notification server, pulling the pertinent data from EG via remote calls
+to perl scripts on the cluster, storing the data, when needed, in a local database, and generating all my messages from there, although I 
+wound up sending overdue and courtesy emails from the cluster since it was so easy to use the provided eg_gen_overdue files for that.
+
+EG cluster <------> Notification Server <----> Asterisk and/or Email  Server
+    ^                                              |
+    |______________________________________________
+
+These files are provided as an Example Only of one way to approach Evergreen notifications, and were written for EG 1.2.x.  There are hard coded
+references to our branches, policies, brand of telelphony cards, number of channels, etc. 
+
+The process goes like this:
+Every morning the notifications server
+	deletes left over tmp(call) files from previous day
+	pulls "holds ready for pickup" info from Evergreen and inserts that info into a local DB (these files are written for mysql)
+	generates asterisk call files for holds from the local db
+	generates asterisk call files for overdues from the local db
+		(the overdue info is inserted via remote DBI connection from a script running on Evergreen)
+	copies call files to an Asterisk server
+	generates and send hold pickup emails from the local db
+The Asterisk server
+	runs programs that feed the call files into the outgoing queue
+	the calls trigger agi-bin scripts which create EG hold notification records and update the notifications server db
+
+
+** HOLD MESSAGES **
+on one of the EG cluster's HTTP servers:
+grpl_holdshelf.cgi (outputs EG data on holds ready for pickup, called from import_holdshelf.plx, via cron, on the notification server)
+grpl_holdnotice.cgi (adds a notification to a hold in EG, called from holdshelf-result.pl on the Asterisk serverm, 
+		      and from send_holdshelf_emails.plx on the notification server)
+
+on the Notification Server:
+import_holdshelf.plx (loads hold info from grpl_holdshelf.cgi into notify database, runs from cron)
+make_holdshelf_callfiles.plx (creates Asterisk call files from notify database, runs from cron)
+send_holdshelf_emails.plx (creates and sends emails from notify database, calls grpl_holdnotice.cgi, runs from cron)
+there are cron jobs to scp call files to the Asterisk server, and to clear the call file directory 
+
+on the Asterisk Server
+holdshelf-result.pl (agi program that updates EG and notify database with call result, called from Asterisk dialplan)
+extensions.conf (Asterisk dialplan, we forego answering detection and play the message twice)
+do_holdshelf.plx (feeds call files to the Asterisk outgoing queue, Asterisk tends to choke if too many call files in the queue, runs from cron)
+there are cron jobs to kill asterisk in the evening and raise it in the morning.
+
+
+** OVERDUE MESSAGES **
+on one of the EG cluster's app servers:
+These files are modified from the eg_gen_overdue files provided in the EG distro support-scripts directory
+grpl_overdue.sh (args for the perl script, called from cron)
+grpl_overdue_email (message template)
+grpl_overdue.pl (pulls the overdues info from EG, sends emails, and inserts into the notify database on the notifications server)
+
+
+on the Notification Server:
+make_overdue_callfiles.plx (creates Asterisk call files from notify database, runs from cron)
+there are cron jobs to scp call files to the Asterisk server, and to clear the call file directory
+
+on the Asterisk Server
+overdue-result.pl (agi program that updates notify database with call result, called from Asterisk dialplan)
+extensions.conf (Asterisk dialplan, we forego answering detection and play the message twice)
+do_overdues.plx (feeds call files to the Asterisk outgoing queue, Asterisk tends to choke if too many call files in the queue, runs from cron)
+there are cron jobs to kill asterisk in the evening and raise it in the morning.
+
+
+** COURTESY MESSAGES **
+on one of the EG cluster's app servers:
+grpl_courtesy.sh (args for the perl script, called from cron)
+grpl_courtesy_email (message template)
+grpl_courtesy.pl (pulls the soon to be due info from EG and sends emails)
+
+
+----------------------------------------------------------
+Bill Ott wrote initial versions of the .cgi files
+
+Traverse Area Distric Library modified this work for their situation, those
+files are under the tadl-egnotify-scripts_20090306 directory.
+
+Doug Kyle
+Grand Rapids Public Library
+
+Questions to help at grpl dot org

Added: grpl/trunk/patron_notifications/do_holdshelf.plx
===================================================================
--- grpl/trunk/patron_notifications/do_holdshelf.plx	                        (rev 0)
+++ grpl/trunk/patron_notifications/do_holdshelf.plx	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,28 @@
+#!/usr/bin/perl -w
+
+my @one = `grep -l Zap/1 /var/spool/asterisk/tmp/holdshelf/*`;
+my @two = `grep  -l Zap/2 /var/spool/asterisk/tmp/holdshelf/*`;
+my @three = `grep -l Zap/3 /var/spool/asterisk/tmp/holdshelf/*`;
+
+map { chop } @one;
+map { chop } @two;
+map { chop } @three;
+my $z1 = 0;
+my $z2 = 0;
+my $z3 = 0;
+my $stop = 0;
+while ( ! $stop ) {
+	if ( ($one[$z1]) && !(`grep -l Zap/1 /var/spool/asterisk/outgoing/*`) ) { system ('mv', $one[$z1], '/var/spool/asterisk/outgoing'); $z1++; }
+	sleep 30;
+        if ( ($two[$z2]) && !(`grep -l Zap/2 /var/spool/asterisk/outgoing/*`) ) { system ('mv', $two[$z2], '/var/spool/asterisk/outgoing'); $z2++; }
+	sleep 30;
+        if ( ($three[$z3]) && !(`grep -l Zap/3 /var/spool/asterisk/outgoing/*`) ) { system ('mv', $three[$z3], '/var/spool/asterisk/outgoing'); $z3++; }
+	sleep 30;
+	$c++;
+	if ( !$one[$z1] && !$two[$z2] && !$three[$z3] ) {
+		$stop = 1;
+	}
+	sleep 30;
+}
+
+


Property changes on: grpl/trunk/patron_notifications/do_holdshelf.plx
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/do_overdues.plx
===================================================================
--- grpl/trunk/patron_notifications/do_overdues.plx	                        (rev 0)
+++ grpl/trunk/patron_notifications/do_overdues.plx	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,28 @@
+#!/usr/bin/perl -w
+
+my @one = `grep -l Zap/1 /var/spool/asterisk/tmp/overdues/*`;
+my @two = `grep  -l Zap/2 /var/spool/asterisk/tmp/overdues/*`;
+my @three = `grep -l Zap/3 /var/spool/asterisk/tmp/overdues/*`;
+my @four = `grep -l Zap/4 /var/spool/asterisk/tmp/overdues/*`;
+map { chop } @one;
+map { chop } @two;
+map { chop } @three;
+map { chop } @four;
+my $c = 0;
+my $stop = 0;
+while ( ! $stop ) {
+        if ($one[$c]) { system ('mv', $one[$c], '/var/spool/asterisk/outgoing') }
+	sleep 30;
+        if ($two[$c]) { system ('mv', $two[$c], '/var/spool/asterisk/outgoing') }
+	sleep 30;
+        if ($three[$c]) { system ('mv', $three[$c], '/var/spool/asterisk/outgoing') }
+	sleep 30;
+	if ($four[$c]) { system ('mv', $four[$c], '/var/spool/asterisk/outgoing') }
+	sleep 30;
+        $c++;
+        if ( !$one[$c] && !$two[$c] && !$three[$c] && !$four[$c] ) {
+                $stop = 1;
+        }
+	sleep 30;
+}
+


Property changes on: grpl/trunk/patron_notifications/do_overdues.plx
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/extensions.conf
===================================================================
--- grpl/trunk/patron_notifications/extensions.conf	                        (rev 0)
+++ grpl/trunk/patron_notifications/extensions.conf	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,299 @@
+;!
+;! Automatically generated configuration file
+;! Filename: extensions.conf (/etc/asterisk/extensions.conf)
+;! Generator: Manager
+;! Creation Date: Mon Jun  2 12:03:36 2008
+;!
+
+[default]
+exten = s,1,Answer
+exten = s,n,AGI(festival-script.pl|This is the Grand Rapids Public Library\, you have items available for pick-up.)
+exten = s,n,Background(tt-thankyou)
+exten = s,n,AGI(record-result.pl|${CHANNEL}-${id})
+exten = s,n,Hangup
+exten = failed,1,AGI(record-result.pl|${CHANNEL}-${id})
+exten => 6001,2,Dial(zap3/1,15)
+
+[from-internal]
+include => record-outboundmsgs
+exten => 500,1,Verbose(1|Echo test application)
+exten => 500,n,Echo()
+exten => 500,n,Hangup()
+
+[holdshelfmsg]
+exten => s,1,Set(TIMEOUT(response)=5)  ; Set Response Timeout to 10 seconds
+exten => s,2,Answer
+exten => s,3,Wait(1)
+exten => s,4,Background(outboundmsgs/holdshelfmsg1)
+exten => s,5,Background(outboundmsgs/${pickuplib})
+exten => s,6,Background(outboundmsgs/${pickuplib}-phone)
+exten => s,7,Background(outboundmsgs/holdshelfmsg1)
+exten => s,8,Background(outboundmsgs/${pickuplib})
+exten => s,9,Background(outboundmsgs/${pickuplib}-phone)
+exten => t,1,Playback(vm-goodbye)
+exten => t,2,AGI(holdshelf-result.pl|${CHANNEL}:${holdid}:${NumberDialed})
+exten => t,3,Hangup
+exten => h,1,DEADAGI(holdshelf-result.pl|${CHANNEL}:${holdid}:${NumberDialed})
+exten => failed,1,Set(NumberDialed=${CUT(PassedInfo,,1)})
+exten => failed,2,Set(CDR(userfield)=${NumberDialed})
+exten = failed,3,AGI(holdshelf-result.pl|${CHANNEL}:${holdid}:${NumberDialed})
+
+[overduemsg]
+exten => s,1,Answer
+exten => s,2,Wait(1)
+exten => s,3,Background(outboundmsgs/overduemsg1)
+exten => s,4,Background(outboundmsgs/GRPL-GR-phone)
+exten => s,5,Wait(3)
+exten => s,6,Background(outboundmsgs/overduemsg1)
+exten => s,7,Background(outboundmsgs/GRPL-GR-phone)
+exten => s,8,AGI(overdue-result.pl|${CHANNEL}:${odid})
+exten => s,9,Hangup
+exten = failed,1,AGI(overdue-result.pl|${CHANNEL}:${odid})
+
+[holdack]
+exten => s,1,Playback(outboundmsgs/thankyou)
+exten => s,2,Playback(vm-goodbye)
+exten => s,3,AGI(holdshelf-result.pl|${CHANNEL}:${holdid}:${NumberDialed}:ack)
+exten => s,4,Hangup
+
+
+[pickupmsg]
+exten => s,1,Set(TIMEOUT(digit)=5)	; Set Digit Timeout to 5 seconds
+exten => s,2,Set(TIMEOUT(response)=10)	; Set Response Timeout to 10 seconds
+exten => s,3,Answer
+exten => s,4,Wait(1)
+exten => s,5,Background(outboundmsgs/msg1)         ; "play outbound msg"
+exten => s,6,Background(outboundmsgs/how_to_ack)   ; "Press 1 to replay or 2 to acknowledge receiving this message"
+exten => 1,1,Goto(s,5)   ; replay message
+exten => 2,1,Goto(msgack,s,1) ; acknowledge message
+exten => t,1,Playback(vm-goodbye)
+exten => t,2,Hangup
+; at this point we could do something like reschedule the call to try again later
+; or send an email saying the msg was not received,
+exten => failed,1,Set(NumberDialed=${CUT(PassedInfo,,1)})
+exten => failed,2,Set(CDR(userfield)=${NumberDialed})
+exten = failed,3,AGI(record-result.pl|${CHANNEL}-${id})
+
+[msgack]
+exten => s,1,Playback(outboundmsgs/thankyou)
+exten => s,2,Playback(vm-goodbye)
+; at this point we might want to log the message acknowledgement somewhere
+; and perhaps trigger some additional processing
+exten => s,3,AGI(record-result.pl|${CHANNEL}:${holdid}:ack)
+exten => s,4,Hangup
+
+[record-outboundmsgs]
+; Record voice files
+;
+; Before using this the first time
+;    mkdir /var/lib/asterisk/sounds/outboundmsgs
+;    chown asterisk_user:asterisk_user /var/lib/asterisk/sounds/outboundmsgs
+;    (Where asterisk_user = the user that asterisk runs under: = root for many installations)
+;
+; In a context for incoming calls put something like
+;  include => record-outboundmsgs
+;
+; Then call
+;   2051 to Record a new outbound msg1
+;   2052 to Record a new outbound holdshelfmsg1
+;
+;   2061 to Record the msg played when the recipient acks the message
+;   2062 to Record the "How to ACK message"
+;
+; After dialing one of the extensions above:
+;   Wait for the record start tone
+;   Record your message
+;   Press # to stop recording
+;   Listen to an automatic playback of your new message
+;
+; outbound msg1
+exten => 2051,1,Wait(2)
+exten => 2051,2,Record(outboundmsgs/msg1:gsm)
+exten => 2051,3,Wait(2)
+exten => 2051,4,Playback(outboundmsgs/msg1)
+exten => 2051,5,wait(2)
+exten => 2051,6,Hangup
+;
+; outbound holdshelfmsg1
+exten => 2052,1,Wait(2)
+exten => 2052,2,Record(outboundmsgs/holdshelfmsg1:gsm)
+exten => 2052,3,Wait(2)
+exten => 2052,4,Playback(outboundmsgs/holdshelfmsg1)
+exten => 2052,5,wait(2)
+exten => 2052,6,Hangup
+;
+; outbound overduemsg1
+exten => 2053,1,Wait(2)
+exten => 2053,2,Record(outboundmsgs/overduemsg1:gsm)
+exten => 2053,3,Wait(2)
+exten => 2053,4,Playback(outboundmsgs/overduemsg1)
+exten => 2053,5,wait(2)
+exten => 2053,6,Hangup
+;
+; Msg played when msg is acked
+exten => 2061,1,Wait(2)
+exten => 2061,2,Record(outboundmsgs/thankyou:gsm)
+exten => 2061,3,Wait(2)
+exten => 2061,4,Playback(outboundmsgs/thankyou)
+exten => 2061,5,wait(2)
+exten => 2061,6,Hangup
+;
+; Msg played after outbound msg: "Press 1 to replay or 2 to acknowledge receiving this message"
+exten => 2062,1,Wait(2)
+exten => 2062,2,Record(outboundmsgs/how_to_ack:gsm)
+exten => 2062,3,Wait(2)
+exten => 2062,4,Playback(outboundmsgs/how_to_ack)
+exten => 2062,5,wait(2)
+exten => 2062,6,Hangup
+;
+; main
+exten => 2071,1,Wait(2)
+exten => 2071,2,Record(outboundmsgs/GRPL-GR:gsm)
+exten => 2071,3,Wait(2)
+exten => 2071,4,Playback(outboundmsgs/GRPL-GR)
+exten => 2071,5,wait(2)
+exten => 2071,6,Hangup
+;
+; main phone
+exten => 2171,1,Wait(2)
+exten => 2171,2,Record(outboundmsgs/GRPL-GR-phone:gsm)
+exten => 2171,3,Wait(2)
+exten => 2171,4,Playback(outboundmsgs/GRPL-GR-phone)
+exten => 2171,5,wait(2)
+exten => 2171,6,Hangup
+;
+; madison
+exten => 2072,1,Wait(2)
+exten => 2072,2,Record(outboundmsgs/GRPL-GM:gsm)
+exten => 2072,3,Wait(2)
+exten => 2072,4,Playback(outboundmsgs/GRPL-GM)
+exten => 2072,5,wait(2)
+exten => 2072,6,Hangup
+;
+; madison phone
+exten => 2172,1,Wait(2)
+exten => 2172,2,Record(outboundmsgs/GRPL-GM-phone:gsm)
+exten => 2172,3,Wait(2)
+exten => 2172,4,Playback(outboundmsgs/GRPL-GM-phone)
+exten => 2172,5,wait(2)
+exten => 2172,6,Hangup
+;
+; ottawa
+exten => 2073,1,Wait(2)
+exten => 2073,2,Record(outboundmsgs/GRPL-GO:gsm)
+exten => 2073,3,Wait(2)
+exten => 2073,4,Playback(outboundmsgs/GRPL-GO)
+exten => 2073,5,wait(2)
+exten => 2073,6,Hangup
+;
+; ottawa phone
+exten => 2173,1,Wait(2)
+exten => 2173,2,Record(outboundmsgs/GRPL-GO-phone:gsm)
+exten => 2173,3,Wait(2)
+exten => 2173,4,Playback(outboundmsgs/GRPL-GO-phone)
+exten => 2173,5,wait(2)
+exten => 2173,6,Hangup
+;
+; seymour
+exten => 2074,1,Wait(2)
+exten => 2074,2,Record(outboundmsgs/GRPL-GS:gsm)
+exten => 2074,3,Wait(2)
+exten => 2074,4,Playback(outboundmsgs/GRPL-GS)
+exten => 2074,5,wait(2)
+exten => 2074,6,Hangup
+;
+; seymour phone
+exten => 2174,1,Wait(2)
+exten => 2174,2,Record(outboundmsgs/GRPL-GS-phone:gsm)
+exten => 2174,3,Wait(2)
+exten => 2174,4,Playback(outboundmsgs/GRPL-GS-phone)
+exten => 2174,5,wait(2)
+exten => 2174,6,Hangup
+;
+; vanbelkum
+exten => 2075,1,Wait(2)
+exten => 2075,2,Record(outboundmsgs/GRPL-GC:gsm)
+exten => 2075,3,Wait(2)
+exten => 2075,4,Playback(outboundmsgs/GRPL-GC)
+exten => 2075,5,wait(2)
+exten => 2075,6,Hangup
+;
+; vanbelkum phone
+exten => 2175,1,Wait(2)
+exten => 2175,2,Record(outboundmsgs/GRPL-GC-phone:gsm)
+exten => 2175,3,Wait(2)
+exten => 2175,4,Playback(outboundmsgs/GRPL-GC-phone)
+exten => 2175,5,wait(2)
+exten => 2175,6,Hangup
+;
+; wleonard
+exten => 2076,1,Wait(2)
+exten => 2076,2,Record(outboundmsgs/GRPL-GN:gsm)
+exten => 2076,3,Wait(2)
+exten => 2076,4,Playback(outboundmsgs/GRPL-GN)
+exten => 2076,5,wait(2)
+exten => 2076,6,Hangup
+;
+; wleonard phone
+exten => 2176,1,Wait(2)
+exten => 2176,2,Record(outboundmsgs/GRPL-GN-phone:gsm)
+exten => 2176,3,Wait(2)
+exten => 2176,4,Playback(outboundmsgs/GRPL-GN-phone)
+exten => 2176,5,wait(2)
+exten => 2176,6,Hangup
+;
+; westside
+exten => 2077,1,Wait(2)
+exten => 2077,2,Record(outboundmsgs/GRPL-GW:gsm)
+exten => 2077,3,Wait(2)
+exten => 2077,4,Playback(outboundmsgs/GRPL-GW)
+exten => 2077,5,wait(2)
+exten => 2077,6,Hangup
+;
+; westside phone
+exten => 2177,1,Wait(2)
+exten => 2177,2,Record(outboundmsgs/GRPL-GW-phone:gsm)
+exten => 2177,3,Wait(2)
+exten => 2177,4,Playback(outboundmsgs/GRPL-GW-phone)
+exten => 2177,5,wait(2)
+exten => 2177,6,Hangup
+;
+; yankee
+exten => 2078,1,Wait(2)
+exten => 2078,2,Record(outboundmsgs/GRPL-GY:gsm)
+exten => 2078,3,Wait(2)
+exten => 2078,4,Playback(outboundmsgs/GRPL-GY)
+exten => 2078,5,wait(2)
+exten => 2078,6,Hangup
+;
+; yankee phone
+exten => 2178,1,Wait(2)
+exten => 2178,2,Record(outboundmsgs/GRPL-GY-phone:gsm)
+exten => 2178,3,Wait(2)
+exten => 2178,4,Playback(outboundmsgs/GRPL-GY-phone)
+exten => 2178,5,wait(2)
+exten => 2178,6,Hangup
+;
+[asterisk_guitools]
+exten = executecommand,1,System(${command})
+exten = executecommand,n,Hangup()
+exten = record_vmenu,1,Answer
+exten = record_vmenu,n,Playback(vm-intro)
+exten = record_vmenu,n,Record(${var1})
+exten = record_vmenu,n,Playback(vm-saved)
+exten = record_vmenu,n,Playback(vm-goodbye)
+exten = record_vmenu,n,Hangup
+exten = play_file,1,Answer
+exten = play_file,n,Playback(${var1})
+exten = play_file,n,Hangup
+hasbeensetup = Y
+
+[numberplan-custom-1]
+plancomment = DialPlan1
+include = default
+include = parkedcalls
+
+[timebasedrules]
+
+[DID_trunk_2]
+include = default

Added: grpl/trunk/patron_notifications/grpl_courtesy.pl
===================================================================
--- grpl/trunk/patron_notifications/grpl_courtesy.pl	                        (rev 0)
+++ grpl/trunk/patron_notifications/grpl_courtesy.pl	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,555 @@
+#!/usr/bin/perl
+# ---------------------------------------------------------------
+# Generates the overdue notices XML file
+# ./eg_gen_overdue.pl <bootstap> 0
+#		generates today's notices
+# ./eg_gen_overdue.pl <bootstap> 1 0
+#		generates notices for today - 1 and today
+# ./eg_gen_overdue.pl <bootstap> 2 1 0  
+# ./eg_gen_overdue.pl <bootstap> 3 2 1 0  etc...
+# ---------------------------------------------------------------
+
+
+
+use strict; use warnings;
+require '/openils/src/Evergreen-ILS-1.2.2.3/Open-ILS/src/support-scripts/oils_header.pl';
+use vars qw/$logger $apputils/;
+use Data::Dumper;
+use OpenILS::Const qw/:const/;
+use OpenILS::Application::AppUtils;
+use DateTime;
+use Email::Send;
+use DateTime::Format::ISO8601;
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::JSON;
+use Unicode::Normalize;
+use OpenILS::Const qw/:const/;
+use OpenSRF::AppSession;
+
+my $U = 'OpenILS::Application::AppUtils';
+
+my $SEND_EMAILS = 1;
+#my $SEND_EMAILS = 0;
+
+my $bsconfig = shift || die "usage: $0 <bootstrap_config>\n";
+my @goback = @ARGV;
+ at goback = (0) unless @goback;
+# osrf_connect($bsconfig);
+osrf_connect("/openils/conf/opensrf_core.xml");
+my $e = OpenILS::Utils::CStoreEditor->new;
+
+my $smtp = $ENV{EG_OVERDUE_SMTP_HOST};
+my $mail_sender = $ENV{EG_OVERDUE_EMAIL_SENDER};
+
+# ---------------------------------------------------------------
+# Set up the email template
+my $etmpl = $ENV{EG_OVERDUE_EMAIL_TEMPLATE};
+my $email_template;
+if( open(F,"$etmpl") ) {
+	my @etmpl = <F>;
+	$email_template = join('', at etmpl);
+	close(F);
+}
+# ---------------------------------------------------------------
+
+
+
+my @date = CORE::localtime;
+my $sec  = $date[0];
+my $min  = $date[1];
+my $hour = $date[2];
+my $day  = $date[3];
+my $mon  = $date[4] + 1;
+my $year = $date[5] + 1900;
+
+my %USER_CACHE;
+my %ORG_CACHE;
+
+
+print_notices($_) for @goback;
+
+
+# -----------------------------------------------------------------------
+# -----------------------------------------------------------------------
+
+
+sub print_notices {
+	my $goback = shift || 0;
+
+	for my $day ( qw/ -3 / ) { #grpl
+		my ($start, $end) = make_date_range($day + $goback);
+
+		$logger->info("OD_notice: process date range $start -> $end");
+
+		my $query = [
+			{
+				xact_finish => undef,
+				due_date => { between => [ $start, $end ] },
+				circ_lib => { between => [ 10, 17 ] }, #grpl
+			},
+			{ order_by => { circ => 'usr, circ_lib' } }
+		];
+		#my $circs = $e->search_action_circulation($query, {idlist=>1});
+
+        my $ses = OpenSRF::AppSession->create('open-ils.cstore');
+        my $req = $ses->request('open-ils.cstore.direct.action.circulation.id_list', @$query);
+        my $circs = [];
+        my $resp;
+        push(@$circs, $resp->content) while ($resp = $req->recv(timeout=>600));
+
+		process_circs( $circs, "${day}day" );
+	}
+}
+
+
+sub process_circs {
+	my $circs = shift;
+	my $range = shift;
+
+	return unless @$circs;
+
+	$logger->info("OD_notice: processing range $range and ".scalar(@$circs)." potential circs");
+
+	my $org; 
+	my $patron;
+	my @current;
+
+	my $x = 0;
+	for my $circ (@$circs) {
+		$circ = $e->retrieve_action_circulation($circ);
+
+		if( !defined $org or 
+				$circ->circ_lib != $org  or $circ->usr ne $patron ) {
+			$org = $circ->circ_lib;
+			$patron = $circ->usr;
+			print_notice( $range, \@current ) if @current;
+			@current = ();
+		}
+
+		push( @current, $circ );
+		$x++;
+	}
+
+	$logger->info("OD_notice: processed $x circs");
+	print_notice( $range, \@current );
+}
+
+sub make_date_range {
+	my $daysback = shift;
+
+	my $epoch = CORE::time - ($daysback * 24 * 60 * 60);
+	my $date = DateTime->from_epoch( epoch => $epoch, time_zone => 'local');
+
+	$date->set_hour(0);
+	$date->set_minute(0);
+	$date->set_second(0);
+	my $start = "$date";
+	
+	$date->set_hour(23);
+	$date->set_minute(59);
+	$date->set_second(59);
+
+	return ($start, "$date");
+}
+
+
+sub print_notice {
+	my( $range, $circs ) = @_;
+	return unless @$circs;
+
+	my $days_over = $range; #grpl
+        $days_over =~ s/'day'//; #grpl
+
+	my $s1 = scalar(@$circs);
+	
+	# we don't charge for lost or claimsreturned
+	$circs = [ 
+		grep {
+			!$_->stop_fines or (
+				$_->stop_fines ne OILS_STOP_FINES_LOST and
+				$_->stop_fines ne OILS_STOP_FINES_CLAIMSRETURNED 
+			)
+		} @$circs 
+	];
+
+	return unless @$circs;
+
+	my $s2 = $s1 - scalar(@$circs);
+	$logger->info("OD_notice: dropped $s2 lost/CR from processing...") if $s2;
+
+	my $org = $circs->[0]->circ_lib;
+
+
+	my $usr = $circs->[0]->usr;
+	$logger->debug("OD_notice: printing $range user:$usr org:$org");
+
+	my @patron_data = fetch_patron_data($usr);
+	my @org_data = fetch_org_data($org);
+
+	return unless (@patron_data and @org_data);
+
+	# $sth->execute($days_over,$patron_data[10]); #grpl
+
+	my $email;
+
+	if( $email = $patron_data[0]->email and $SEND_EMAILS
+		and $email =~ /.+\@.+/ ) {
+			send_email($range, \@patron_data, \@org_data, $circs);
+
+#	} else {
+#		if( $patron_data[9] ) {
+
+			print "\t\t<notice type='courtesy' count='$range'>\n";
+			print_patron_xml_chunk(@patron_data);
+			print_org_xml_chunk(@org_data);
+			print_circ_chunk($_) for @$circs;
+			print "\t\t</notice>\n";
+
+#		} else {
+#			# There is no zip, therefore no address.
+#			$logger->warn("OD_notice: unable to send mail notification for $usr due to lack of valid address");
+#		}
+	}
+}
+
+
+sub fetch_patron_data {
+	my $user_id = shift;
+
+	my $patron = $USER_CACHE{$user_id};
+
+	if( ! $patron ) {
+		$logger->debug("OD_notice:   fetching patron $user_id");
+
+		$patron = $e->retrieve_actor_user(
+			[
+				$user_id,
+				{
+					flesh => 1,
+					flesh_fields => { 
+						'au' => [qw/ card billing_address mailing_address /] 
+					}
+				}
+			]
+		) or return handle_event($e->event);
+
+		$USER_CACHE{$user_id} = $patron;
+	}
+
+	my $bc = $patron->card->barcode;
+	my $fn = $patron->first_given_name;
+	my $mn = $patron->second_given_name;
+	my $ln = $patron->family_name;
+
+	my ( $s1, $s2, $city, $state, $zip );
+
+	my $baddr = $patron->mailing_address;
+	unless( $baddr and $U->is_true($baddr->valid) ) {
+		$baddr = $patron->billing_address;
+		$baddr = undef unless( $baddr and $U->is_true($baddr->valid) );
+	}
+
+	if( $baddr ) {
+		$s1		= $baddr->street1;
+		$s2		= $baddr->street2;
+		$city		= $baddr->city;
+		$state	= $baddr->state;
+		$zip		= $baddr->post_code;
+	}
+
+	my $pn = $patron->day_phone; #grpl
+
+	$bc = entityize($bc);
+	$fn = entityize($fn);
+	$mn = entityize($mn);
+	$ln = entityize($ln);
+	$s1 = entityize($s1);
+	$s2 = entityize($s2);
+	$city  = entityize($city);
+	$state = entityize($state);
+	$zip	 = entityize($zip);
+	$pn = entityize($pn); #grpl
+
+	return ( $patron, $bc, $fn, $mn, $ln, $s1, $s2, $city, $state, $zip, $pn ); #grpl
+}
+
+	
+sub print_patron_xml_chunk {
+	my( $patron, $bc, $fn, $mn, $ln, $s1, $s2, $city, $state, $zip ) = @_;
+	my $pid = $patron->id;
+	print <<"	XML";
+			<patron>
+				<id type="barcode">$bc</id>
+				<fullname>$fn $mn $ln</fullname>
+				<street1>$s1 $s2</street1>
+				<city_state_zip>$city, $state $zip</city_state_zip>
+				<sys_id>$pid</sys_id>
+			</patron>
+	XML
+}
+
+
+sub fetch_org_data {
+	my $org_id = shift;
+
+	my $org = $ORG_CACHE{$org_id};
+
+	if( ! $org ) {
+		$logger->debug("OD_notice:   fetching org $org_id");
+
+		$org = $e->retrieve_actor_org_unit(
+			[
+				$org_id,
+				{
+					flesh => 1, 
+					flesh_fields => 
+						{ aou => [ qw/billing_address mailing_address/ ] }
+				}
+			]
+		) or return handle_event($e->event);
+
+		$ORG_CACHE{$org_id} = $org;
+	}
+
+	my $name = $org->name;
+	my $phone = $org->phone;
+	my $email = $org->email;
+
+
+	my( $s1, $s2, $city, $state, $zip );
+	my $baddr = $org->billing_address || $org->mailing_address;
+	if( $baddr ) {
+		$s1		= $baddr->street1;
+		$s2		= $baddr->street2;
+		$city		= $baddr->city;
+		$state	= $baddr->state;
+		$zip		= $baddr->post_code;
+	}
+
+	$name  = entityize($name);
+	$phone = entityize($phone);
+	$s1	 = entityize($s1);
+	$s2	 = entityize($s2);
+	$city  = entityize($city);
+	$state = entityize($state);
+	$zip	 = entityize($zip);
+	$email = entityize($email);
+
+	return ( $org, $name, $phone, $s1, $s2, $city, $state, $zip, $email );
+}
+
+
+sub print_org_xml_chunk {
+	my( $org, $name, $phone, $s1, $s2, $city, $state, $zip, $email ) = @_;
+	print <<"	XML";
+			<library>
+				<libname>$name</libname>
+				<libphone>$phone</libphone>
+				<libstreet1>$s1 $s2</libstreet1>
+				<libcity_state_zip>$city, $state $zip</libcity_state_zip>
+			</library>
+	XML
+}
+
+
+sub fetch_circ_data {
+	my $circ = shift;
+
+	my $title;
+	my $author;
+	my $cn;
+
+	my $d = $circ->due_date;
+	$d =~ s/[T ].*//og; # just for logging
+	$logger->debug("OD_notice:   processing circ ".$circ->id." $d");
+
+	my $due = DateTime::Format::ISO8601->new->parse_datetime(
+		clense_ISO8601($circ->due_date));
+
+	my $day  = $due->day;
+	my $mon  = $due->month;
+	my $year = $due->year;
+
+	my $copy = $e->retrieve_asset_copy($circ->target_copy)
+		or return handle_event($e->event);
+   
+        my $loc = $copy->location;
+        my $cl = $e->retrieve_asset_copy_location($loc);
+        $cl = $cl->[6];
+
+	my $bc = $copy->barcode;
+
+	if( $copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
+		$title = $copy->dummy_title || "";
+		$author = $copy->dummy_author || "";
+
+	} else {
+
+		my $volume = $e->retrieve_asset_call_number(
+			[
+				$copy->call_number,
+				{
+					flesh => 1,
+					flesh_fields => {
+						acn => [ qw/record/ ]
+					}
+				}
+			]
+		) or return handle_event($e->event);
+
+		$cn = $volume->label;
+		my $mods = $apputils->record_to_mvr($volume->record);
+		if( $mods ) {
+			$title = $mods->title || "";
+			$author = $mods->author || "";
+		}
+	}
+
+	$title = entityize($title);
+	$author = entityize($author);
+	$cn = entityize($cn);
+	$bc = entityize($bc);
+
+        my $xact = $circ->[13];
+        my $fine = simplereq('open-ils.cstore', 'open-ils.cstore.direct.money.billable_transaction_summary.retrieve', $xact);
+        $fine = $fine->[3];
+
+        my $co = DateTime::Format::ISO8601->new->parse_datetime(
+                clense_ISO8601($circ->xact_start));
+        my $co_day = $co->day;
+        my $co_mon = $co->month;
+        my $co_year = $co->year;
+        my $co_date = "$co_mon/$co_day/$co_year";
+
+        my $pr = $copy->price;
+
+	return( $title, $author, $cn, $bc, $day, $mon, $year, $co_date, $pr, $cl, $fine );
+}
+
+
+sub print_circ_chunk {
+	my $circ = shift;
+	my ( $title, $author, $cn, $bc, $day, $mon, $year, $co, $pr, $cl, $fine ) = fetch_circ_data($circ);
+	my $cid = $circ->id;
+	print <<"	XML";
+			<item>
+				<title>$title</title>
+				<author>$author</author>
+				<duedate>$mon/$day/$year</duedate>
+				<callno>$cn</callno>
+				<location>$cl</location>
+				<barcode>$bc</barcode>
+				<circ_id>$cid</circ_id>
+				<check_out>$co</check_out>
+				<item_price>$pr</item_price>
+				<fine>$fine</fine>
+			</item>
+	XML
+}
+
+
+
+sub send_email {
+	my( $range, $patron_data, $org_data, $circs ) = @_;
+	my( $org, $org_name, $org_phone, $org_s1, $org_s2, $org_city, $org_state, $org_zip, $org_email ) = @$org_data;
+	my( $patron, $bc, $fn, $mn, $ln, $user_s1, $user_s2, $user_city, $user_state, $user_zip ) = @$patron_data;
+
+	return unless $SEND_EMAILS;
+
+	my $pemail = $patron_data->[0]->email;
+
+	my $tmpl = $email_template;
+	my @time = localtime;
+	my $year = $time[5] + 1900;
+	my $mon  = $time[4] + 1;
+	my $day  = $time[3];
+
+#	my $r = ($range eq '7day') ? 7 : 14;
+	my $r = $range; #grpl
+	$r =~ s/'day'//; #grpl
+
+	# - default to the global sender for the errors-to header
+	my $errors_to = $mail_sender;
+
+	# if they have an org setting for errors-to, use that as the errors-to address
+	if( my $set = $e->search_actor_org_unit_setting( 
+			{ name => 'org.bounced_emails', org_unit => $org->id } )->[0] ) {
+
+		my $bemail = OpenSRF::Utils::JSON->JSON2perl($set->value);
+		$errors_to = $bemail if $bemail;
+	}
+
+
+	$tmpl =~ s/\${EMAIL_RECIPIENT}/$pemail/;
+	$tmpl =~ s/\${EMAIL_SENDER}/$errors_to/o; 
+	$tmpl =~ s/\${EMAIL_REPLY_TO}/$errors_to/;
+	$tmpl =~ s/\${EMAIL_ERRORS_TO}/$errors_to/;
+   $tmpl =~ s/\${EMAIL_HEADERS}//; # - we have no additional headers to add
+
+   $tmpl =~ s/\${RANGE}/$r/;
+   $tmpl =~ s/\${DATE}/$mon\/$day\/$year/;
+   $tmpl =~ s/\${FIRST_NAME}/$fn/;
+   $tmpl =~ s/\${MIDDLE_NAME}/$mn/;
+   $tmpl =~ s/\${LAST_NAME}/$ln/;
+
+	my ($itmpl) = $tmpl =~ /\${COURTESY_ITEMS\[(.*)\]}/ms;
+
+	my $items = '';
+	for my $circ (@$circs) {
+		my $circtmpl = $itmpl;
+		my ( $title, $author, $cn, $bc, $due_day, $due_mon, $due_year, $co ) = fetch_circ_data($circ);
+		$circtmpl =~ s/\${TITLE}/$title/o;
+		$circtmpl =~ s/\${AUTHOR}/$author/o;
+		$circtmpl =~ s/\${CALL_NUMBER}/$cn/o;
+		$circtmpl =~ s/\${DUE_DAY}/$due_day/o;
+		$circtmpl =~ s/\${DUE_MONTH}/$due_mon/o;
+		$circtmpl =~ s/\${DUE_YEAR}/$due_year/o;
+		$circtmpl =~ s/\${ITEM_BARCODE}/$bc/o;
+		$circtmpl =~ s/\${ITEM_OUT}/$co/o;
+		$items .= "$circtmpl\n";
+	}
+
+	$tmpl =~ s/\${COURTESY_ITEMS\[.*\]}/$items/ms;
+
+#	my $org_addr = "$org_s1 $org_s2 $org_city, $org_state $org_zip";
+#	$tmpl =~ s/\${ORG_NAME}/$org_name/o;
+#	$tmpl =~ s/\${ORG_ADDRESS}/$org_addr/o;
+#	$tmpl =~ s/\${ORG_PHONE}/$org_phone/o;
+
+	$logger->debug("OD_notice: sending email to $pemail: $tmpl");
+
+	my $sender = Email::Send->new({mailer => 'SMTP'});
+	$sender->mailer_args([Host => $smtp]);
+
+	my $stat = $sender->send($tmpl);
+
+	if( $stat and $stat->type eq 'success' ) {
+		$logger->info("OD_notice:   successfully sent overdue email");
+	} else {
+		$logger->warn("OD_notice:   unable to send hold overdue email: ".Dumper($stat));
+	}
+
+	$logger->info("OD_notice:   sending email to".$patron_data->[0]->email);
+}
+
+sub handle_event {
+	my $evt = shift;
+	warn "OD_notice: ".Dumper($evt) . "\n";
+	$logger->error("OD_notice: ".Dumper($evt));
+	return undef;
+}
+
+
+sub entityize {
+	my $stuff = shift || return "";
+	$stuff =~ s/\</&lt;/og;
+	$stuff =~ s/\>/&gt;/og;
+	$stuff =~ s/\&/&amp;/og;
+	$stuff = NFC($stuff);
+	$stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
+	return $stuff;
+}
+
+
+
+


Property changes on: grpl/trunk/patron_notifications/grpl_courtesy.pl
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/grpl_courtesy.sh
===================================================================
--- grpl/trunk/patron_notifications/grpl_courtesy.sh	                        (rev 0)
+++ grpl/trunk/patron_notifications/grpl_courtesy.sh	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,30 @@
+#!/bin/bash
+# ---------------------------------------------------------------
+# This file runs the overdue generation script.
+# If today is Monday, it runs the script for Sat/Sun/Mon, 
+# otherwise it runs once per day.
+# ---------------------------------------------------------------
+
+
+
+
+SSH_CLIENT=$1
+RECIPIENT=$2;
+DATE=$(date +%Y-%m-%d);
+DAY=$(date +%u);
+BSCONFIG="/openils/conf/opensrf_core.xml"
+ODDIR="/openils/var/data/courtesy";
+
+export EG_OVERDUE_EMAIL_TEMPLATE="grpl_courtesy_email";
+export EG_OVERDUE_SMTP_HOST="your_email_host";
+export EG_OVERDUE_EMAIL_SENDER="courtesy_notice at your_domain";
+
+[ $(whoami) != "opensrf" ] && echo "Must be run as opensrf" && exit 1;
+source ~/.bashrc;
+ARGS="0"
+
+#[ $DAY == 6 -o $DAY == 7 ] && exit 0; # don't run on saturday or sunday
+#if [ $DAY == 1 ]; then ARGS="2 1 0"; fi; # If today is monday, run for sat/sun/mon
+
+./grpl_courtesy.pl $BSCONFIG $ARGS > "$ODDIR/grpl_courtesy.$DATE.xml"
+


Property changes on: grpl/trunk/patron_notifications/grpl_courtesy.sh
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/grpl_courtesy_email
===================================================================
--- grpl/trunk/patron_notifications/grpl_courtesy_email	                        (rev 0)
+++ grpl/trunk/patron_notifications/grpl_courtesy_email	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,32 @@
+To: ${EMAIL_RECIPIENT}
+From: ${EMAIL_SENDER}
+Reply-To: ${EMAIL_REPLY_TO}
+Errors-To: ${EMAIL_ERRORS_TO}
+Subject: Library Courtesy Notice
+${EMAIL_HEADERS}
+
+
+${DATE}
+Dear ${FIRST_NAME} ${MIDDLE_NAME} ${LAST_NAME}
+
+This is a reminder that the following items will be due in 3 days.
+To avoid overdue charges, please return them on or before the due
+date. Some items may be renewed online at
+
+${COURTESY_ITEMS[
+AUTHOR: ${AUTHOR}
+TITLE: ${TITLE}
+CALL NO: ${CALL_NUMBER}
+BARCODE: ${ITEM_BARCODE}
+DUE: ${DUE_MONTH}/${DUE_DAY}/${DUE_YEAR}
+DATE CHECKED OUT: ${ITEM_OUT}
+]}
+
+This an an automated message, please do not reply to it.  Instead
+if you have questions about this notice, please contact the library
+where you checked the item(s) out.
+
+Grand Rapids - Main / 111 Library St NE  Grand Rapids, MI 49503
+616-988-5400
+
+

Added: grpl/trunk/patron_notifications/grpl_holdnotice.cgi
===================================================================
--- grpl/trunk/patron_notifications/grpl_holdnotice.cgi	                        (rev 0)
+++ grpl/trunk/patron_notifications/grpl_holdnotice.cgi	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+require '/openils/bin/support-scripts/oils_header.pl';
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use CGI qw(:standard);
+use DateTime;
+use DateTime::Format::ISO8601;
+
+print "Content-type: text/html\n\n";
+
+my $conf = '/openils/conf/opensrf_core.xml';
+
+my $holdid = param('h');
+my $status = param('s');
+
+my $d = DateTime->now;
+$d->add(days=>10);
+
+our %grpl_config;
+do '/openils/conf/grpl-egauth-setup.pl';
+osrf_connect($conf);
+my $authtok = oils_login($grpl_config{usr},$grpl_config{pw});
+
+
+$we = new_editor(xact=>1, authtoken => $auth);
+my $notify = Fieldmapper::action::hold_notification->new;
+$notify->hold($holdid);
+$notify->notify_time('now');
+$notify->method($status);
+$notify->notify_staff(1);
+$we->create_action_hold_notification($notify)
+	or return $we->die_event;
+$we->commit;
+
+


Property changes on: grpl/trunk/patron_notifications/grpl_holdnotice.cgi
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/grpl_holdshelf.cgi
===================================================================
--- grpl/trunk/patron_notifications/grpl_holdshelf.cgi	                        (rev 0)
+++ grpl/trunk/patron_notifications/grpl_holdshelf.cgi	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,90 @@
+#!/usr/bin/perl
+
+require '/openils/bin/support-scripts/oils_header.pl';
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use CGI qw(:standard);
+
+print "Content-type: text/html\n\n";
+
+my $conf = '/openils/conf/opensrf_core.xml';
+
+our %grpl_config;
+do '/openils/conf/grpl-egauth-setup.pl';
+osrf_connect($conf);
+my $authtok = oils_login($grpl_config{usr},$grpl_config{pw});
+my @date = localtime;
+my $today = ($date[5] + 1900) .'-'. ($date[4] + 1) .'-'. $date[3];
+my $tomorrow = ($date[5] + 1900) .'-'. ($date[4] + 1) .'-'. ($date[3] + 1);
+my $cdate = simplereq( ACTOR(),'open-ils.actor.org_unit.closed.retrieve.all', $authtok, { orgid => 10, start_date => $today, end_date => $tomorrow });
+exit if ($cdate->[0]);
+
+foreach my $loc (10..17) {
+	my $hrs = simplereq( ACTOR(), 'open-ils.actor.org_unit.hours_of_operation.retrieve', $authtok, $loc);
+	my $hr_op = wrap_perl($hrs);
+	my $dow = 'dow_' . ($date[6] - 1) . '_open';
+	next if ($hr_op->{$dow} eq '00:00:00');
+
+	my $ses = OpenSRF::AppSession->create( "open-ils.circ" );
+	my $req = $ses->request('open-ils.circ.captured_holds.on_shelf.retrieve', $authtok, $loc);
+	while(my $resp = $req->recv(timeout => 120)) {
+		my $hid = $resp->content;
+                my $hd = simplereq( CIRC(), 'open-ils.circ.hold.details.retrieve', $authtok, $$hid[14]);
+                my $hh = wrap_perl($hd);
+
+		my $avtime;
+		if ($hh->{hold}->{transit}) {
+			$avtime = $hh->{hold}->{transit}->{dest_recv_time}
+		}
+		my $pnotify = $hh->{hold}->{phone_notify};
+                my $p  = simplereq( ACTOR(),'open-ils.actor.user.fleshed.retrieve', $authtok, $hh->{hold}->{usr});
+                my $ph = wrap_perl($p);
+		if ( !$pnotify && ($hh->{hold}->{id} < 9454) ) {
+			$pnotify = $ph->{day_phone};
+		}
+		$pnotify =~ s/(\d+)\D+(\d+)\D+(\d+).*/$1$2$3/;
+                $pnotify =~ s/\(//;
+                $pnotify =~ s/\///g;
+		foreach my $sc (@{$p}[10]) {
+			foreach my $v (@{$sc}) { 
+				if ($v->{stat_cat_entry} eq 'Block Calls') {
+					$pnotify = 'Block Calls';
+				}
+			}
+		}
+                my $o = simplereq(ACTOR(), 'open-ils.actor.org_unit.retrieve', $authtok, $loc);
+                my $oh = wrap_perl($o);
+                my $oa = simplereq(ACTOR(), 'open-ils.actor.org_unit.address.retrieve', $oh->{mailing_address});
+                my $oah= wrap_perl($oa);
+		$avtime = $hh->{hold}->{capture_time} unless $avtime;
+		$avtime =~ s/(\d\d\d\d-\d\d-\d\d)T(\d\d:\d\d:\d\d).*/$1 $2/;
+		print "$hh->{hold}->{id}|$ph->{first_given_name}|$ph->{family_name}|$ph->{mailing_address}->{street1}|$ph->{mailing_address}->{city}|$ph->{mailing_address}->{state}|$ph->{mailing_address}->{post_code}|$ph->{email}|$hh->{hold}->{email_notify}|$pnotify|$hh->{hold}->{notify_time}|$avtime|$hh->{mvr}->{title}|$hh->{mvr}->{author}|$oh->{shortname}|$oah->{street1}|$oah->{city}|$oah->{state}|$oah->{post_code}|$ph->{card}->{barcode}||\n" unless $hh->{hold}->{notify_time};
+#        }
+   } # end while
+} # end foreach
+
+sub wrap_perl {
+   my $obj = shift;
+   my $ref = ref($obj);
+
+   if ($ref =~ /^Fieldmapper/o) {
+      $ref = $obj->json_hint;
+      $obj = $obj->to_bare_hash;
+   }
+
+   if( $ref eq 'HASH' ) {
+      $obj->{$_} = wrap_perl( $obj->{$_} ) for (keys %$obj);
+   } elsif( $ref eq 'ARRAY' ) {
+      $obj->[$_] = wrap_perl( $obj->[$_] ) for(0..scalar(@$obj) - 1 );
+   } elsif( $ref ) {
+      if(UNIVERSAL::isa($obj, 'HASH')) {
+         $obj->{$_} = wrap_perl( $obj->{$_} ) for (keys %$obj);
+         bless($obj, 'HASH'); # so our parser won't add the hints
+      } elsif(UNIVERSAL::isa($obj, 'ARRAY')) {
+         $obj->[$_] = wrap_perl( $obj->[$_] ) for(0..scalar(@$obj) - 1);
+         bless($obj, 'ARRAY'); # so our parser won't add the hints
+      }
+#      $obj = { $CLASS_KEY => $ref, $PAYLOAD_KEY => $obj };
+   }
+   return $obj;
+}
+


Property changes on: grpl/trunk/patron_notifications/grpl_holdshelf.cgi
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/grpl_overdue.pl
===================================================================
--- grpl/trunk/patron_notifications/grpl_overdue.pl	                        (rev 0)
+++ grpl/trunk/patron_notifications/grpl_overdue.pl	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,572 @@
+#!/usr/bin/perl
+# ---------------------------------------------------------------
+# Generates the overdue notices XML file
+# ./eg_gen_overdue.pl <bootstap> 0
+#		generates today's notices
+# ./eg_gen_overdue.pl <bootstap> 1 0
+#		generates notices for today - 1 and today
+# ./eg_gen_overdue.pl <bootstap> 2 1 0  
+# ./eg_gen_overdue.pl <bootstap> 3 2 1 0  etc...
+# ---------------------------------------------------------------
+
+
+
+use strict; use warnings;
+require 'oils_header.pl';
+use vars qw/$logger $apputils/;
+use Data::Dumper;
+use OpenILS::Const qw/:const/;
+use OpenILS::Application::AppUtils;
+use DateTime;
+use Email::Send;
+use DateTime::Format::ISO8601;
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::JSON;
+use Unicode::Normalize;
+use OpenILS::Const qw/:const/;
+use OpenSRF::AppSession;
+use DBI; #grpl
+use DBD::mysql; #grpl
+
+my $U = 'OpenILS::Application::AppUtils';
+
+my $SEND_EMAILS = 1;
+#my $SEND_EMAILS = 0;
+my $bsconfig = shift || die "usage: $0 <bootstrap_config>\n";
+#my $bsconfig = "/openils/conf/opensrf_core.xml";
+
+my @goback = @ARGV;
+ at goback = (0) unless @goback;
+osrf_connect($bsconfig);
+my $e = OpenILS::Utils::CStoreEditor->new;
+
+my $smtp = $ENV{EG_OVERDUE_SMTP_HOST};
+my $mail_sender = $ENV{EG_OVERDUE_EMAIL_SENDER};
+
+# ---------------------------------------------------------------
+# Set up the email template
+my $etmpl = $ENV{EG_OVERDUE_EMAIL_TEMPLATE};
+my $email_template;
+if( open(F,"$etmpl") ) {
+	my @etmpl = <F>;
+	$email_template = join('', at etmpl);
+	close(F);
+}
+# ---------------------------------------------------------------
+
+
+
+my @date = CORE::localtime;
+my $sec  = $date[0];
+my $min  = $date[1];
+my $hour = $date[2];
+my $day  = $date[3];
+my $mon  = $date[4] + 1;
+my $year = $date[5] + 1900;
+
+my %USER_CACHE;
+my %ORG_CACHE;
+
+my $dbh=DBI->connect('dbi:mysql:database=notify;host=your_db_host;port=3306', "user", 'password', { RaiseError => 1, PrintError => 1 } ); # grpl
+my $sth=$dbh->prepare("insert into overdues set keynum = '', days_over = ?, phone_num = ?, notify_status = '', notify_time = now()"); # grpl
+
+
+print_notices($_) for @goback;
+
+
+# -----------------------------------------------------------------------
+# -----------------------------------------------------------------------
+
+
+sub print_notices {
+	my $goback = shift || 0;
+
+	for my $day ( qw/ 7 14 / ) { #grpl
+		my ($start, $end) = make_date_range($day + $goback);
+
+		$logger->info("OD_notice: process date range $start -> $end");
+
+		my $query = [
+			{
+				checkin_time => undef,
+				due_date => { between => [ $start, $end ] },
+				circ_lib => { between => [ 10, 17 ] }, #grpl
+			},
+			{ order_by => { circ => 'usr, circ_lib' } }
+		];
+		#my $circs = $e->search_action_circulation($query, {idlist=>1});
+
+        my $ses = OpenSRF::AppSession->create('open-ils.cstore');
+        my $req = $ses->request('open-ils.cstore.direct.action.circulation.id_list', @$query);
+        my $circs = [];
+        my $resp;
+        push(@$circs, $resp->content) while ($resp = $req->recv(timeout=>600));
+
+		process_circs( $circs, "${day}day" );
+	}
+}
+
+
+sub process_circs {
+	my $circs = shift;
+	my $range = shift;
+
+	return unless @$circs;
+
+	$logger->info("OD_notice: processing range $range and ".scalar(@$circs)." potential circs");
+
+	my $org; 
+	my $patron;
+	my @current;
+
+	my $x = 0;
+	for my $circ (@$circs) {
+		$circ = $e->retrieve_action_circulation($circ);
+
+		if( !defined $org or 
+				$circ->circ_lib != $org  or $circ->usr ne $patron ) {
+			$org = $circ->circ_lib;
+			$patron = $circ->usr;
+			print_notice( $range, \@current ) if @current;
+			@current = ();
+		}
+
+		push( @current, $circ );
+		$x++;
+	}
+
+	$logger->info("OD_notice: processed $x circs");
+	print_notice( $range, \@current );
+}
+
+sub make_date_range {
+	my $daysback = shift;
+
+	my $epoch = CORE::time - ($daysback * 24 * 60 * 60);
+	my $date = DateTime->from_epoch( epoch => $epoch, time_zone => 'local');
+
+	$date->set_hour(0);
+	$date->set_minute(0);
+	$date->set_second(0);
+	my $start = "$date";
+	
+	$date->set_hour(23);
+	$date->set_minute(59);
+	$date->set_second(59);
+
+	return ($start, "$date");
+}
+
+
+sub print_notice {
+	my( $range, $circs ) = @_;
+	return unless @$circs;
+
+	my $days_over = $range; #grpl
+        $days_over =~ s/'day'//; #grpl
+
+	my $s1 = scalar(@$circs);
+	
+	# we don't charge for lost or claimsreturned
+	$circs = [ 
+		grep {
+			!$_->stop_fines or (
+				$_->stop_fines ne OILS_STOP_FINES_LOST and
+				$_->stop_fines ne OILS_STOP_FINES_CLAIMSRETURNED 
+			)
+		} @$circs 
+	];
+
+	return unless @$circs;
+
+	my $s2 = $s1 - scalar(@$circs);
+	$logger->info("OD_notice: dropped $s2 lost/CR from processing...") if $s2;
+
+	my $org = $circs->[0]->circ_lib;
+
+
+	my $usr = $circs->[0]->usr;
+	$logger->debug("OD_notice: printing $range user:$usr org:$org");
+
+	my @patron_data = fetch_patron_data($usr);
+	my @org_data = fetch_org_data($org);
+
+	return unless (@patron_data and @org_data);
+	my $wd = (localtime)[6]; #grpl
+	if ( $wd != 0 ) { # grpl if not Sun
+	  if ( ($patron_data[10] ne '') && (!$patron_data[11]) && (!$patron_data[0]->email) ) { # grpl [10] is phone_num and [11] is Block Calls stat_cat
+		my $pnum = $patron_data[10];
+		$pnum =~ s/(\d+)\D+(\d+)\D+(\d+).*/$1$2$3/;
+		$sth->execute($days_over,$pnum); #grpl
+	  }
+	}
+
+	my $email;
+
+	if( $email = $patron_data[0]->email and $SEND_EMAILS
+		and $email =~ /.+\@.+/ 
+		and ($range eq '7day' or $range eq '14day') ) { #grpl
+			send_email($range, \@patron_data, \@org_data, $circs);
+
+	} else {
+
+		if( $patron_data[9] ) {
+
+			#print "\t\t<notice type='overdue' count='$range'>\n";
+			#print_patron_xml_chunk(@patron_data);
+			#print_org_xml_chunk(@org_data);
+			#print_circ_chunk($_) for @$circs;
+			#print "\t\t</notice>\n";
+
+		} else {
+			# There is no zip, therefore no address.
+			$logger->warn("OD_notice: unable to send mail notification for $usr due to lack of valid address");
+		}
+	}
+}
+
+
+sub fetch_patron_data {
+	my $user_id = shift;
+
+	my $patron = $USER_CACHE{$user_id};
+
+	if( ! $patron ) {
+		$logger->debug("OD_notice:   fetching patron $user_id");
+
+		$patron = $e->retrieve_actor_user(
+			[
+				$user_id,
+				{
+					flesh => 1,
+					flesh_fields => { 
+						'au' => [qw/ card billing_address mailing_address stat_cat_entries/] 
+					}
+				}
+			]
+		) or return handle_event($e->event);
+
+		$USER_CACHE{$user_id} = $patron;
+	}
+
+	my $bc = $patron->card->barcode;
+	my $fn = $patron->first_given_name;
+	my $mn = $patron->second_given_name;
+	my $ln = $patron->family_name;
+
+	my ( $s1, $s2, $city, $state, $zip );
+
+	my $baddr = $patron->mailing_address;
+	unless( $baddr and $U->is_true($baddr->valid) ) {
+		$baddr = $patron->billing_address;
+		$baddr = undef unless( $baddr and $U->is_true($baddr->valid) );
+	}
+
+	if( $baddr ) {
+		$s1		= $baddr->street1;
+		$s2		= $baddr->street2;
+		$city		= $baddr->city;
+		$state	= $baddr->state;
+		$zip		= $baddr->post_code;
+	}
+
+	$bc = entityize($bc);
+	$fn = entityize($fn);
+	$mn = entityize($mn);
+	$ln = entityize($ln);
+	$s1 = entityize($s1);
+	$s2 = entityize($s2);
+	$city  = entityize($city);
+	$state = entityize($state);
+	$zip	 = entityize($zip);
+
+	my $pn = $patron->day_phone; #grpl
+	$pn = entityize($pn); #grpl
+	my $patron_block = 0; #grpl
+	foreach my $sc (@{$patron->stat_cat_entries}) { #grpl
+		if ( $sc->[5] eq 'Block Calls' ) {
+			$patron_block = 1;
+		}
+	}
+
+	return ( $patron, $bc, $fn, $mn, $ln, $s1, $s2, $city, $state, $zip, $pn, $patron_block ); #grpl
+}
+
+	
+sub print_patron_xml_chunk {
+	my( $patron, $bc, $fn, $mn, $ln, $s1, $s2, $city, $state, $zip ) = @_;
+	my $pid = $patron->id;
+	print <<"	XML";
+			<patron>
+				<id type="barcode">$bc</id>
+				<fullname>$fn $mn $ln</fullname>
+				<street1>$s1 $s2</street1>
+				<city_state_zip>$city, $state $zip</city_state_zip>
+				<sys_id>$pid</sys_id>
+			</patron>
+	XML
+}
+
+
+sub fetch_org_data {
+	my $org_id = shift;
+
+	my $org = $ORG_CACHE{$org_id};
+
+	if( ! $org ) {
+		$logger->debug("OD_notice:   fetching org $org_id");
+
+		$org = $e->retrieve_actor_org_unit(
+			[
+				$org_id,
+				{
+					flesh => 1, 
+					flesh_fields => 
+						{ aou => [ qw/billing_address mailing_address/ ] }
+				}
+			]
+		) or return handle_event($e->event);
+
+		$ORG_CACHE{$org_id} = $org;
+	}
+
+	my $name = $org->name;
+	my $phone = $org->phone;
+	my $email = $org->email;
+
+
+	my( $s1, $s2, $city, $state, $zip );
+	my $baddr = $org->billing_address || $org->mailing_address;
+	if( $baddr ) {
+		$s1		= $baddr->street1;
+		$s2		= $baddr->street2;
+		$city		= $baddr->city;
+		$state	= $baddr->state;
+		$zip		= $baddr->post_code;
+	}
+
+	$name  = entityize($name);
+	$phone = entityize($phone);
+	$s1	 = entityize($s1);
+	$s2	 = entityize($s2);
+	$city  = entityize($city);
+	$state = entityize($state);
+	$zip	 = entityize($zip);
+	$email = entityize($email);
+
+	return ( $org, $name, $phone, $s1, $s2, $city, $state, $zip, $email );
+}
+
+
+sub print_org_xml_chunk {
+	my( $org, $name, $phone, $s1, $s2, $city, $state, $zip, $email ) = @_;
+	print <<"	XML";
+			<library>
+				<libname>$name</libname>
+				<libphone>$phone</libphone>
+				<libstreet1>$s1 $s2</libstreet1>
+				<libcity_state_zip>$city, $state $zip</libcity_state_zip>
+			</library>
+	XML
+}
+
+
+sub fetch_circ_data {
+	my $circ = shift;
+
+	my $title;
+	my $author;
+	my $cn;
+
+	my $d = $circ->due_date;
+	$d =~ s/[T ].*//og; # just for logging
+	$logger->debug("OD_notice:   processing circ ".$circ->id." $d");
+
+	my $due = DateTime::Format::ISO8601->new->parse_datetime(
+		clense_ISO8601($circ->due_date));
+
+	my $day  = $due->day;
+	my $mon  = $due->month;
+	my $year = $due->year;
+
+	my $copy = $e->retrieve_asset_copy($circ->target_copy)
+		or return handle_event($e->event);
+   
+        my $loc = $copy->location;
+        my $cl = $e->retrieve_asset_copy_location($loc);
+        $cl = $cl->[7];
+
+	my $bc = $copy->barcode;
+
+	if( $copy->call_number == OILS_PRECAT_CALL_NUMBER ) {
+		$title = $copy->dummy_title || "";
+		$author = $copy->dummy_author || "";
+
+	} else {
+
+		my $volume = $e->retrieve_asset_call_number(
+			[
+				$copy->call_number,
+				{
+					flesh => 1,
+					flesh_fields => {
+						acn => [ qw/record/ ]
+					}
+				}
+			]
+		) or return handle_event($e->event);
+
+		$cn = $volume->label;
+		my $mods = $apputils->record_to_mvr($volume->record);
+		if( $mods ) {
+			$title = $mods->title || "";
+			$author = $mods->author || "";
+		}
+	}
+
+	$title = entityize($title);
+	$author = entityize($author);
+	$cn = entityize($cn);
+	$bc = entityize($bc);
+
+        my $xact = $circ->[13];
+        my $fine = simplereq('open-ils.cstore', 'open-ils.cstore.direct.money.billable_transaction_summary.retrieve', $xact);
+        $fine = $fine->[3];
+
+        my $co = DateTime::Format::ISO8601->new->parse_datetime(
+                clense_ISO8601($circ->xact_start));
+        my $co_day = $co->day;
+        my $co_mon = $co->month;
+        my $co_year = $co->year;
+        my $co_date = "$co_mon/$co_day/$co_year";
+
+        my $pr = $copy->price;
+
+	return( $title, $author, $cn, $bc, $day, $mon, $year, $co_date, $pr, $cl, $fine );
+}
+
+
+sub print_circ_chunk {
+	my $circ = shift;
+	my ( $title, $author, $cn, $bc, $day, $mon, $year, $co, $pr, $cl, $fine ) = fetch_circ_data($circ);
+	my $cid = $circ->id;
+	print <<"	XML";
+			<item>
+				<title>$title</title>
+				<author>$author</author>
+				<duedate>$mon/$day/$year</duedate>
+				<callno>$cn</callno>
+				<location>$cl</location>
+				<barcode>$bc</barcode>
+				<circ_id>$cid</circ_id>
+				<check_out>$co</check_out>
+				<item_price>$pr</item_price>
+				<fine>$fine</fine>
+			</item>
+	XML
+}
+
+
+
+sub send_email {
+	my( $range, $patron_data, $org_data, $circs ) = @_;
+	my( $org, $org_name, $org_phone, $org_s1, $org_s2, $org_city, $org_state, $org_zip, $org_email ) = @$org_data;
+	my( $patron, $bc, $fn, $mn, $ln, $user_s1, $user_s2, $user_city, $user_state, $user_zip ) = @$patron_data;
+
+	return unless $SEND_EMAILS;
+
+	my $pemail = $patron_data->[0]->email;
+
+	my $tmpl = $email_template;
+	my @time = localtime;
+	my $year = $time[5] + 1900;
+	my $mon  = $time[4] + 1;
+	my $day  = $time[3];
+
+	my $r = ($range eq '7day') ? 7 : 14; #grpl
+
+	# - default to the global sender for the errors-to header
+	my $errors_to = $mail_sender;
+
+	# if they have an org setting for errors-to, use that as the errors-to address
+	if( my $set = $e->search_actor_org_unit_setting( 
+			{ name => 'org.bounced_emails', org_unit => $org->id } )->[0] ) {
+
+		my $bemail = OpenSRF::Utils::JSON->JSON2perl($set->value);
+		$errors_to = $bemail if $bemail;
+	}
+
+
+	$tmpl =~ s/\${EMAIL_RECIPIENT}/$pemail/;
+	$tmpl =~ s/\${EMAIL_SENDER}/$errors_to/o; 
+	$tmpl =~ s/\${EMAIL_REPLY_TO}/$errors_to/;
+	$tmpl =~ s/\${EMAIL_ERRORS_TO}/$errors_to/;
+   $tmpl =~ s/\${EMAIL_HEADERS}//; # - we have no additional headers to add
+
+   $tmpl =~ s/\${RANGE}/$r/;
+   $tmpl =~ s/\${DATE}/$mon\/$day\/$year/;
+   $tmpl =~ s/\${FIRST_NAME}/$fn/;
+   $tmpl =~ s/\${MIDDLE_NAME}/$mn/;
+   $tmpl =~ s/\${LAST_NAME}/$ln/;
+   $tmpl =~ s/\${BARCODE}/$bc/; # grpl
+
+	my ($itmpl) = $tmpl =~ /\${OVERDUE_ITEMS\[(.*)\]}/ms;
+
+	my $items = '';
+	for my $circ (@$circs) {
+		my $circtmpl = $itmpl;
+		my ( $title, $author, $cn, $bc, $due_day, $due_mon, $due_year ) = fetch_circ_data($circ);
+		$circtmpl =~ s/\${TITLE}/$title/o;
+		$circtmpl =~ s/\${AUTHOR}/$author/o;
+		$circtmpl =~ s/\${CALL_NUMBER}/$cn/o;
+		$circtmpl =~ s/\${DUE_DAY}/$due_day/o;
+		$circtmpl =~ s/\${DUE_MONTH}/$due_mon/o;
+		$circtmpl =~ s/\${DUE_YEAR}/$due_year/o;
+		$circtmpl =~ s/\${ITEM_BARCODE}/$bc/o;
+		$items .= "$circtmpl\n";
+	}
+
+	$tmpl =~ s/\${OVERDUE_ITEMS\[.*\]}/$items/ms;
+
+	my $org_addr = "$org_s1 $org_s2 $org_city, $org_state $org_zip";
+	$tmpl =~ s/\${ORG_NAME}/$org_name/o;
+	$tmpl =~ s/\${ORG_ADDRESS}/$org_addr/o;
+	$tmpl =~ s/\${ORG_PHONE}/$org_phone/o;
+
+	$logger->debug("OD_notice: sending email to $pemail: $tmpl");
+
+	my $sender = Email::Send->new({mailer => 'SMTP'});
+	$sender->mailer_args([Host => $smtp]);
+
+	my $stat = $sender->send($tmpl);
+
+	if( $stat and $stat->type eq 'success' ) {
+		$logger->info("OD_notice:   successfully sent overdue email");
+	} else {
+		$logger->warn("OD_notice:   unable to send hold overdue email: ".Dumper($stat));
+	}
+
+	$logger->info("OD_notice:   sending email to".$patron_data->[0]->email);
+}
+
+sub handle_event {
+	my $evt = shift;
+	warn "OD_notice: ".Dumper($evt) . "\n";
+	$logger->error("OD_notice: ".Dumper($evt));
+	return undef;
+}
+
+
+sub entityize {
+	my $stuff = shift || return "";
+	$stuff =~ s/\</&lt;/og;
+	$stuff =~ s/\>/&gt;/og;
+	$stuff =~ s/\&/&amp;/og;
+	$stuff = NFC($stuff);
+	$stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
+	return $stuff;
+}
+
+
+
+


Property changes on: grpl/trunk/patron_notifications/grpl_overdue.pl
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/grpl_overdue.sh
===================================================================
--- grpl/trunk/patron_notifications/grpl_overdue.sh	                        (rev 0)
+++ grpl/trunk/patron_notifications/grpl_overdue.sh	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,30 @@
+#!/bin/bash
+# ---------------------------------------------------------------
+# This file runs the overdue generation script.
+# If today is Monday, it runs the script for Sat/Sun/Mon, 
+# otherwise it runs once per day.
+# ---------------------------------------------------------------
+
+
+
+
+SSH_CLIENT=$1
+RECIPIENT=$2;
+DATE=$(date +%Y-%m-%d);
+DAY=$(date +%u);
+BSCONFIG="/openils/conf/opensrf_core.xml"
+ODDIR="/openils/var/data/overdue";
+
+export EG_OVERDUE_EMAIL_TEMPLATE="grpl_overdue_email";
+export EG_OVERDUE_SMTP_HOST="your_email_host";
+export EG_OVERDUE_EMAIL_SENDER="overdues at your_domain";
+
+[ $(whoami) != "opensrf" ] && echo "Must be run as opensrf" && exit 1;
+source ~/.bashrc;
+ARGS="0"
+
+#[ $DAY == 6 -o $DAY == 7 ] && exit 0; # don't run on saturday or sunday
+if [ $DAY == 1 ]; then ARGS="2 1 0"; fi; # If today is monday, run for sat/sun/mon
+
+./grpl_overdue.pl $BSCONFIG $ARGS > "$ODDIR/grpl_overdue.$DATE.xml"
+


Property changes on: grpl/trunk/patron_notifications/grpl_overdue.sh
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/grpl_overdue_email
===================================================================
--- grpl/trunk/patron_notifications/grpl_overdue_email	                        (rev 0)
+++ grpl/trunk/patron_notifications/grpl_overdue_email	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,30 @@
+To: ${EMAIL_RECIPIENT}
+From: ${EMAIL_SENDER}
+Reply-To: ${EMAIL_REPLY_TO}
+Errors-To: ${EMAIL_ERRORS_TO}
+Subject: Overdue Materials Notification
+${EMAIL_HEADERS}
+
+
+${DATE}
+Dear ${FIRST_NAME} ${MIDDLE_NAME} ${LAST_NAME} : ${BARCODE}
+
+
+Our records indicate these items are ${RANGE} days or more overdue:
+
+TITLE                 AUTHOR                CALL NUMBER
+
+${OVERDUE_ITEMS[
+${TITLE} : ${AUTHOR}		${CALL_NUMBER}
+Due: ${DUE_MONTH}/${DUE_DAY}/${DUE_YEAR}		ID: ${ITEM_BARCODE}
+]}
+
+
+Please return the above items to avoid additional fines.  Please do not
+
+respond to this email.  Contact your library for more information:
+
+${ORG_NAME} / ${ORG_ADDRESS}
+${ORG_PHONE}
+
+

Added: grpl/trunk/patron_notifications/holdshelf-result.pl
===================================================================
--- grpl/trunk/patron_notifications/holdshelf-result.pl	                        (rev 0)
+++ grpl/trunk/patron_notifications/holdshelf-result.pl	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+use Asterisk::AGI;
+use DBI;
+use DBD::mysql;
+use LWP;
+  
+my $AGI = new Asterisk::AGI;
+
+my %input = $AGI->ReadParse();
+my ($res)=@ARGV;
+#my $res = 'Zap/1-1:1235,1242,1210,1207';
+my $status;
+my ($channel,$hid,$numdialed,$ack) = split(':', $res);
+my @hold_ids = split(',', $hid);
+if ($channel =~ 'Failed') {
+	$status = 'failed'
+} elsif ($ack eq 'ack') {
+	$status = 'acknowledged'
+} else {
+	$status = 'completed'
+}
+my $reason = $AGI->get_variable("HANGUPCAUSE");
+
+my $logdir = "/var/log/asterisk/holdshelf.log"; 
+my $time;
+
+# update notify server database
+my $dbh=DBI->connect('dbi:mysql:database=notify;host=your_db_host;port=3306', "user", 'passw', { RaiseError => 1, PrintError => 1 } );
+my $sth=$dbh->prepare("update holdshelf set notify_time = now(), notify_status = ? where id = ?");
+my $sth2=$dbh->prepare("select notify_time from holdshelf where id = ?");
+foreach my $h (@hold_ids) {
+	$sth->execute($status,$h);
+	$sth2->execute($h);
+	$time = @{$sth2->fetchrow_arrayref}[0];
+}
+$sth->finish;
+$sth2->finish;
+$dbh->disconnect;
+
+# update hold notice in Evergreen database
+foreach my $h (@hold_ids) {
+	$ua=LWP::UserAgent->new();
+	$egres=$ua->get("http://your_eg_server/cgi-bin/utils/grpl_holdnotice.cgi?h=$h&s=$status");
+}
+
+
+# write local log file  
+open (OUT,">>$logdir");
+print OUT "@hold_ids $numdialed $time $res $status $reason\n";
+close (OUT);
+


Property changes on: grpl/trunk/patron_notifications/holdshelf-result.pl
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/import_holdshelf.plx
===================================================================
--- grpl/trunk/patron_notifications/import_holdshelf.plx	                        (rev 0)
+++ grpl/trunk/patron_notifications/import_holdshelf.plx	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,16 @@
+#!/usr/bin/perl -w
+
+use LWP::UserAgent();
+use XML::Simple;
+
+$ua=LWP::UserAgent->new();
+
+$res=$ua->get('http://your_eg_server/cgi-bin/utils/grpl_holdshelf.cgi');
+$c = $res->content;
+
+$outname = ((localtime)[4]+1).(localtime)[3].(localtime)[2].(localtime)[1].'holdshelf';
+open(OUT, ">/usr/notify/$outname");
+print OUT $c;
+close OUT;
+
+system('mysql', 'notify', '-e', "load data infile '/usr/notify/$outname' ignore into table holdshelf fields terminated by '|'");


Property changes on: grpl/trunk/patron_notifications/import_holdshelf.plx
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/make_holdshelf_callfiles.plx
===================================================================
--- grpl/trunk/patron_notifications/make_holdshelf_callfiles.plx	                        (rev 0)
+++ grpl/trunk/patron_notifications/make_holdshelf_callfiles.plx	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,61 @@
+#!/usr/bin/perl -w
+
+use DBI;
+use DBD::mysql;
+
+my $dbh = DBI->connect('dbi:mysql:database=notify', "user", 'passw', { RaiseError => 1, PrintError => 1 } );
+
+# get distinct phone number/pickup library combinations
+my $sth=$dbh->prepare("select id,phone_notify,count(phone_notify) as hnum ,pickup_lib from holdshelf where (notify_time = 0 and notify_status = '') group by phone_notify,pickup_lib having phone_notify <> '' and phone_notify <> 'Block Calls'");
+$sth->execute;
+
+my $sth2= $dbh->prepare("select id from holdshelf where notify_time = 0 and phone_notify = ? and pickup_lib = ?");
+
+my $f;
+my $cft;
+my $channel = 1;
+
+# slurp in default call file
+{
+$f = '/usr/notify/asterisk/holdshelf_template';
+local( $/, *CFT ) ;
+open( CFT, $f) or die "Can't open $f";
+$cft = <CFT>;
+}
+
+while (my $h=$sth->fetchrow_hashref) {
+	my $id = $h->{id};
+	my $num = $h->{phone_notify};
+	my $numholds = $h->{hnum};
+	my $lib = $h->{pickup_lib};
+
+	if ($numholds > 1) { #get all hold ids for this phone number/pickup library
+		$sth2->execute($num,$lib);
+		$id = '';
+		map { $id .= ($$_[0] . ',') } @{$sth2->fetchall_arrayref};
+		chop $id;
+	}
+
+
+        # substitue hold info into call file timeplate
+        my $temp = $cft;
+        $temp =~ s/CHANNEL/$channel/;
+        $temp =~ s/NUM/$num/g;
+	$temp =~ s/ID/$id/;
+	$temp =~ s/PKUPLIB/$lib/;
+
+        # write new call file
+        my $cf = "/usr/notify/asterisk/tmp/$num.holdshelf";
+        open(OUT, ">$cf");
+        print OUT $temp;
+        close OUT;
+
+        # switch to next channel for next call file
+        if ($channel == 1) {
+                $channel = 2
+        } elsif ($channel == 2) {
+                $channel = 3
+        } else { $channel = 1 }
+
+}
+


Property changes on: grpl/trunk/patron_notifications/make_holdshelf_callfiles.plx
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/make_overdue_callfiles.plx
===================================================================
--- grpl/trunk/patron_notifications/make_overdue_callfiles.plx	                        (rev 0)
+++ grpl/trunk/patron_notifications/make_overdue_callfiles.plx	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,52 @@
+#!/usr/bin/perl -w
+
+use DBI;
+use DBD::mysql;
+
+my $dbh = DBI->connect('dbi:mysql:database=notify', "user", 'passw', { RaiseError => 1, PrintError => 1 } );
+
+# get distinct phone number/pickup library combinations
+my $sth=$dbh->prepare("select distinct(phone_num) from overdues where phone_num <> ''");
+$sth->execute;
+
+my $f;
+my $cft;
+my $channel = 1;
+
+# slurp in default call file
+{
+$f = '/usr/notify/asterisk/overdue_template';
+local( $/, *CFT ) ;
+open( CFT, $f) or die "Can't open $f";
+$cft = <CFT>;
+}
+
+while (my $h=$sth->fetchrow_hashref) {
+	#my $id = $h->{keynum};
+	my $num = $h->{phone_num};
+	$num =~ s/\D//g;
+
+
+        # substitue hold info into call file timeplate
+        my $temp = $cft;
+        $temp =~ s/CHANNEL/$channel/;
+        $temp =~ s/NUM/$num/g;
+	#$temp =~ s/ID/$id/;
+
+        # write new call file
+        my $cf = "/usr/notify/asterisk/tmp/$num.overdue";
+        open(OUT, ">$cf");
+        print OUT $temp;
+        close OUT;
+
+        # switch to next channel for next call file
+        if ($channel == 1) {
+                $channel = 2
+        } elsif ($channel == 2) {
+                $channel = 3
+	} elsif ($channel == 3) {
+                $channel = 4
+        } else { $channel = 1 }
+
+}
+


Property changes on: grpl/trunk/patron_notifications/make_overdue_callfiles.plx
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/overdue-result.pl
===================================================================
--- grpl/trunk/patron_notifications/overdue-result.pl	                        (rev 0)
+++ grpl/trunk/patron_notifications/overdue-result.pl	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+# overdue-result.pl
+
+use Asterisk::AGI;
+use DBI;
+use DBD::mysql;
+  
+my $AGI = new Asterisk::AGI;
+
+my %input = $AGI->ReadParse();
+my ($res)=@ARGV;
+my $status;
+my ($channel,$odid) = split(':', $res);
+if ($channel =~ 'Failed') {
+	$status = 'failed'
+} else {
+	$status = 'completed'
+}
+
+my $logdir = "/var/log/asterisk/overdues.log"; 
+my $time;
+
+# update notify server database
+my $dbh=DBI->connect('dbi:mysql:database=notify;host=your_db_host;port=3306', "user", 'passw', { RaiseError => 1, PrintError => 1 } );
+my $sth=$dbh->prepare("update overdues set notify_time = now(), notify_status = ? where id = ?");
+$sth->execute($status,$odid);
+$sth->finish;
+$dbh->disconnect;
+
+# write local log file  
+open (OUT,">>$logdir");
+print OUT "$time $res $status \n";
+close (OUT);
+


Property changes on: grpl/trunk/patron_notifications/overdue-result.pl
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/send_holdshelf_emails.plx
===================================================================
--- grpl/trunk/patron_notifications/send_holdshelf_emails.plx	                        (rev 0)
+++ grpl/trunk/patron_notifications/send_holdshelf_emails.plx	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,66 @@
+#!/usr/bin/perl -w
+
+use DBI;
+use DBD::mysql;
+use MIME::Lite;
+use LWP;
+
+my $dbh = DBI->connect('dbi:mysql:database=notify', "user", 'passw', { RaiseError => 1, PrintError => 1 } );
+
+my $sth=$dbh->prepare("select id,email,email_notify,pickup_lib,title,barcode from holdshelf where email <> '' and email_notify='t';");
+$sth->execute;
+
+my $updh=$dbh->prepare("update holdshelf set email_notify='s',notify_time=now() where id = ?");
+
+while (my $m=$sth->fetchrow_hashref) {
+	my $id = $m->{id};
+	my $addr = $m->{email};
+	my $lib = $m->{pickup_lib};
+	my $title = $m->{title};
+	my $bcode = $m->{barcode};
+	my $branch;
+	my $pnum;
+
+	SWITCH: {
+		$_ = $lib;
+		/GRPL-GR/ and do {$branch="Main Library"; $pnum="616 988-5400"; last SWITCH; };
+		/GRPL-GM/ and do {$branch="Madison Square Branch"; $pnum="616 988-5411"; last SWITCH; };
+		/GRPL-GO/ and do {$branch="Ottawa Hills Branch"; $pnum="616 988-5412"; last SWITCH; };
+		/GRPL-GS/ and do {$branch="Seymour Branch"; $pnum="616 988-5413"; last SWITCH; };
+		/GRPL-GC/ and do {$branch="Van Belkum Branch"; $pnum="616 988-5410"; last SWITCH; };
+		/GRPL-GN/ and do {$branch="West Leonard Branch"; $pnum="616 988-5416"; last SWITCH; };
+		/GRPL-GW/ and do {$branch="West Side Branch"; $pnum="616 988-5414"; last SWITCH; };
+		/GRPL-GY/ and do {$branch="Yankee Clipper Branch"; $pnum="616 988-5415"; last SWITCH; };
+	}
+
+	send_me($addr,$branch,$pnum,$title,$bcode);
+	$updh->execute($id);
+	# update hold notice in Evergreen database
+        my $ua=LWP::UserAgent->new();
+        my $hn=$ua->get("http://your_db_host/cgi-bin/utils/grpl_holdnotice.cgi?h=$id&s=email_sent");
+
+}
+
+
+sub send_me {
+   my ($a,$b,$p,$t,$c) = @_;
+
+   $a =~ s/(.*?)(\;.*)/$1/;  # if someone thinks it's a good idea to have multiple addresses separated by a semi-colon, trash the second.
+
+   my $body = "Your hold item: $t  \nis now available for pickup at the $b. This hold will expire in 7 business days. For more information please call $p.\n\nPatron Barcode: $c.";
+
+   my $msg = MIME::Lite->new(
+    To      =>"$a",
+    From    =>'holdmsg at your_domain',
+    Subject =>'Library Items on hold',
+    Type    =>'multipart/mixed'
+         );
+
+    $msg->attach(
+        Type => 'text/plain',
+        Data => "$body");
+
+    $msg->send('smtp','your_smtp_server',Debug=>0);
+
+}
+


Property changes on: grpl/trunk/patron_notifications/send_holdshelf_emails.plx
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/README-TADL.txt
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/README-TADL.txt	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/README-TADL.txt	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,61 @@
+This is a snapshot of what TADL is using for Evergreen patron notification.
+
+It is based heavily on code shared with TADL by Bill Ott and Doug Kyle
+at the Grand Rapids Public Library (GRPL).
+
+Please address any questions to
+Jeff Godin
+Traverse Area District Library
+231-932-8546
+jgodin at tadl.org
+
+I've made every effort to sanitize these files without rendering them 
+useless. Please let me know if I've broken something for you.
+
+A description of the files in this archive follows:
+
+./grpl/
+Collection of original files shared with TADL by GRPL
+
+./overdues-sample.xml
+sample XML generated by GRPL-modified eg_gen_overdue.pl 
+
+./asterisk-box/var/lib/asterisk/agi-bin/holdshelf-result.pl
+called via AGI from extensions.conf to record notification
+to mysql table "holdshelf", 
+
+./asterisk-box/etc/asterisk/
+Asterisk config is here. Most of the "meat" is in extensions.conf
+
+./asterisk-box/queue-feeder.pl
+Script that feeds /var/spool/asterisk/outgoing one callfile at a time
+
+./notify-box/holdshelf-schema.mysqldump
+Schema for mysql table "holdshelf"
+
+./notify-box/noticeitems-schema.txt
+Schema for postgresql table "noticeitems"
+
+./notify-box/home/notify/bin/xml2obj.py
+./notify-box/home/notify/bin/import-notices.py
+imports overdues.xml data to postgresql table "noticeitems"
+
+./notify-box/home/notify/bin/email-overdues.py
+sends e-mail messages based on postgresql table "noticeitems"
+
+./notify-box/root/import_holdshelf_all.plx
+calls tadl_holdshelf_all.cgi on evergreen server and imports 
+data into mysql table "holdshelf" (and others)
+
+./notify-box/root/send_holdshelf_emails.plx
+sends e-mails based on mysql table "holdshelf"
+
+./notify-box/usr/notify/asterisk/holdshelf_template
+./notify-box/usr/notify/asterisk/make_holdshelf_callfiles.plx
+create callfiles for asterisk, based off of this template
+
+./evergreen-server/tadl_holdshelf_all.cgi
+called by import_holdshelf_all.plx
+
+./evergreen-server/tadl_holdnotice.cgi
+called by holdshelf-result.pl

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/asterisk.conf
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/asterisk.conf	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/asterisk.conf	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,9 @@
+[global]
+astetcdir => /etc/asterisk
+astmoddir => /usr/lib/asterisk/modules
+astvarlibdir => /var/lib/asterisk
+astagidir => /var/lib/asterisk/agi-bin
+astspooldir => /var/spool/asterisk
+astrundir => /var/run/asterisk
+astlogdir => /var/log/asterisk
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/cdr.conf
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/cdr.conf	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/cdr.conf	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,10 @@
+[general]
+enable=yes
+unanswered=yes
+
+
+[csv]
+usegmtime=no
+loguniqueid=yes
+loguserfield=yes
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/cdr_pgsql.conf
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/cdr_pgsql.conf	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/cdr_pgsql.conf	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,8 @@
+[global]
+hostname=localhost
+port=5432
+dbname=asterisk
+password=REMOVED
+user=asterisk
+table=cdr
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/extensions.conf
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/extensions.conf	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/extensions.conf	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,216 @@
+;
+; recording and holdshelf contexts based on GRPL-shared extensions.conf
+; which I believe were based on  
+; http://www.voip-info.org/wiki/view/Asterisk+auto-dial+out+deliver+message
+;
+; modifications that i can remember here:
+; * modified to prevent double-logging some results
+; * use Background's m argument to prevent hangup on invalid dial (like #)
+; 
+; Changed the order of Playback, AGI on holdack so that a hangup during the
+; thank you playback wouldn't result in a no-log, but now the patron gets
+; dead air while that AGI runs. Need to address that.
+; 
+
+
+[general]
+static=yes
+writeprotect=yes
+autofallthrough=yes
+
+[globals]
+
+
+[incoming]
+exten => REMOVED,1,Ringing
+exten => REMOVED,n,Wait,3
+exten => REMOVED,n,Answer
+exten => REMOVED,n,Wait,1
+exten => REMOVED,n,Background(tt-monkeys)
+
+include => record-outboundmsgs
+
+[test-outgoing]
+exten => s,1,Answer()
+exten => s,n,Wait(1)
+exten => s,n,Playback(tt-monkeys)
+exten => s,n,Wait(1)
+exten => s,n,Hangup()
+
+[holdshelfmsg]
+exten => s,1,Set(TIMEOUT(digit)=5)      ; Set Digit Timeout to 5 seconds
+exten => s,n,Set(TIMEOUT(response)=10)  ; Set Response Timeout to 10 seconds
+exten => s,n,Set(NumberDialed=${CUT(PassedInfo,,1)})
+exten => s,n,Set(CDR(accountcode)=${NumberDialed}-${holdid})
+exten => s,n,Answer
+exten => s,n,Monitor()
+exten => s,n,Wait(1)
+
+#begin message
+exten => s,n(start),Background(outboundmsgs/holdshelfmsg1,m) ; hello, you have one or more library items waiting for pickup at
+exten => s,n,Background(outboundmsgs/${pickuplib},m) ; (library name here)
+exten => s,n,Background(outboundmsgs/how_to_ack,m)   ; "Press 1 to replay or 2 to acknowledge receiving this message"
+
+#repeat message once
+exten => s,n,Background(outboundmsgs/holdshelfmsg1,m)
+exten => s,n,Background(outboundmsgs/${pickuplib},m)
+exten => s,n,Background(outboundmsgs/how_to_ack,m)
+
+#repeat a second time -- dirty hack for voicemail/answering machines
+exten => s,n,Background(outboundmsgs/holdshelfmsg1,m)
+exten => s,n,Background(outboundmsgs/${pickuplib},m)
+exten => s,n,Background(outboundmsgs/how_to_ack,m)
+
+exten => 1,1,Goto(s,start)   ; replay message
+
+exten => 2,1,Goto(holdack,s,1) ; acknowledge message
+
+exten => t,1,Playback(vm-goodbye)
+exten => t,n,AGI(holdshelf-result.pl|${CHANNEL}:${holdid}:${NumberDialed})
+exten => t,n,Set(resultlogged=1)
+exten => t,n,Hangup
+
+exten => h,1,NoOp(Hangup Extension Called, resultlogged: ${resultlogged})
+exten => h,n,GotoIf($[${ISNULL(${resultlogged})}]?hanguplog,1:hangupnolog,1)
+
+exten => hanguplog,1,NoOp(Hangup log extension reached)
+exten => hanguplog,n,DEADAGI(holdshelf-result.pl|${CHANNEL}:${holdid}:${NumberDialed})
+
+exten => hangupnolog,1,NoOp(Hangup but NO log extension reached)
+
+exten => failed,1,Set(NumberDialed=${CUT(PassedInfo,,1)})
+exten => failed,n,Set(CDR(userfield)=${NumberDialed})
+exten => failed,n,AGI(holdshelf-result.pl|${CHANNEL}:${holdid}:${NumberDialed})
+exten => failed,n,Set(resultlogged=1)
+
+[holdack]
+exten => s,1,AGI(holdshelf-result.pl|${CHANNEL}:${holdid}:${NumberDialed}:ack)
+exten => s,n,Playback(outboundmsgs/thankyou)
+exten => s,n,Hangup
+
+[record-outboundmsgs]
+; Record voice files
+;
+; Before using this the first time
+;    mkdir /var/lib/asterisk/sounds/outboundmsgs
+;    chown asterisk_user:asterisk_user /var/lib/asterisk/sounds/outboundmsgs
+;    (Where asterisk_user = the user that asterisk runs under: = root for many installations)
+;
+; In a context for incoming calls put something like
+;  include => record-outboundmsgs
+;
+; Then call
+;   2051 to Record a new outbound msg1
+;   2052 to Record a new outbound holdshelfmsg1
+;
+;   2061 to Record the msg played when the recipient acks the message
+;   2062 to Record the "How to ACK message"
+;
+; After dialing one of the extensions above:
+;   Wait for the record start tone
+;   Record your message
+;   Press # to stop recording
+;   Listen to an automatic playback of your new message
+;
+; outbound msg1
+exten => 2051,1,Wait(2)
+exten => 2051,2,Record(outboundmsgs/msg1:gsm)
+exten => 2051,3,Wait(2)
+exten => 2051,4,Playback(outboundmsgs/msg1)
+exten => 2051,5,wait(2)
+exten => 2051,6,Hangup
+;
+; outbound holdshelfmsg1
+exten => 2052,1,Wait(2)
+exten => 2052,2,Record(outboundmsgs/holdshelfmsg1:gsm)
+exten => 2052,3,Wait(2)
+exten => 2052,4,Playback(outboundmsgs/holdshelfmsg1)
+exten => 2052,5,wait(2)
+exten => 2052,6,Hangup
+;
+; outbound overduemsg1
+exten => 2053,1,Wait(2)
+exten => 2053,2,Record(outboundmsgs/overduemsg1:gsm)
+exten => 2053,3,Wait(2)
+exten => 2053,4,Playback(outboundmsgs/overduemsg1)
+exten => 2053,5,wait(2)
+exten => 2053,6,Hangup
+;
+; Msg played when msg is acked
+exten => 2061,1,Wait(2)
+exten => 2061,2,Record(outboundmsgs/thankyou:gsm)
+exten => 2061,3,Wait(2)
+exten => 2061,4,Playback(outboundmsgs/thankyou)
+exten => 2061,5,wait(2)
+exten => 2061,6,Hangup
+;
+; Msg played after outbound msg: "Press 1 to replay or 2 to acknowledge receiving this message"
+exten => 2062,1,Wait(2)
+exten => 2062,2,Record(outboundmsgs/how_to_ack:gsm)
+exten => 2062,3,Wait(2)
+exten => 2062,4,Playback(outboundmsgs/how_to_ack)
+exten => 2062,5,wait(2)
+exten => 2062,6,Hangup
+;
+; main
+exten => 2071,1,Wait(2)
+exten => 2071,2,Record(outboundmsgs/TADL-WOOD:gsm)
+exten => 2071,3,Wait(2)
+exten => 2071,4,Playback(outboundmsgs/TADL-WOOD)
+exten => 2071,5,wait(2)
+exten => 2071,6,Hangup
+;
+; main phone
+exten => 2171,1,Wait(2)
+exten => 2171,2,Record(outboundmsgs/TADL-WOOD-phone:gsm)
+exten => 2171,3,Wait(2)
+exten => 2171,4,Playback(outboundmsgs/TADL-WOOD-phone)
+exten => 2171,5,wait(2)
+exten => 2171,6,Hangup
+;
+
+;
+; interlochen 
+exten => 2072,1,Wait(2)
+exten => 2072,2,Record(outboundmsgs/TADL-IPL:gsm)
+exten => 2072,3,Wait(2)
+exten => 2072,4,Playback(outboundmsgs/TADL-IPL)
+exten => 2072,5,wait(2)
+exten => 2072,6,Hangup
+
+;
+; kingsley
+exten => 2073,1,Wait(2)
+exten => 2073,2,Record(outboundmsgs/TADL-KBL:gsm)
+exten => 2073,3,Wait(2)
+exten => 2073,4,Playback(outboundmsgs/TADL-KBL)
+exten => 2073,5,wait(2)
+exten => 2073,6,Hangup
+
+;
+; peninsula
+exten => 2074,1,Wait(2)
+exten => 2074,2,Record(outboundmsgs/TADL-PCL:gsm)
+exten => 2074,3,Wait(2)
+exten => 2074,4,Playback(outboundmsgs/TADL-PCL)
+exten => 2074,5,wait(2)
+exten => 2074,6,Hangup
+
+;
+; fife lake
+exten => 2075,1,Wait(2)
+exten => 2075,2,Record(outboundmsgs/TADL-FLPL:gsm)
+exten => 2075,3,Wait(2)
+exten => 2075,4,Playback(outboundmsgs/TADL-FLPL)
+exten => 2075,5,wait(2)
+exten => 2075,6,Hangup
+
+;
+; east bay
+exten => 2076,1,Wait(2)
+exten => 2076,2,Record(outboundmsgs/TADL-EBB:gsm)
+exten => 2076,3,Wait(2)
+exten => 2076,4,Playback(outboundmsgs/TADL-EBB)
+exten => 2076,5,wait(2)
+exten => 2076,6,Hangup
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/features.conf
===================================================================

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/modules.conf
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/modules.conf	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/modules.conf	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,67 @@
+[modules]
+autoload=yes
+;
+; Any modules that need to be loaded before the Asterisk core has been
+; initialized (just after the logger has been initialized) can be loaded
+; using 'preload'. This will frequently be needed if you wish to map all
+; module configuration files into Realtime storage, since the Realtime
+; driver will need to be loaded before the modules using those configuration
+; files are initialized.
+;
+; An example of loading ODBC support would be:
+;preload => res_odbc.so
+;preload => res_config_odbc.so
+;
+; If you want, load the GTK console right away.  
+; Don't load the KDE console since
+; it's not as sophisticated right now.
+;
+noload => pbx_gtkconsole.so
+;load => pbx_gtkconsole.so
+noload => pbx_kdeconsole.so
+;
+; Intercom application is obsoleted by
+; chan_oss.  Don't load it.
+;
+noload => app_intercom.so
+;
+; The 'modem' channel driver and its subdrivers are
+; obsolete, don't load them.
+;
+noload => chan_modem.so
+noload => chan_modem_aopen.so
+noload => chan_modem_bestdata.so
+noload => chan_modem_i4l.so
+noload => chan_capi.so
+;
+noload => res_musiconhold.so
+;
+; Load either OSS or ALSA, not both
+; By default, load OSS only (automatically) and do not load ALSA
+;
+noload => chan_alsa.so
+;noload => chan_oss.so
+
+;jeff
+noload => chan_agent.so
+noload => chan_iax2.so
+noload => chan_mgcp.so
+noload => chan_oss.so
+noload => chan_phone.so
+noload => chan_skinny.so
+noload => app_voicemail.so
+noload => app_followme.so
+noload => app_queue.so
+noload => pbx_ael.so
+noload => pbx_dundi.so
+noload => cdr_custom.so
+noload => res_smdi.so
+noload => res_config_pgsql.so
+noload => app_amd.so
+noload => app_festival.so
+
+;
+; Module names listed in "global" section will have symbols globally
+; exported to modules loaded after them.
+;
+[global]

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/sip.conf
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/sip.conf	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/etc/asterisk/sip.conf	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,26 @@
+[clearrate-voip1]
+type=friend
+call-limit=1
+qualify=250
+host=REMOVED
+trustrpid = yes
+sendrpid = yes
+disallow=all
+allow=ulaw
+insecure=port,invite
+context=incoming
+dtmfmode=rfc2833
+
+[clearrate-voip2]
+type=friend
+call-limit=1
+qualify=250
+host=REMOVED
+trustrpid = yes
+sendrpid = yes
+disallow=all
+allow=ulaw
+insecure=port,invite
+context=incoming
+dtmfmode=rfc2833
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/queue-feeder.pl
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/queue-feeder.pl	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/queue-feeder.pl	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+
+# 
+# Copyright 2008 Traverse Area District Library
+# Jeff Godin <jeff at tcnet.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+
+my $tmpdir = "/var/spool/asterisk/tmp";
+my $outdir = "/var/spool/asterisk/outgoing";
+
+use strict;
+
+my $fullcount;
+my $files;
+
+while () {
+
+	opendir(OUTDIR,$outdir);
+	my @outfiles = readdir(OUTDIR);
+	
+	my $outcount = scalar(@outfiles);
+	
+	if ($outcount > 2) {
+		$fullcount++;
+		#print "output full $fullcount times!\n";
+		if ($fullcount > 20) {
+			print "touching $outdir and resetting fullcount...\n";
+			system("touch", $outdir);
+			$fullcount = 0;	
+		}
+	} else {
+		print "output not full!\n";
+		opendir(TMPDIR,$tmpdir);
+		while (my $tmpfile = readdir(TMPDIR)) {
+			next if $tmpfile =~ /^\./;
+			print "moving $tmpfile\n";
+			system("chown", "asterisk:", $tmpdir . "/" . $tmpfile);
+			system("mv", $tmpdir . "/" . $tmpfile, $outdir);
+			$files++;
+			$fullcount = 0;
+			opendir(COUNTDIR,$tmpdir);
+			my $filecount;
+			$filecount = 0;
+			while ( my $countfile = readdir(COUNTDIR)) {
+				next if $countfile =~ /^\./;
+				$filecount++;
+			}
+			print "$files files moved, $filecount files remain.\n";
+			my $avgtime;
+			if ($files > 2) {
+				$avgtime = (time() - $^T) / ($files - 1);
+				my $completetime = time() + ($avgtime * $filecount);
+				print "avgtime = $avgtime - expect complete at " . scalar localtime($completetime) . "\n";
+			}
+			last;
+		}	
+	}
+
+	sleep 5;
+}

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib/asterisk/agi-bin/holdshelf-result.pl
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib/asterisk/agi-bin/holdshelf-result.pl	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/asterisk-box/var/lib/asterisk/agi-bin/holdshelf-result.pl	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,62 @@
+#!/usr/bin/perl
+
+# GRPL-shared code
+# modified by TADL:
+# * use proxy server on localhost
+# * don't record notification in Evergreen if "failed"
+#
+
+use Asterisk::AGI;
+use DBI;
+use DBD::mysql;
+use LWP;
+ 
+$ENV{http_proxy} = "localhost:3128";
+ 
+my $AGI = new Asterisk::AGI;
+
+my %input = $AGI->ReadParse();
+my ($res)=@ARGV;
+#my $res = 'Zap/1-1:1235,1242,1210,1207';
+my $status;
+my ($channel,$hid,$numdialed,$ack) = split(':', $res);
+my @hold_ids = split(',', $hid);
+if ($channel =~ 'Failed') {
+	$status = 'failed'
+} elsif ($ack eq 'ack') {
+	$status = 'acknowledged'
+} else {
+	$status = 'completed'
+}
+
+my $logdir = "/var/log/asterisk/holdshelf.log"; 
+my $time;
+
+# update notify server database
+#my $dbh=DBI->connect('dbi:mysql:database=notify;host=127.0.0.1;port=3306', "user", 'pass', { RaiseError => 1, PrintError => 1 } );
+my $dbh=DBI->connect('dbi:mysql:database=notify;host=127.0.0.1;port=3306', "root", undef, { RaiseError => 1, PrintError => 1 } );
+my $sth=$dbh->prepare("update holdshelf set notify_time = now(), notify_status = ? where id = ?");
+my $sth2=$dbh->prepare("select notify_time from holdshelf where id = ?");
+foreach my $h (@hold_ids) {
+	$sth->execute($status,$h);
+	$sth2->execute($h);
+	$time = @{$sth2->fetchrow_arrayref}[0];
+}
+$sth->finish;
+$sth2->finish;
+$dbh->disconnect;
+
+# update hold notice in Evergreen database
+foreach my $h (@hold_ids) {
+	# $ua=LWP::UserAgent->new();
+	# $egres=$ua->get("http://eg1.michiganevergreen.org/cgi-bin/utils/grpl_holdnotice.cgi?h=$h&s=$status");
+##  	system("/usr/bin/wget", "-q", "http://eg1.michiganevergreen.org/cgi-bin/utils/grpl_holdnotice.cgi?h=$h&s=$status");
+	system("/usr/bin/wget", "-q", "http://eg1.michiganevergreen.org/cgi-bin/utils/tadl/tadl_holdnotice.cgi?h=$h&s=$status") unless $status =~ 'failed';
+}
+
+
+# write local log file  
+open (OUT,">>$logdir");
+print OUT "@hold_ids $numdialed $time $res $status \n";
+close (OUT);
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server/tadl_holdnotice.cgi
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server/tadl_holdnotice.cgi	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server/tadl_holdnotice.cgi	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,43 @@
+#!/usr/bin/perl
+
+# GRPL-shared code
+# oils_login() needs credentials
+
+require '/openils/bin/support-scripts/oils_header.pl';
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use CGI qw(:standard);
+use DateTime;
+use DateTime::Format::ISO8601;
+
+print "Content-type: text/html\n\n";
+
+my $conf = '/openils/conf/opensrf_core.xml';
+
+my $holdid = param('h');
+my $status = param('s');
+
+my $d = DateTime->now;
+$d->add(days=>10);
+
+osrf_connect($conf);
+my $auth = oils_login('','');
+
+#my $we = new_editor(xact=>1, requestor=>1);
+#$we = new_editor(xact=>1, requestor=>$auth);
+$we = new_editor(xact=>1, authtoken => $auth);
+my $notify = Fieldmapper::action::hold_notification->new;
+$notify->hold($holdid);
+$notify->notify_time('now');
+$notify->method($status);
+$notify->notify_staff(1);
+$we->create_action_hold_notification($notify)
+	or return $we->die_event;
+$we->commit;
+
+#my $e = new_editor(authtoken=>$auth, xact=>1);
+#my $hold = $e->retrieve_action_hold_request($holdid);
+#$hold->expire_time($d->ymd);
+#$e->update_action_hold_request($hold)
+#      or return $e->event;
+#$e->commit; 
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server/tadl_holdshelf_all.cgi
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server/tadl_holdshelf_all.cgi	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/evergreen-server/tadl_holdshelf_all.cgi	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,88 @@
+#!/usr/bin/perl
+
+# GRPL-shared code
+# oils_login() needs credentials
+# minor tweak by TADL to output all holds on shelf, not just those
+# that have not yet been notified
+# 
+# hold id 53619 is when we re-enabled the phone 
+# notification option in the patron OPAC
+# -- we force phone notification for holds before that id
+#
+
+require '/openils/bin/support-scripts/oils_header.pl';
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use CGI qw(:standard);
+
+print "Content-type: text/html\n\n";
+
+my $conf = '/openils/conf/opensrf_core.xml';
+
+foreach my $loc (23..28) {
+        osrf_connect($conf);
+        my $authtok = oils_login('','');
+
+	my $ses = OpenSRF::AppSession->create( "open-ils.circ" );
+	my $req = $ses->request('open-ils.circ.captured_holds.on_shelf.retrieve', $authtok, $loc);
+	while(my $resp = $req->recv(timeout => 120)) {
+		my $hid = $resp->content;
+                my $hd = simplereq( CIRC(), 'open-ils.circ.hold.details.retrieve', $authtok, $$hid[14]);
+                my $hh = wrap_perl($hd);
+
+		my $avtime;
+		if ($hh->{hold}->{transit}) {
+			$avtime = $hh->{hold}->{transit}->{dest_recv_time}
+		}
+
+		my $pnotify = $hh->{hold}->{phone_notify};
+                my $p  = simplereq( ACTOR(),'open-ils.actor.user.fleshed.retrieve', $authtok, $hh->{hold}->{usr});
+                my $ph = wrap_perl($p);
+		if ( !$pnotify && ($hh->{hold}->{id} < 53619) ) {
+			$pnotify = $ph->{day_phone};
+		}
+		$pnotify =~ s/(\d+)\D+(\d+)\D+(\d+).*/$1$2$3/;
+                $pnotify =~ s/\(//;
+                $pnotify =~ s/\///g;
+		foreach my $sc (@{$p}[10]) {
+			foreach my $v (@{$sc}) { 
+				if ($v->{stat_cat_entry} eq 'Block Calls') {
+					$pnotify = 'Block Calls';
+				}
+			}
+		}
+                my $o = simplereq(ACTOR(), 'open-ils.actor.org_unit.retrieve', $authtok, $loc);
+                my $oh = wrap_perl($o);
+                my $oa = simplereq(ACTOR(), 'open-ils.actor.org_unit.address.retrieve', $oh->{mailing_address});
+                my $oah= wrap_perl($oa);
+		$avtime = $hh->{hold}->{capture_time} unless $avtime;
+		$avtime =~ s/(\d\d\d\d-\d\d-\d\d)T(\d\d:\d\d:\d\d).*/$1 $2/;
+		print "$hh->{hold}->{id}|$ph->{first_given_name}|$ph->{family_name}|$ph->{mailing_address}->{street1}|$ph->{mailing_address}->{city}|$ph->{mailing_address}->{state}|$ph->{mailing_address}->{post_code}|$ph->{email}|$hh->{hold}->{email_notify}|$pnotify|$hh->{hold}->{notify_time}|$avtime|$hh->{mvr}->{title}|$hh->{mvr}->{author}|$oh->{shortname}|$oah->{street1}|$oah->{city}|$oah->{state}|$oah->{post_code}||\n";
+   } # end while
+} # end foreach
+
+sub wrap_perl {
+   my $obj = shift;
+   my $ref = ref($obj);
+
+   if ($ref =~ /^Fieldmapper/o) {
+      $ref = $obj->json_hint;
+      $obj = $obj->to_bare_hash;
+   }
+
+   if( $ref eq 'HASH' ) {
+      $obj->{$_} = wrap_perl( $obj->{$_} ) for (keys %$obj);
+   } elsif( $ref eq 'ARRAY' ) {
+      $obj->[$_] = wrap_perl( $obj->[$_] ) for(0..scalar(@$obj) - 1 );
+   } elsif( $ref ) {
+      if(UNIVERSAL::isa($obj, 'HASH')) {
+         $obj->{$_} = wrap_perl( $obj->{$_} ) for (keys %$obj);
+         bless($obj, 'HASH'); # so our parser won't add the hints
+      } elsif(UNIVERSAL::isa($obj, 'ARRAY')) {
+         $obj->[$_] = wrap_perl( $obj->[$_] ) for(0..scalar(@$obj) - 1);
+         bless($obj, 'ARRAY'); # so our parser won't add the hints
+      }
+#      $obj = { $CLASS_KEY => $ref, $PAYLOAD_KEY => $obj };
+   }
+   return $obj;
+}
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/holdshelf-schema.mysqldump
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/holdshelf-schema.mysqldump	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/holdshelf-schema.mysqldump	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,57 @@
+-- MySQL dump 10.11
+--
+-- Host: localhost    Database: notify
+-- ------------------------------------------------------
+-- Server version	5.0.32-Debian_7etch8-log
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `holdshelf`
+--
+
+DROP TABLE IF EXISTS `holdshelf`;
+CREATE TABLE `holdshelf` (
+  `id` int(11) NOT NULL default '0',
+  `first` varchar(32) default NULL,
+  `last` varchar(64) default NULL,
+  `street` varchar(128) default NULL,
+  `city` varchar(32) default NULL,
+  `state` varchar(2) default NULL,
+  `zip` varchar(10) default NULL,
+  `email` varchar(128) default NULL,
+  `email_notify` varchar(1) default NULL,
+  `phone_notify` varchar(12) default NULL,
+  `notify_time` datetime default NULL,
+  `capture_time` datetime default NULL,
+  `title` varchar(255) default NULL,
+  `author` varchar(128) default NULL,
+  `pickup_lib` varchar(128) default NULL,
+  `pickup_street` varchar(128) default NULL,
+  `pickup_city` varchar(32) default NULL,
+  `pickup_state` varchar(2) default NULL,
+  `pcikup_zip` varchar(10) default NULL,
+  `notify_status` varchar(255) default NULL,
+  `failcount` int(11) default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2009-02-13 16:21:27

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/email-overdues.py
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/email-overdues.py	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/email-overdues.py	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,72 @@
+#!/usr/bin/python
+
+"""
+Copyright 2009 Traverse Area District Library
+Jeff Godin <jeff at tcnet.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+"""
+
+import smtplib
+# these are for python 2.4 -- python 2.5 is different
+from email.MIMEText import MIMEText
+from email.Utils import make_msgid
+# python 2.5 uses:
+#from email.mime.text import MIMEText
+#from email.utils import make_msgid
+
+import psycopg2 as dbapi2
+import psycopg2.extensions
+
+# without this, psycopg2 will often return utf-8 encoded str objects,
+# and we want unicode objects here
+psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
+
+db = dbapi2.connect(database="notify", user="notify")
+cur = db.cursor()
+cur.execute("select count(item_circ_id) as total_overdue, date_trunc('day', max(file_time))::date as file_date, patron_fullname, patron_email, patron_sys_id from noticeitems where patron_email <> '' and notified_email = false group by patron_fullname, patron_email, patron_sys_id")
+rows = cur.fetchall()
+for row in rows:
+    (total_overdue, file_date, patron_fullname, patron_email, patron_sys_id) = row
+    body = u'\nOur records show that the following '
+    if ( total_overdue == 1 ):
+        body = body + 'item is overdue. \n\n'
+    else:
+        body = body + '%s items are overdue. \n\n' % (total_overdue, )
+    itemcur = db.cursor()
+    itemcur.execute('select distinct item_circ_id, item_title, item_author, item_duedate FROM noticeitems WHERE notified_email = false and patron_sys_id = %s;', (patron_sys_id, ))
+    itemrows = itemcur.fetchall()
+    for itemrow in itemrows:
+        (item_circ_id, item_title, item_author, item_duedate) = itemrow
+        body = body + "Title: " + item_title.strip() + '\n'
+        body = body + "Author: " + item_author.strip() + '\n'
+        body = body + "Due: " + str(item_duedate) + '\n'
+        body = body + '\n'
+    if ( total_overdue == 1 ):
+        itemref = 'this item'
+    else:
+        itemref = 'these items'
+    body = body + 'If you have already returned %s, please disregard this message.\nFor your convenience, you may return %s to any of the libraries in the Traverse Area District Library system.\n\nThank you!' % (itemref, itemref)
+    msg = MIMEText(body, 'plain', 'latin-1')
+    msg['Subject'] = 'Overdue library materials as of %s' % (file_date, )
+    msg['From'] = 'TADL Evergreen Notifications <notify at catalog.tadl.org>'
+    to = patron_fullname + ' <' + patron_email + '>'
+    msg['To'] = to
+    msgid = make_msgid()
+    msg['Message-ID'] = msgid
+    print msg.as_string()
+    s = smtplib.SMTP('egmx')
+    s.sendmail('notify at catalog.tadl.org', patron_email, msg.as_string())
+    s.close()
+    statuscur = db.cursor()
+    statuscur.execute("UPDATE noticeitems SET notified_email = true, notified_email_msgid = %s, notified_email_time = now() where notified_email = false and patron_sys_id = %s", (msgid, patron_sys_id, ))
+    db.commit()
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/import-notices.py
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/import-notices.py	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/import-notices.py	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,86 @@
+#!/usr/bin/python
+
+"""
+Copyright 2009 Traverse Area District Library
+Jeff Godin <jeff at tcnet.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+"""
+
+from optparse import OptionParser
+import string
+import sys
+
+import psycopg2 as dbapi2
+
+from xml2obj import Xml2Obj
+
+optparser = OptionParser()
+optparser.add_option("-f", "--file", dest="filename")
+(options, args) = optparser.parse_args()
+xmlparser = Xml2Obj()
+if (options.filename == None):
+    optparser.error('-f/--file argument is required')
+
+file = xmlparser.Parse(options.filename)
+
+db = dbapi2.connect (database="notify", user="notify")
+cur = db.cursor()
+
+file_type = file.getAttribute('type')
+file_time = file.getAttribute('date') + ' ' + file.getAttribute('time')
+agencies = file.getElements('agency')
+for agency in agencies:
+    agency_name = agency.getAttribute('name')
+    notices = agency.getElements('notice')
+    for notice in notices:
+        notice_type = notice.getAttribute('type')
+        notice_count = notice.getAttribute('count')
+        patron = notice.getElements('patron')[0]
+        patron_sys_id = patron.getElements('sys_id')[0].getData()
+        patron_barcode = patron.getElements('id')[0].getData()
+        patron_fullname = patron.getElements('fullname')[0].getData()
+        patron_day_phone = patron.getElements('day_phone')[0].getData()
+        patron_email = patron.getElements('email')[0].getData()
+        libraries = notice.getElements('library')
+        for library in libraries:
+            library_name = library.getElements('libname')[0].getData()
+            items = notice.getElements('item')
+            for item in items:
+                item_title = item.getElements('title')[0].getData()
+                item_author = item.getElements('author')[0].getData()
+                item_duedate = item.getElements('duedate')[0].getData()
+                item_callno = item.getElements('callno')[0].getData()
+                item_location = item.getElements('location')[0].getData()
+                item_barcode = item.getElements('barcode')[0].getData()
+                item_circ_id = item.getElements('circ_id')[0].getData()
+                item_check_out = item.getElements('check_out')[0].getData()
+                item_item_price = item.getElements('item_price')[0].getData()
+                if isinstance(item_author, str):
+                    print type(item_author)
+                    print item_author
+                insert_query = "INSERT INTO noticeitems (file_type, \
+                 file_time, agency_name, notice_type, notice_count, \
+                 patron_sys_id, patron_barcode, patron_fullname, \
+                 patron_day_phone, patron_email, library_name, item_title, \
+                 item_author, item_duedate, item_callno, item_location, \
+                 item_barcode, item_circ_id, item_check_out, item_item_price) \
+                 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, \
+                 %s, %s, %s, %s, %s, %s, %s)"
+                insert_row = (file_type, file_time, agency_name, notice_type,
+                              notice_count, patron_sys_id, patron_barcode, 
+                              patron_fullname, patron_day_phone, patron_email,
+                              library_name, item_title, item_author,
+                              item_duedate, item_callno, item_location,
+                              item_barcode, item_circ_id, item_check_out,
+                              item_price)
+                cur.execute(insert_query, insert_row)
+                db.commit()

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/xml2obj.py
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/xml2obj.py	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/home/notify/bin/xml2obj.py	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,103 @@
+#!/usr/bin/python
+
+"""
+This code is taken from ActiveState Code Recipe 149368: xml2obj
+"A generic script using expat to convert xml into objects"
+http://code.activestate.com/recipes/149368/
+posted Wed, 11 Sep 2002
+
+Note: Recipes that were part of the original Python Cookbook -- Python
+recipes submitted before July 15, 2008 -- on ASPN (aspn.activestate.com) are
+under the Python license in accordance with the Python Cookbook agreement.
+"""
+
+"""
+Borrowed from wxPython XML tree demo and modified.
+"""
+
+import string
+from xml.parsers import expat
+
+class Element:
+    'A parsed XML element'
+    def __init__(self,name,attributes):
+        'Element constructor'
+        # The element's tag name
+        self.name = name
+        # The element's attribute dictionary
+        self.attributes = attributes
+        # The element's cdata
+        self.cdata = u''
+        # The element's child element list (sequence)
+        self.children = []
+        
+    def AddChild(self,element):
+        'Add a reference to a child element'
+        self.children.append(element)
+        
+    def getAttribute(self,key):
+        'Get an attribute value'
+        return self.attributes.get(key)
+    
+    def getData(self):
+        'Get the cdata'
+        return self.cdata
+        
+    def getElements(self,name=''):
+        'Get a list of child elements'
+        #If no tag name is specified, return the all children
+        if not name:
+            return self.children
+        else:
+            # else return only those children with a matching tag name
+            elements = []
+            for element in self.children:
+                if element.name == name:
+                    elements.append(element)
+            return elements
+
+class Xml2Obj:
+    'XML to Object'
+    def __init__(self):
+        self.root = None
+        self.nodeStack = []
+        
+    def StartElement(self,name,attributes):
+        'SAX start element even handler'
+        # Instantiate an Element object
+        element = Element(name.encode(),attributes)
+        
+        # Push element onto the stack and make it a child of parent
+        if len(self.nodeStack) > 0:
+            parent = self.nodeStack[-1]
+            parent.AddChild(element)
+        else:
+            self.root = element
+        self.nodeStack.append(element)
+        
+    def EndElement(self,name):
+        'SAX end element event handler'
+        self.nodeStack = self.nodeStack[:-1]
+
+    def CharacterData(self,data):
+        'SAX character data event handler'
+        if string.strip(data):
+            #data = data.encode()
+            element = self.nodeStack[-1]
+            element.cdata += data
+            return
+
+    def Parse(self,filename):
+        # Create a SAX parser
+        Parser = expat.ParserCreate()
+
+        # SAX event handlers
+        Parser.StartElementHandler = self.StartElement
+        Parser.EndElementHandler = self.EndElement
+        Parser.CharacterDataHandler = self.CharacterData
+
+        # Parse the XML File
+        ParserStatus = Parser.Parse(open(filename,'r').read(), 1)
+        
+        return self.root
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/noticeitems-schema.txt
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/noticeitems-schema.txt	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/noticeitems-schema.txt	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,26 @@
+create table noticeitems (
+file_type text,
+file_time timestamp,
+agency_name text,
+notice_type text,
+notice_count text,
+patron_sys_id text,
+patron_barcode text,
+patron_fullname text,
+patron_day_phone text,
+patron_email text,
+library_name text,
+item_title text,
+item_author text,
+item_duedate date,
+item_callno text,
+item_location text,
+item_barcode text,
+item_circ_id text,
+item_check_out date,
+item_item_price money,
+notified_email boolean default False,
+notified_email_msgid text,
+notified_email_time timestamp with time zone
+);
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root/import_holdshelf_all.plx
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root/import_holdshelf_all.plx	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root/import_holdshelf_all.plx	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,46 @@
+#!/usr/bin/perl -w
+
+# GRPL-shared code with some messy SQL for handling the way TADL
+# uses the holdshelf database for notifications
+# * also added "failcount" to track number of failed calls
+# * "holdshelfnonotify" for holds we can't even attempt to notify 
+#
+
+use LWP::UserAgent();
+use XML::Simple;
+
+$ua=LWP::UserAgent->new();
+
+$res=$ua->get('http://eg1.michiganevergreen.org/cgi-bin/utils/tadl/tadl_holdshelf_all.cgi');
+$c = $res->content;
+
+$outname = ((localtime)[4]+1).(localtime)[3].(localtime)[2].(localtime)[1].'holdshelf_all';
+open(OUT, ">/root/$outname");
+print OUT $c;
+close OUT;
+
+system('mysql', 'notify', '-e', "load data infile '/root/$outname' ignore into table holdshelf fields terminated by '|'");
+
+#stop forcing email notification on
+#system('mysql', 'notify', '-e', "update holdshelf set email_notify = 't' where email <> '' and email_notify = 'f';");
+#and only force it on for pre-migration-complete holds:
+system('mysql', 'notify', '-e', "update holdshelf set email_notify = 't' where id <= 45446 and email <> '' and email_notify = 'f';");
+system('mysql', 'notify', '-e', "update holdshelf set email_notify = 'f' where email = '' and email_notify = 't';");
+
+system('mysql', 'notify', '-e', "drop table holdnonotify;");
+system('mysql', 'notify', '-e', "create table holdnonotify like holdshelf;");
+system('mysql', 'notify', '-e', "insert into holdnonotify select * from holdshelf where email_notify='f' and phone_notify='';");
+system('mysql', 'notify', '-e', "delete from holdshelf where email_notify='f' and phone_notify=''");
+
+system('mysql', 'notify', '-e', "drop table tempshelf;"); 
+system('mysql', 'notify', '-e', "create table tempshelf like holdshelf;");
+system('mysql', 'notify', '-e', "load data infile '/root/$outname' ignore into table tempshelf fields terminated by '|'");
+
+system('mysql', 'notify', '-e', "drop table delshelf;"); 
+system('mysql', 'notify', '-e', "create table delshelf like holdshelf;");
+system('mysql', 'notify', '-e', "insert into delshelf select holdshelf.* from holdshelf left join tempshelf on holdshelf.id = tempshelf.id where tempshelf.id is null;");
+system('mysql', 'notify', '-e', "delete holdshelf from holdshelf inner join delshelf where delshelf.id = holdshelf.id;");
+
+system('mysql', 'notify', '-e', "update holdshelf set notify_status = '', failcount = coalesce(failcount,0) + 1 where notify_status = 'failed';");
+
+#%hold_info = %{XMLin($c, NoAttr => 1, ForceArray => 1)};

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root/send_holdshelf_emails.plx
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root/send_holdshelf_emails.plx	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/root/send_holdshelf_emails.plx	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,66 @@
+#!/usr/bin/perl -w
+
+# GRPL-shared code with minor (mostly config) tweaks for TADL
+
+use DBI;
+use DBD::mysql;
+use MIME::Lite;
+use LWP;
+
+my $dbh = DBI->connect('dbi:mysql:database=notify', "", '', { RaiseError => 1, PrintError => 1 } );
+#$dbh->trace(4);
+
+my $sth=$dbh->prepare("select id,email,email_notify,pickup_lib,title from holdshelf where email <> '' and email_notify='t';");
+$sth->execute;
+
+my $updh=$dbh->prepare("update holdshelf set email_notify='s',notify_time=now() where id = ?");
+
+while (my $m=$sth->fetchrow_hashref) {
+	my $id = $m->{id};
+	my $addr = $m->{email};
+	my $lib = $m->{pickup_lib};
+	my $title = $m->{title};
+	my $branch;
+	my $pnum;
+
+	SWITCH: {
+		$_ = $lib;
+		/TADL-EBB/ and do {$branch="East Bay Branch"; $pnum="231 922-2085"; last SWITCH; };
+		/TADL-FLPL/ and do {$branch="Fife Lake Public Library"; $pnum="231 879-4101"; last SWITCH; };
+		/TADL-IPL/ and do {$branch="Interlochen Public Library"; $pnum="231 276-6767"; last SWITCH; };
+		/TADL-KBL/ and do {$branch="Kingley Branch"; $pnum="231 263-5484"; last SWITCH; };
+		/TADL-PCL/ and do {$branch="Peninsula Community Library"; $pnum="231 223-7700"; last SWITCH; };
+		/TADL-WOOD/ and do {$branch="Woodmere (Main) Branch"; $pnum="231 932-8500"; last SWITCH; };
+	}
+	print "sending for hold $id\n";
+	send_me($addr,$branch,$pnum,$title);
+	$updh->execute($id);
+	# update hold notice in Evergreen database
+        my $ua=LWP::UserAgent->new();
+        my $hn=$ua->get("http://eg1.michiganevergreen.org/cgi-bin/utils/tadl/tadl_holdnotice.cgi?h=$id&s=email_sent");
+
+}
+
+
+sub send_me {
+   my ($a,$b,$p,$t) = @_;
+
+   $a =~ s/(.*?)(\;.*)/$1/;  # if someone thinks it's a good idea to have multiple addresses separated by a semi-colon, trash the second.
+
+   my $body = "Your hold item: $t is now available for pickup at the $b. \n This hold will expire in 7 days. For more information please call $p. \n\n This is an outgoing email address only.  Please do not respond.";
+
+   my $msg = MIME::Lite->new(
+    To      =>"$a",
+    From    =>'"TADL Catalog" <Reserve.Notification at catalog.tadl.org>',
+    Subject =>'Your reserve is in at the Traverse Area District Library',
+    Type    =>'multipart/mixed'
+         );
+
+    $msg->attach(
+        Type => 'text/plain',
+        Data => "$body");
+
+    $msg->send('smtp','egmx.in.tcnet.org',Debug=>0);
+
+}
+

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/holdshelf_template
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/holdshelf_template	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/holdshelf_template	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,13 @@
+; GRPL-shared template, used by make_holdshelf_callfiles.plx
+; see that script for mention of TADL changes
+CallerID: CIDNAME <CIDNO>
+Channel: SIP/clearrate-voip1/NUM   ; outgoing channel and phone number to dial
+Set: holdid=HOLDID             ; local variable to identify hold
+Set: pickuplib=PKUPLIB     ; local variable to identify pickup library
+WaitTime: 60            ; total time allowed to complete call
+MaxRetries: 0           ; retry attempts that is, initial call plus 1 retry = 2 calls
+RetryTime: 600          ; time between retries
+Context: holdshelfmsg      ; see extensions.conf
+Extension: s            ; see extensions.conf
+Priority: 1
+Set: PassedInfo= NUM

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/make_holdshelf_callfiles.plx
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/make_holdshelf_callfiles.plx	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/make_holdshelf_callfiles.plx	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,117 @@
+#!/usr/bin/perl -w
+
+# GRPL-shared code 
+# Summary of TADL modifications to this file:
+# * removed channel switching logic - not applicable for us, we use SIP
+# * added caller-id mapping per pickup lib shortname
+# * changed some of the SQL logic
+# * added various queries for placing calls to different subsets of holds
+#  (failed, not previously failed, only those not yet emailed, etc)
+#
+
+use DBI;
+use DBD::mysql;
+
+my $dbh = DBI->connect('dbi:mysql:database=notify', "", '', { RaiseError => 1, PrintError => 1 } );
+#$dbh->trace(4);
+
+# get distinct phone number/pickup library combinations
+#my $sth=$dbh->prepare("select id,phone_notify,count(phone_notify) as hnum ,pickup_lib from holdshelf where (notify_time = 0 and notify_status = '') group by phone_notify,pickup_lib having phone_notify <> '' and phone_notify <> 'Block Calls'");
+#my $sth=$dbh->prepare("select id,phone_notify,count(phone_notify) as hnum ,pickup_lib from holdshelf where (email_notify = 'f' and notify_status = '') group by phone_notify,pickup_lib having phone_notify <> '' and phone_notify <> 'Block Calls'");
+
+# normal operation
+my $sth=$dbh->prepare("select id,phone_notify,count(phone_notify) as hnum ,pickup_lib from holdshelf where (notify_status = '') group by phone_notify,pickup_lib having phone_notify <> '' and phone_notify <> 'Block Calls'");
+
+# just those without a failcount
+#my $sth=$dbh->prepare("select id,phone_notify,count(phone_notify) as hnum ,pickup_lib from holdshelf where (notify_status = '' and failcount < 1) group by phone_notify,pickup_lib having phone_notify <> '' and phone_notify <> 'Block Calls'");
+
+# just those with failcount > 0
+#my $sth=$dbh->prepare("select id,phone_notify,count(phone_notify) as hnum ,pickup_lib from holdshelf where (notify_status = '' and failcount > 0) group by phone_notify,pickup_lib having phone_notify <> '' and phone_notify <> 'Block Calls'");
+
+# just those without email
+#my $sth=$dbh->prepare("select id,phone_notify,count(phone_notify) as hnum ,pickup_lib from holdshelf where (notify_status = '' and email = '') group by phone_notify,pickup_lib having phone_notify <> '' and phone_notify <> 'Block Calls'");
+$sth->execute;
+
+#my $sth2= $dbh->prepare("select id from holdshelf where notify_time = 0 and phone_notify = ? and pickup_lib = ?");
+#my $sth2= $dbh->prepare("select id from holdshelf where email_notify = 'f' and phone_notify = ? and pickup_lib = ?");
+my $sth2= $dbh->prepare("select id from holdshelf where phone_notify = ? and pickup_lib = ?");
+
+# define array of lib shortname to callerid data
+%cid = (
+	"TADL-WOOD" => {
+		name	=> "TADL",
+		number	=> "2319328500",
+	},
+	"TADL-IPL" => {
+		name	=> "IPL",
+		number	=> "2312766767",
+	},
+	"TADL-KBL" => {
+		name	=> "KBL",
+		number	=> "2312635484",
+	},
+	"TADL-PCL" => {
+		name	=> "PCL",
+		number	=> "2312237700",
+	},
+	"TADL-FLPL" => {
+		name	=> "FLPL",
+		number	=> "2318794101",
+	},
+	"TADL-EBB" => {
+		name	=> "EBB",
+		number	=> "2319222085",
+	},
+);
+
+my $f;
+my $cft;
+
+# slurp in default call file
+{
+$f = '/usr/notify/asterisk/holdshelf_template';
+local( $/, *CFT ) ;
+open( CFT, $f) or die "Can't open $f";
+$cft = <CFT>;
+}
+
+while (my $h=$sth->fetchrow_hashref) {
+	my $id = $h->{id};
+	my $num = $h->{phone_notify};
+	my $numholds = $h->{hnum};
+	my $lib = $h->{pickup_lib};
+
+	if ($numholds > 1) { #get all hold ids for this phone number/pickup library
+		$sth2->execute($num,$lib);
+		$id = '';
+		map { $id .= ($$_[0] . ',') } @{$sth2->fetchall_arrayref};
+		chop $id;
+	}
+
+
+        # substitue hold info into call file timeplate
+        my $temp = $cft;
+        $temp =~ s/NUM/$num/g;
+	$temp =~ s/HOLDID/$id/;
+	$temp =~ s/PKUPLIB/$lib/;
+	$temp =~ s/CIDNAME/$cid{$lib}{name}/;
+	$temp =~ s/CIDNO/$cid{$lib}{number}/;
+
+
+        # write new call file
+        my $cf = "/usr/notify/asterisk/tmp/$num$lib.holdshelf";
+        open(OUT, ">$cf");
+        print OUT $temp;
+        close OUT;
+
+        # set call file time
+        #my $newtime = time + 120;
+        #my $stamp =
+        #system('touch', '-t', $stamp, $cf);
+
+        # move into queue
+        # system ('mv', $cf, '/var/spool/asterisk/outgoing');
+	# scp to asterisk server
+
+}
+


Property changes on: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/notify-box/usr/notify/asterisk/make_holdshelf_callfiles.plx
___________________________________________________________________
Name: svn:executable
   + 

Added: grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/overdues-sample.xml
===================================================================
--- grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/overdues-sample.xml	                        (rev 0)
+++ grpl/trunk/patron_notifications/tadl-egnotify-scripts_20090306/overdues-sample.xml	2009-04-17 19:42:02 UTC (rev 351)
@@ -0,0 +1,124 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<file type="notice" date="8/4/2008" time="5:5:7">
+<agency name="MLC">
+<notice type='overdue' count='14day'>
+<patron>
+<id type="barcode">22780000000001</id>
+<fullname>Mitchell E Mouse</fullname>
+<street1>2003 E WEST ST</street1>
+<city_state_zip>Union City, MI 49094</city_state_zip>
+<sys_id>17884</sys_id>
+<day_phone></day_phone>
+<email></email>
+</patron>
+<library>
+<libname>Union Township Branch</libname>
+<libphone></libphone>
+<libstreet1>221 N. Broadway St. </libstreet1>
+<libcity_state_zip>Union City, MI 49094-1153</libcity_state_zip>
+</library>
+<item>
+<title>South Carolina sea creatures</title>
+<author>Rand, Johnathan.</author>
+<duedate>7/21/2008</duedate>
+<callno>J FIC RAN17</callno>
+<location>123</location>
+<barcode>35406423940095</barcode>
+<circ_id>271367</circ_id>
+<check_out>7/1/2008</check_out>
+<item_price>11.95</item_price>
+</item>
+</notice>
+<notice type='overdue' count='14day'>
+<patron>
+<id type="barcode">2278000000002</id>
+<fullname>Robert Smith</fullname>
+<street1>3111 SOUTH MAIN</street1>
+<city_state_zip>SHERWOOD, MI 49089</city_state_zip>
+<sys_id>22834</sys_id>
+<day_phone></day_phone>
+<email></email>
+</patron>
+<library>
+<libname>Union Township Branch</libname>
+<libphone></libphone>
+<libstreet1>221 N. Broadway St. </libstreet1>
+<libcity_state_zip>Union City, MI 49094-1153</libcity_state_zip>
+</library>
+<item>
+<title>Say cheese and die!</title>
+<author>Stine, R. L.</author>
+<duedate>7/21/2008</duedate>
+<callno>J FIC STI4</callno>
+<location>123</location>
+<barcode>35406423825908</barcode>
+<circ_id>273690</circ_id>
+<check_out>7/1/2008</check_out>
+<item_price>11.95</item_price>
+</item>
+</notice>
+<notice type='overdue' count='14day'>
+<patron>
+<id type="barcode">254010000001</id>
+<fullname>John S. Doe</fullname>
+<street1>100 SMITH STREET </street1>
+<city_state_zip>UNION CITY, MI 49094</city_state_zip>
+<sys_id>26988</sys_id>
+<day_phone></day_phone>
+<email></email>
+</patron>
+<library>
+<libname>Union Township Branch</libname>
+<libphone></libphone>
+<libstreet1>221 N. Broadway St. </libstreet1>
+<libcity_state_zip>Union City, MI 49094-1153</libcity_state_zip>
+</library>
+<item>
+<title>Violin</title>
+<author>Rice, Anne</author>
+<duedate>7/21/2008</duedate>
+<callno>FIC RIC</callno>
+<location>123</location>
+<barcode>35406423824893</barcode>
+<circ_id>271615</circ_id>
+<check_out>7/1/2008</check_out>
+<item_price>11.95</item_price>
+</item>
+<item>
+<title>Family first : your step-by-step plan for creating a phenomenal 
+family</title>
+<author>McGraw, Phillip C.</author>
+<duedate>7/21/2008</duedate>
+<callno>649.1 MCG</callno>
+<location>123</location>
+<barcode>35406423934866</barcode>
+<circ_id>271914</circ_id>
+<check_out>7/1/2008</check_out>
+<item_price>11.95</item_price>
+</item>
+<item>
+<title>Servant of the bones </title>
+<author>Rice, Anne</author>
+<duedate>7/21/2008</duedate>
+<callno>FIC RIC</callno>
+<location>123</location>
+<barcode>35406423817111</barcode>
+<circ_id>272450</circ_id>
+<check_out>7/1/2008</check_out>
+<item_price>11.95</item_price>
+</item>
+<item>
+<title>Christ the Lord out of Egypt : a novel</title>
+<author>Rice, Anne</author>
+<duedate>7/21/2008</duedate>
+<callno>FIC RIC</callno>
+<location>123</location>
+<barcode>35406423946969</barcode>
+<circ_id>272496</circ_id>
+<check_out>7/1/2008</check_out>
+<item_price>11.95</item_price>
+</item>
+</notice>
+</agency>
+</file>
+



More information about the open-ils-commits mailing list