Bug 24159: (QA follow-up) Make terminology consistent
[koha.git] / misc / cronjobs / thirdparty / TalkingTech_itiva_outbound.pl
blobdd4d2f9ff2a353f818aaaf52e1b2331002f5897c
1 #!/usr/bin/perl
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>.
20 use strict;
21 use warnings;
23 BEGIN {
25 # find Koha's Perl modules
26 # test carefully before changing this
27 use FindBin;
28 eval { require "$FindBin::Bin/../kohalib.pl" };
31 use Getopt::Long;
32 use Pod::Usage;
34 use Koha::Script -cron;
35 use C4::Context;
36 use C4::Items;
37 use C4::Letters;
38 use C4::Overdues;
39 use Koha::Calendar;
40 use Koha::DateUtils;
41 use Koha::Patrons;
42 use Koha::Libraries;
44 sub usage {
45 pod2usage( -verbose => 2 );
46 exit;
49 die "TalkingTechItivaPhoneNotification system preference not activated... dying\n"
50 unless ( C4::Context->preference("TalkingTechItivaPhoneNotification") );
52 # Database handle
53 my $dbh = C4::Context->dbh;
55 # Options
56 my $verbose;
57 my $language = "EN";
58 my @types;
59 my @holds_waiting_days_to_call;
60 my $library_code;
61 my $help;
62 my $outfile;
63 my $skip_patrons_with_email;
64 my $patron_branchcode;
66 # maps to convert I-tiva terms to Koha terms
67 my $type_module_map = {
68 'PREOVERDUE' => 'circulation',
69 'OVERDUE' => 'circulation',
70 'RESERVE' => 'reserves',
73 my $type_notice_map = {
74 'PREOVERDUE' => 'PREDUE',
75 'OVERDUE' => 'OVERDUE',
76 'RESERVE' => 'HOLD',
79 GetOptions(
80 'o|output:s' => \$outfile,
81 'v' => \$verbose,
82 'lang:s' => \$language,
83 'type:s' => \@types,
84 'w|waiting-hold-day:s' => \@holds_waiting_days_to_call,
85 'c|code|library-code:s' => \$library_code,
86 's|skip-patrons-with-email' => \$skip_patrons_with_email,
87 'pb|patron-branchcode:s' => \$patron_branchcode,
88 'h|help' => \$help,
91 $language = uc($language);
92 $library_code ||= '';
94 pod2usage( -verbose => 1 ) if $help;
96 if ($patron_branchcode) {
97 die("Invalid branchcode '$patron_branchcode' passed in -pb --patron-branchcode parameter")
98 unless Koha::Libraries->search( { branchcode => $patron_branchcode } )->count;
101 # output log or STDOUT
102 my $OUT;
103 if ( defined $outfile ) {
104 open( $OUT, '>', "$outfile" ) || die("Cannot open output file");
105 } else {
106 print "No output file defined; printing to STDOUT\n"
107 if ( defined $verbose );
108 $OUT = *STDOUT || die "Couldn't duplicate STDOUT: $!";
111 my $format = 'V'; # format for phone notifications
113 foreach my $type (@types) {
114 $type = uc($type); #just in case lower or mixed-case was supplied
115 my $module = $type_module_map->{$type}; #since the module is required to get the letter
116 my $code = $type_notice_map->{$type}; #to get the Koha name of the notice
118 my @loop;
119 if ( $type eq 'OVERDUE' ) {
120 @loop = GetOverdueIssues( $patron_branchcode );
121 } elsif ( $type eq 'PREOVERDUE' ) {
122 @loop = GetPredueIssues( $patron_branchcode );
123 } elsif ( $type eq 'RESERVE' ) {
124 @loop = GetWaitingHolds( $patron_branchcode );
125 } else {
126 print "Unknown or unsupported message type $type; skipping...\n"
127 if ( defined $verbose );
128 next;
131 my $patrons;
132 foreach my $issues (@loop) {
133 $patrons->{$issues->{borrowernumber}} ||= Koha::Patrons->find( $issues->{borrowernumber} ) if $skip_patrons_with_email;
134 next if $skip_patrons_with_email && $patrons->{$issues->{borrowernumber}}->notice_email_address;
136 my $date_dt = dt_from_string ( $issues->{'date_due'} );
137 my $due_date = output_pref( { dt => $date_dt, dateonly => 1, dateformat =>'metric' } );
139 my $letter = C4::Letters::GetPreparedLetter(
140 module => $module,
141 letter_code => $code,
142 lang => 'default', # It does not sound useful to send a lang here
143 tables => {
144 borrowers => $issues->{'borrowernumber'},
145 biblio => $issues->{'biblionumber'},
146 biblioitems => $issues->{'biblionumber'},
148 message_transport_type => 'phone',
151 die "No letter found for type $type!... dying\n" unless $letter;
153 my $message_id = 0;
154 if ($outfile) {
155 $message_id = C4::Letters::EnqueueLetter(
156 { letter => $letter,
157 borrowernumber => $issues->{'borrowernumber'},
158 message_transport_type => 'phone',
163 print $OUT "\"$format\",\"$language\",\"$type\",\"$issues->{level}\",\"$issues->{cardnumber}\",\"$issues->{patron_title}\",\"$issues->{firstname}\",";
164 print $OUT "\"$issues->{surname}\",\"$issues->{phone}\",\"$issues->{email}\",\"$library_code\",";
165 print $OUT "\"$issues->{site}\",\"$issues->{site_name}\",\"$issues->{barcode}\",\"$due_date\",\"$issues->{title}\",\"$message_id\"\n";
169 =head1 NAME
171 TalkingTech_itiva_outbound.pl
173 =head1 SYNOPSIS
175 TalkingTech_itiva_outbound.pl
176 TalkingTech_itiva_outbound.pl --type=OVERDUE -w 0 -w 2 -w 6 --output=/tmp/talkingtech/outbound.csv
177 TalkingTech_itiva_outbound.pl --type=RESERVE --type=PREOVERDUE --lang=FR
180 Script to generate Spec C outbound notifications file for Talking Tech i-tiva
181 phone notification system.
183 =over
185 =item B<--help> B<-h>
187 Prints this help
189 =item B<-v>
191 Provide verbose log information.
193 =item B<--output> B<-o>
195 Destination for outbound notifications file (CSV format). If no value is specified,
196 output is dumped to screen.
198 =item B<--lang>
200 Sets the language for all outbound messages. Currently supported values are EN, FR and ES.
201 If no value is specified, EN will be used by default.
203 =item B<--type>
205 REQUIRED. Sets which messaging types are to be used. Can be given multiple times, to
206 specify multiple types in a single output file. Currently supported values are RESERVE, PREOVERDUE
207 and OVERDUE. If no value is given, this script will not produce any outbound notifications.
209 =item B<--waiting-hold-day> B<-w>
211 OPTIONAL for --type=RESERVE. Sets the days after a hold has been set to waiting on which to call. Use
212 switch as many times as desired. For example, passing "-w 0 -w 2 -w 6" will cause calls to be placed
213 on the day the hold was set to waiting, 2 days after the waiting date, and 6 days after. See example above.
214 If this switch is not used with --type=RESERVE, calls will be placed every day until the waiting reserve
215 is picked up or canceled.
217 =item B<--library-code> B<--code> B<-c>
219 OPTIONAL
220 The code of the source library of the message.
221 The library code is used to group notices together for
222 consortium purposes and apply library specific settings, such as
223 prompts, to those notices.
224 This field can be blank if all messages are from a single library.
226 =item B<--patron-branchcode> B<--pb>
228 OPTIONAL
230 Limits the the patrons to generate notices for based on the patron's home library.
231 Items and holds from other libraries will still be included for the given patron.
233 =back
235 =cut
237 sub GetOverdueIssues {
238 my ( $patron_branchcode ) = @_;
240 my $patron_branchcode_filter = $patron_branchcode ? "AND borrowers.branchcode = '$patron_branchcode'" : q{};
242 my $query = "SELECT borrowers.borrowernumber, borrowers.cardnumber, borrowers.title as patron_title, borrowers.firstname, borrowers.surname,
243 borrowers.phone, borrowers.email, borrowers.branchcode, biblio.biblionumber, biblio.title, items.barcode, issues.date_due,
244 max(overduerules.branchcode) as rulebranch, TO_DAYS(NOW())-TO_DAYS(date_due) as daysoverdue, delay1, delay2, delay3,
245 issues.branchcode as site, branches.branchname as site_name
246 FROM borrowers JOIN issues USING (borrowernumber)
247 JOIN items USING (itemnumber)
248 JOIN biblio USING (biblionumber)
249 JOIN branches ON (issues.branchcode = branches.branchcode)
250 JOIN overduerules USING (categorycode)
251 JOIN overduerules_transport_types USING ( overduerules_id )
252 WHERE ( overduerules.branchcode = borrowers.branchcode or overduerules.branchcode = '')
253 AND overduerules_transport_types.message_transport_type = 'phone'
254 AND ( (TO_DAYS(NOW())-TO_DAYS(date_due) ) = delay1
255 OR (TO_DAYS(NOW())-TO_DAYS(date_due) ) = delay2
256 OR (TO_DAYS(NOW())-TO_DAYS(date_due) ) = delay3 )
257 $patron_branchcode_filter
258 GROUP BY items.itemnumber
260 my $sth = $dbh->prepare($query);
261 $sth->execute();
262 my @results;
263 while ( my $issue = $sth->fetchrow_hashref() ) {
264 if ( $issue->{'daysoverdue'} == $issue->{'delay1'} ) {
265 $issue->{'level'} = 1;
266 } elsif ( $issue->{'daysoverdue'} == $issue->{'delay2'} ) {
267 $issue->{'level'} = 2;
268 } elsif ( $issue->{'daysoverdue'} == $issue->{'delay3'} ) {
269 $issue->{'level'} = 3;
270 } else {
272 # this shouldn't ever happen, based our SQL criteria
274 push @results, $issue;
276 return @results;
279 sub GetPredueIssues {
280 my ( $patron_branchcode ) = @_;
282 my $patron_branchcode_filter = $patron_branchcode ? "AND borrowers.branchcode = '$patron_branchcode'" : q{};
284 my $query = "SELECT borrowers.borrowernumber, borrowers.cardnumber, borrowers.title as patron_title, borrowers.firstname, borrowers.surname,
285 borrowers.phone, borrowers.email, borrowers.branchcode, biblio.biblionumber, biblio.title, items.barcode, issues.date_due,
286 issues.branchcode as site, branches.branchname as site_name
287 FROM borrowers JOIN issues USING (borrowernumber)
288 JOIN items USING (itemnumber)
289 JOIN biblio USING (biblionumber)
290 JOIN branches ON (issues.branchcode = branches.branchcode)
291 JOIN borrower_message_preferences USING (borrowernumber)
292 JOIN borrower_message_transport_preferences USING (borrower_message_preference_id)
293 JOIN message_attributes USING (message_attribute_id)
294 WHERE ( TO_DAYS( date_due ) - TO_DAYS( NOW() ) ) = days_in_advance
295 AND message_transport_type = 'phone'
296 AND message_name = 'Advance_Notice'
297 $patron_branchcode_filter
299 my $sth = $dbh->prepare($query);
300 $sth->execute();
301 my @results;
302 while ( my $issue = $sth->fetchrow_hashref() ) {
303 $issue->{'level'} = 1; # only one level for Predue notifications
304 push @results, $issue;
306 return @results;
309 sub GetWaitingHolds {
310 my ( $patron_branchcode ) = @_;
312 my $patron_branchcode_filter = $patron_branchcode ? "AND borrowers.branchcode = '$patron_branchcode'" : q{};
314 my $query = "SELECT borrowers.borrowernumber, borrowers.cardnumber, borrowers.title as patron_title, borrowers.firstname, borrowers.surname, borrowers.categorycode,
315 borrowers.phone, borrowers.email, borrowers.branchcode, biblio.biblionumber, biblio.title, items.barcode, reserves.waitingdate,
316 reserves.branchcode AS site, branches.branchname AS site_name,
317 TO_DAYS(NOW())-TO_DAYS(reserves.waitingdate) AS days_since_waiting
318 FROM borrowers JOIN reserves USING (borrowernumber)
319 JOIN items USING (itemnumber)
320 JOIN biblio ON (biblio.biblionumber = items.biblionumber)
321 JOIN branches ON (reserves.branchcode = branches.branchcode)
322 JOIN borrower_message_preferences USING (borrowernumber)
323 JOIN borrower_message_transport_preferences USING (borrower_message_preference_id)
324 JOIN message_attributes USING (message_attribute_id)
325 WHERE ( reserves.found = 'W' )
326 AND message_transport_type = 'phone'
327 AND message_name = 'Hold_Filled'
328 $patron_branchcode_filter
330 my $pickupdelay = C4::Context->preference("ReservesMaxPickUpDelay");
331 my $sth = $dbh->prepare($query);
332 $sth->execute();
333 my @results;
334 while ( my $issue = $sth->fetchrow_hashref() ) {
335 my $item = Koha::Items->find({ barcode => $issue->{barcode} });
336 my $daysmode = Koha::CirculationRules->get_effective_daysmode(
338 categorycode => $issue->{categorycode},
339 itemtype => $item->effective_itemtype,
340 branchcode => $issue->{site},
344 my $calendar = Koha::Calendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode );
346 my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
347 my $pickup_date = $waiting_date->clone->add( days => $pickupdelay );
348 if ( $calendar->is_holiday($pickup_date) ) {
349 $pickup_date = $calendar->next_open_days( $pickup_date, 1 );
352 $issue->{'date_due'} = output_pref({dt => $pickup_date, dateformat => 'iso' });
353 $issue->{'level'} = 1; # only one level for Hold Waiting notifications
355 my $days_to_subtract = 0;
356 if ( $calendar->is_holiday($waiting_date) ) {
357 my $next_open_day = $calendar->next_open_days( $waiting_date, 1 );
358 $days_to_subtract = $calendar->days_between($waiting_date, $next_open_day)->days;
361 $issue->{'days_since_waiting'} = $issue->{'days_since_waiting'} - $days_to_subtract;
363 if ( ( grep $_ eq $issue->{'days_since_waiting'}, @holds_waiting_days_to_call )
364 || !scalar(@holds_waiting_days_to_call) ) {
365 push @results, $issue;
368 return @results;