3 # Copyright (C) 2011 ByWater Solutions
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
25 # find Koha's Perl modules
26 # test carefully before changing this
28 eval { require "$FindBin::Bin/../kohalib.pl" };
31 use Getopt::Long qw( GetOptions );
32 use Pod::Usage qw( pod2usage );
34 use Koha::Script -cron;
39 use Koha::DateUtils qw( dt_from_string output_pref );
44 pod2usage( -verbose => 2 );
48 die "TalkingTechItivaPhoneNotification system preference not activated... dying\n"
49 unless ( C4::Context->preference("TalkingTechItivaPhoneNotification") );
52 my $dbh = C4::Context->dbh;
58 my @holds_waiting_days_to_call;
62 my $skip_patrons_with_email;
63 my $patron_branchcode;
65 # maps to convert I-tiva terms to Koha terms
66 my $type_module_map = {
67 'PREOVERDUE' => 'circulation',
68 'OVERDUE' => 'circulation',
69 'RESERVE' => 'reserves',
72 my $type_notice_map = {
73 'PREOVERDUE' => 'PREDUE',
74 'OVERDUE' => 'OVERDUE',
79 'o|output:s' => \$outfile,
81 'lang:s' => \$language,
83 'w|waiting-hold-day:s' => \@holds_waiting_days_to_call,
84 'c|code|library-code:s' => \$library_code,
85 's|skip-patrons-with-email' => \$skip_patrons_with_email,
86 'pb|patron-branchcode:s' => \$patron_branchcode,
90 $language = uc($language);
93 pod2usage( -verbose => 1 ) if $help;
95 if ($patron_branchcode) {
96 die("Invalid branchcode '$patron_branchcode' passed in -pb --patron-branchcode parameter")
97 unless Koha::Libraries->search( { branchcode => $patron_branchcode } )->count;
100 # output log or STDOUT
102 if ( defined $outfile ) {
103 open( $OUT, '>', "$outfile" ) || die("Cannot open output file");
105 print "No output file defined; printing to STDOUT\n"
106 if ( defined $verbose );
107 $OUT = *STDOUT || die "Couldn't duplicate STDOUT: $!";
110 my $format = 'V'; # format for phone notifications
112 foreach my $type (@types) {
113 $type = uc($type); #just in case lower or mixed-case was supplied
114 my $module = $type_module_map->{$type}; #since the module is required to get the letter
115 my $code = $type_notice_map->{$type}; #to get the Koha name of the notice
118 if ( $type eq 'OVERDUE' ) {
119 @loop = GetOverdueIssues( $patron_branchcode );
120 } elsif ( $type eq 'PREOVERDUE' ) {
121 @loop = GetPredueIssues( $patron_branchcode );
122 } elsif ( $type eq 'RESERVE' ) {
123 @loop = GetWaitingHolds( $patron_branchcode );
125 print "Unknown or unsupported message type $type; skipping...\n"
126 if ( defined $verbose );
131 foreach my $issues (@loop) {
132 $patrons->{$issues->{borrowernumber}} ||= Koha::Patrons->find( $issues->{borrowernumber} ) if $skip_patrons_with_email;
133 next if $skip_patrons_with_email && $patrons->{$issues->{borrowernumber}}->notice_email_address;
135 my $date_dt = dt_from_string ( $issues->{'date_due'} );
136 my $due_date = output_pref( { dt => $date_dt, dateonly => 1, dateformat =>'metric' } );
138 my $letter = C4::Letters::GetPreparedLetter(
140 letter_code => $code,
141 lang => 'default', # It does not sound useful to send a lang here
143 borrowers => $issues->{'borrowernumber'},
144 biblio => $issues->{'biblionumber'},
145 biblioitems => $issues->{'biblionumber'},
147 message_transport_type => 'itiva',
150 die "No letter found for type $type!... dying\n" unless $letter;
154 $message_id = C4::Letters::EnqueueLetter(
156 borrowernumber => $issues->{'borrowernumber'},
157 message_transport_type => 'itiva',
162 $issues->{title} =~ s/'//g;
163 $issues->{title} =~ s/"//g;
165 print $OUT "\"$format\",\"$language\",\"$type\",\"$issues->{level}\",\"$issues->{cardnumber}\",\"$issues->{patron_title}\",\"$issues->{firstname}\",";
166 print $OUT "\"$issues->{surname}\",\"$issues->{phone}\",\"$issues->{email}\",\"$library_code\",";
167 print $OUT "\"$issues->{site}\",\"$issues->{site_name}\",\"$issues->{barcode}\",\"$due_date\",\"$issues->{title}\",\"$message_id\"\n";
173 TalkingTech_itiva_outbound.pl
177 TalkingTech_itiva_outbound.pl
178 TalkingTech_itiva_outbound.pl --type=OVERDUE -w 0 -w 2 -w 6 --output=/tmp/talkingtech/outbound.csv
179 TalkingTech_itiva_outbound.pl --type=RESERVE --type=PREOVERDUE --lang=FR
182 Script to generate Spec C outbound notifications file for Talking Tech i-tiva
183 phone notification system.
187 =item B<--help> B<-h>
193 Provide verbose log information.
195 =item B<--output> B<-o>
197 Destination for outbound notifications file (CSV format). If no value is specified,
198 output is dumped to screen.
202 Sets the language for all outbound messages. Currently supported values are EN, FR and ES.
203 If no value is specified, EN will be used by default.
207 REQUIRED. Sets which messaging types are to be used. Can be given multiple times, to
208 specify multiple types in a single output file. Currently supported values are RESERVE, PREOVERDUE
209 and OVERDUE. If no value is given, this script will not produce any outbound notifications.
211 =item B<--waiting-hold-day> B<-w>
213 OPTIONAL for --type=RESERVE. Sets the days after a hold has been set to waiting on which to call. Use
214 switch as many times as desired. For example, passing "-w 0 -w 2 -w 6" will cause calls to be placed
215 on the day the hold was set to waiting, 2 days after the waiting date, and 6 days after. See example above.
216 If this switch is not used with --type=RESERVE, calls will be placed every day until the waiting reserve
217 is picked up or canceled.
219 =item B<--library-code> B<--code> B<-c>
222 The code of the source library of the message.
223 The library code is used to group notices together for
224 consortium purposes and apply library specific settings, such as
225 prompts, to those notices.
226 This field can be blank if all messages are from a single library.
228 =item B<--patron-branchcode> B<--pb>
232 Limits the the patrons to generate notices for based on the patron's home library.
233 Items and holds from other libraries will still be included for the given patron.
239 sub GetOverdueIssues {
240 my ( $patron_branchcode ) = @_;
242 my $patron_branchcode_filter = $patron_branchcode ? "AND borrowers.branchcode = '$patron_branchcode'" : q{};
244 my $query = "SELECT borrowers.borrowernumber, borrowers.cardnumber, borrowers.title as patron_title, borrowers.firstname, borrowers.surname,
245 borrowers.phone, borrowers.email, borrowers.branchcode, biblio.biblionumber, biblio.title, items.barcode, issues.date_due,
246 max(overduerules.branchcode) as rulebranch, TO_DAYS(NOW())-TO_DAYS(date_due) as daysoverdue, delay1, delay2, delay3,
247 issues.branchcode as site, branches.branchname as site_name
248 FROM borrowers JOIN issues USING (borrowernumber)
249 JOIN items USING (itemnumber)
250 JOIN biblio USING (biblionumber)
251 JOIN branches ON (issues.branchcode = branches.branchcode)
252 JOIN overduerules USING (categorycode)
253 JOIN overduerules_transport_types USING ( overduerules_id )
254 WHERE ( overduerules.branchcode = borrowers.branchcode or overduerules.branchcode = '')
255 AND overduerules_transport_types.message_transport_type = 'itiva'
256 AND ( (TO_DAYS(NOW())-TO_DAYS(date_due) ) = delay1
257 OR (TO_DAYS(NOW())-TO_DAYS(date_due) ) = delay2
258 OR (TO_DAYS(NOW())-TO_DAYS(date_due) ) = delay3 )
259 $patron_branchcode_filter
260 GROUP BY items.itemnumber
262 my $sth = $dbh->prepare($query);
265 while ( my $issue = $sth->fetchrow_hashref() ) {
266 if ( $issue->{'daysoverdue'} == $issue->{'delay1'} ) {
267 $issue->{'level'} = 1;
268 } elsif ( $issue->{'daysoverdue'} == $issue->{'delay2'} ) {
269 $issue->{'level'} = 2;
270 } elsif ( $issue->{'daysoverdue'} == $issue->{'delay3'} ) {
271 $issue->{'level'} = 3;
274 # this shouldn't ever happen, based our SQL criteria
276 push @results, $issue;
281 sub GetPredueIssues {
282 my ( $patron_branchcode ) = @_;
284 my $patron_branchcode_filter = $patron_branchcode ? "AND borrowers.branchcode = '$patron_branchcode'" : q{};
286 my $query = "SELECT borrowers.borrowernumber, borrowers.cardnumber, borrowers.title as patron_title, borrowers.firstname, borrowers.surname,
287 borrowers.phone, borrowers.email, borrowers.branchcode, biblio.biblionumber, biblio.title, items.barcode, issues.date_due,
288 issues.branchcode as site, branches.branchname as site_name
289 FROM borrowers JOIN issues USING (borrowernumber)
290 JOIN items USING (itemnumber)
291 JOIN biblio USING (biblionumber)
292 JOIN branches ON (issues.branchcode = branches.branchcode)
293 JOIN borrower_message_preferences USING (borrowernumber)
294 JOIN borrower_message_transport_preferences USING (borrower_message_preference_id)
295 JOIN message_attributes USING (message_attribute_id)
296 WHERE ( TO_DAYS( date_due ) - TO_DAYS( NOW() ) ) = days_in_advance
297 AND message_transport_type = 'itiva'
298 AND message_name = 'Advance_Notice'
299 $patron_branchcode_filter
301 my $sth = $dbh->prepare($query);
304 while ( my $issue = $sth->fetchrow_hashref() ) {
305 $issue->{'level'} = 1; # only one level for Predue notifications
306 push @results, $issue;
311 sub GetWaitingHolds {
312 my ( $patron_branchcode ) = @_;
314 my $patron_branchcode_filter = $patron_branchcode ? "AND borrowers.branchcode = '$patron_branchcode'" : q{};
316 my $query = "SELECT borrowers.borrowernumber, borrowers.cardnumber, borrowers.title as patron_title, borrowers.firstname, borrowers.surname, borrowers.categorycode,
317 borrowers.phone, borrowers.email, borrowers.branchcode, biblio.biblionumber, biblio.title, items.barcode, reserves.waitingdate,
318 reserves.branchcode AS site, branches.branchname AS site_name,
319 TO_DAYS(NOW())-TO_DAYS(reserves.waitingdate) AS days_since_waiting
320 FROM borrowers JOIN reserves USING (borrowernumber)
321 JOIN items USING (itemnumber)
322 JOIN biblio ON (biblio.biblionumber = items.biblionumber)
323 JOIN branches ON (reserves.branchcode = branches.branchcode)
324 JOIN borrower_message_preferences USING (borrowernumber)
325 JOIN borrower_message_transport_preferences USING (borrower_message_preference_id)
326 JOIN message_attributes USING (message_attribute_id)
327 WHERE ( reserves.found = 'W' )
328 AND message_transport_type = 'itiva'
329 AND message_name = 'Hold_Filled'
330 $patron_branchcode_filter
332 my $pickupdelay = C4::Context->preference("ReservesMaxPickUpDelay");
333 my $sth = $dbh->prepare($query);
336 while ( my $issue = $sth->fetchrow_hashref() ) {
337 my $item = Koha::Items->find({ barcode => $issue->{barcode} });
338 my $daysmode = Koha::CirculationRules->get_effective_daysmode(
340 categorycode => $issue->{categorycode},
341 itemtype => $item->effective_itemtype,
342 branchcode => $issue->{site},
346 my $calendar = Koha::Calendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode );
348 my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
349 my $pickup_date = $waiting_date->clone->add( days => $pickupdelay );
350 if ( $calendar->is_holiday($pickup_date) ) {
351 $pickup_date = $calendar->next_open_days( $pickup_date, 1 );
354 $issue->{'date_due'} = output_pref({dt => $pickup_date, dateformat => 'iso' });
355 $issue->{'level'} = 1; # only one level for Hold Waiting notifications
357 my $days_to_subtract = 0;
358 if ( $calendar->is_holiday($waiting_date) ) {
359 my $next_open_day = $calendar->next_open_days( $waiting_date, 1 );
360 $days_to_subtract = $calendar->days_between($waiting_date, $next_open_day)->days;
363 $issue->{'days_since_waiting'} = $issue->{'days_since_waiting'} - $days_to_subtract;
365 if ( ( grep $_ eq $issue->{'days_since_waiting'}, @holds_waiting_days_to_call )
366 || !scalar(@holds_waiting_days_to_call) ) {
367 push @results, $issue;