View | Details | Raw Unified | Return to bug 15986
Collapse All | Expand All

(-)a/misc/cronjobs/holds_reminder.pl (-1 / +761 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
BEGIN {
21
22
    # find Koha's Perl modules
23
    # test carefully before changing this
24
    use FindBin;
25
    eval { require "$FindBin::Bin/../kohalib.pl" };
26
}
27
28
use Getopt::Long;
29
use Pod::Usage;
30
use Text::CSV_XS;
31
use DateTime;
32
use DateTime::Duration;
33
34
use C4::Context;
35
use C4::Letters;
36
use C4::Log;
37
use Koha::DateUtils;
38
use Koha::Calendar;
39
use Koha::Libraries;
40
41
=head1 NAME
42
43
holds_reminder.pl - prepare reminder messages to be sent to patrons with waiting holds
44
45
=head1 SYNOPSIS
46
47
holds_reminder.pl
48
  [ -n ][ -library <branchcode> ][ -library <branchcode> ... ]
49
  [ -days <number of days> ][ -csv [<filename>] ][ -itemscontent <field list> ]
50
  [ -email <email_type> ... ]
51
52
 Options:
53
   -help                          brief help message
54
   -man                           full documentation
55
   -v                             verbose
56
   -n                             No email will be sent
57
   -days          <days>          days waiting to deal with
58
   -lettercode   <lettercode>     predefined notice to use
59
   -library      <branchname>     only deal with holds from this library (repeatable : several libraries can be given)
60
   -csv          <filename>       populate CSV file
61
   -html         <directory>      Output html to a file in the given directory
62
   -text         <directory>      Output plain text to a file in the given directory
63
   -itemscontent <list of fields> item information in templates
64
   -holidays                      use the calendar to not count holidays as waiting days
65
   -mtt          <message_transport_type> type of messages to send, default is email only
66
   -email        <email_type>     type of email that will be used. Can be 'email', 'emailpro' or 'B_email'. Repeatable.
67
   -optout                        if included the script will skip borrowers without holds filed notices enabled for email
68
=head1 OPTIONS
69
70
=over 8
71
72
=item B<-help>
73
74
Print a brief help message and exits.
75
76
=item B<-man>
77
78
Prints the manual page and exits.
79
80
=item B<-v>
81
82
Verbose. Without this flag set, only fatal errors are reported.
83
84
=item B<-n>
85
86
Do not send any email. Reminder notices that would have been sent to
87
the patrons or to the admin are printed to standard out. CSV data (if
88
the -csv flag is set) is written to standard out or to any csv
89
filename given.
90
91
=item B<-days>
92
93
Optional parameter, number of days an items has been 'waiting' on hold
94
to send a message for. If not included a notice will be sent to all
95
patrons with waiting holds.
96
97
=item B<-list-all>
98
99
Default lists only those holds that have been waiting since B<-days>
100
parameter.
101
Choose list-all to include all s waiting holds.
102
103
=item B<-library>
104
105
select notices for one specific library. Use the value in the
106
branches.branchcode table. This option can be repeated in order
107
to select notices for a group of libraries.
108
109
=item B<-csv>
110
111
Produces CSV data. if -n (no mail) flag is set, then this CSV data is
112
sent to standard out or to a filename if provided. Otherwise, only
113
notices that could not be emailed are sent in CSV format to the admin.
114
115
=item B<-html>
116
117
Produces html data. If patron does not have an email address or
118
-n (no mail) flag is set, an HTML file is generated in the specified
119
directory. This can be downloaded or further processed by library staff.
120
The file will be called notices-YYYY-MM-DD.html and placed in the directory
121
specified.
122
123
=item B<-text>
124
125
Produces plain text data. If patron does not have an email address or
126
-n (no mail) flag is set, a text file is generated in the specified
127
directory. This can be downloaded or further processed by library staff.
128
The file will be called notices-YYYY-MM-DD.txt and placed in the directory
129
specified.
130
131
=item B<-itemscontent>
132
133
comma separated list of fields that get substituted into templates in
134
places of the E<lt>E<lt>items.contentE<gt>E<gt> placeholder. This
135
defaults to due date,title,barcode,author
136
137
Other possible values come from fields in the biblios, items and
138
issues tables.
139
140
=item B<-holidays>
141
142
This option determines whether library holidays are used when calculating how
143
long an item has been waiting. If enabled the count will skip closed days.
144
145
=item B<-date>
146
147
use it in order to send notices on a specific date and not Now. Format: YYYY-MM-DD.
148
149
=item B<-email>
150
151
Allows to specify which type of email will be used. Can be email, emailpro or B_email. Repeatable.
152
153
=back
154
155
=head1 DESCRIPTION
156
157
This script is designed to alert patrons and administrators of waiting
158
holds.
159
160
=head2 Configuration
161
162
This script sends reminders to patrons with waiting holds using a notice
163
defined in the Tools->Notices & slips module within Koha. The lettercode
164
is passed into this script and, along with other options, determine the content
165
of the notices sent to patrons.
166
167
=head2 Outgoing emails
168
169
Typically, messages are prepared for each patron with waiting holds.
170
Messages for whom there is no email address on file are collected and
171
sent as attachments in a single email to each library
172
administrator, or if that is not set, then to the email address in the
173
C<KohaAdminEmailAddress> system preference.
174
175
These emails are staged in the outgoing message queue, as are messages
176
produced by other features of Koha. This message queue must be
177
processed regularly by the
178
F<misc/cronjobs/process_message_queue.pl> program.
179
180
=head2 Templates
181
182
Templates can contain variables enclosed in double angle brackets like
183
E<lt>E<lt>thisE<gt>E<gt>. Those variables will be replaced with values
184
specific to the waiting holds or relevant patron. Available variables
185
are:
186
187
=over
188
189
=item E<lt>E<lt>bibE<gt>E<gt>
190
191
the name of the library
192
193
=item E<lt>E<lt>items.contentE<gt>E<gt>
194
195
one line for each item, each line containing a tab separated list of
196
title, author, barcode, waitingdate
197
198
=item E<lt>E<lt>borrowers.*E<gt>E<gt>
199
200
any field from the borrowers table
201
202
=item E<lt>E<lt>branches.*E<gt>E<gt>
203
204
any field from the branches table
205
206
=back
207
208
=head2 CSV output
209
210
The C<-csv> command line option lets you specify a file to which
211
hold reminder data should be output in CSV format.
212
213
With the C<-n> flag set, data about all holds is written to the
214
file. Without that flag, only information about holds that were
215
unable to be sent directly to the patrons will be written. In other
216
words, this CSV file replaces the data that is typically sent to the
217
administrator email address.
218
219
=head1 USAGE EXAMPLES
220
221
C<holds_reminder.pl> - With no arguments the simple help is printed
222
223
C<holds_reminder.pl -lettercode CODE > In this most basic usage all
224
libraries are processed individually, and notices are prepared for
225
all patrons with waiting holds for whom we have email addresses.
226
Messages for those patrons for whom we have no email
227
address are sent in a single attachment to the library administrator's
228
email address, or to the address in the KohaAdminEmailAddress system
229
preference.
230
231
C<holds_reminder.pl -lettercode CODE -n -csv /tmp/holds_reminder.csv> - sends no email and
232
populates F</tmp/holds_reminder.csv> with information about all waiting holds
233
items.
234
235
C<holds_reminder.pl -lettercode CODE -library MAIN -days 14> - prepare notices of
236
holds waiting for 2 weeks for the MAIN library.
237
238
C<holds_reminder.pl -library MAIN -days 14 -list-all> - prepare notices
239
of holds waiting for 2 weeks for the MAIN library and include all the
240
patron's waiting hold
241
242
=cut
243
244
# These variables are set by command line options.
245
# They are initially set to default values.
246
my $dbh = C4::Context->dbh();
247
my $help    = 0;
248
my $man     = 0;
249
my $verbose = 0;
250
my $nomail  = 0;
251
my $days    ;
252
my $lettercode;
253
my @branchcodes; # Branch(es) passed as parameter
254
my @emails_to_use;    # Emails to use for messaging
255
my @emails;           # Emails given in command-line parameters
256
my @message_transport_types;
257
my $csvfilename;
258
my $htmlfilename;
259
my $text_filename;
260
my $triggered = 0;
261
my $listall = 0;
262
my $itemscontent = join( ',', qw( waitingdate title barcode author itemnumber ) );
263
my $use_calendar = 0;
264
my ( $date_input, $today );
265
my $opt_out = 0;
266
267
GetOptions(
268
    'help|?'         => \$help,
269
    'man'            => \$man,
270
    'v'              => \$verbose,
271
    'n'              => \$nomail,
272
    'days=s'         => \$days,
273
    'lettercode=s'   => \$lettercode,
274
    'library=s'      => \@branchcodes,
275
    'csv:s'          => \$csvfilename,    # this optional argument gets '' if not supplied.
276
    'html:s'         => \$htmlfilename,    # this optional argument gets '' if not supplied.
277
    'text:s'         => \$text_filename,    # this optional argument gets '' if not supplied.
278
    'itemscontent=s' => \$itemscontent,
279
    'list-all'       => \$listall,
280
    't|triggered'    => \$triggered,
281
    'date=s'         => \$date_input,
282
    'email=s'        => \@emails,
283
    'mtt=s'          => \@message_transport_types,
284
    'holidays'       => \$use_calendar,
285
    'optout'         => \$opt_out
286
) or pod2usage(1);
287
pod2usage(1) if $help;
288
pod2usage( -verbose => 2 ) if $man;
289
290
if ( !$lettercode ) {
291
    pod2usage({
292
        -exitval => 1,
293
        -msg => qq{\nError: You must specify a lettercode to send reminders.\n},
294
    });
295
}
296
297
298
cronlogaction();
299
300
unless (defined $days) {
301
    $days=0;
302
    $listall=1;
303
}
304
305
if ( defined $csvfilename && $csvfilename =~ /^-/ ) {
306
    warn qq(using "$csvfilename" as filename, that seems odd);
307
}
308
309
my $PrintNoticesMaxLines = C4::Context->preference('PrintNoticesMaxLines');
310
311
if (scalar @branchcodes > 0) {
312
    my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch';
313
    $verbose and warn "$branchcodes_word @branchcodes passed on parameter\n";
314
}
315
else {
316
    my $branches = Koha::Libraries->search( {} , {columns => 'branchcode' } );
317
    while ( my $branch = $branches->next ) {
318
        push @branchcodes, $branch->branchcode;
319
    }
320
}
321
322
my $date_to_run;
323
my $date;
324
if ( $date_input ){
325
    eval {
326
        $date_to_run = dt_from_string( $date_input, 'iso' );
327
    };
328
    die "$date_input is not a valid date, aborting! Use a date in format YYYY-MM-DD."
329
        if $@ or not $date_to_run;
330
    $date = $dbh->quote( $date_input );
331
}
332
else {
333
    $date="NOW()";
334
    $date_to_run = dt_from_string();
335
}
336
337
338
# these are the fields that will be substituted into <<item.content>>
339
my @item_content_fields = split( /,/, $itemscontent );
340
341
binmode( STDOUT, ':encoding(UTF-8)' );
342
343
344
our $csv;       # the Text::CSV_XS object
345
our $csv_fh;    # the filehandle to the CSV file.
346
if ( defined $csvfilename ) {
347
    my $sep_char = C4::Context->preference('delimiter') || ';';
348
    $sep_char = "\t" if ($sep_char eq 'tabulation');
349
    $csv = Text::CSV_XS->new( { binary => 1 , sep_char => $sep_char } );
350
    if ( $csvfilename eq '' ) {
351
        $csv_fh = *STDOUT;
352
    } else {
353
        open $csv_fh, ">", $csvfilename or die "unable to open $csvfilename: $!";
354
    }
355
    if ( $csv->combine(qw(name surname address1 address2 zipcode city country email phone cardnumber itemcount itemsinfo branchname letternumber)) ) {
356
        print $csv_fh $csv->string, "\n";
357
    } else {
358
        $verbose and warn 'combine failed on argument: ' . $csv->error_input;
359
    }
360
}
361
362
our $fh;
363
if ( defined $htmlfilename ) {
364
  if ( $htmlfilename eq '' ) {
365
    $fh = *STDOUT;
366
  } else {
367
    my $today = DateTime->now(time_zone => C4::Context->tz );
368
    open $fh, ">:encoding(UTF-8)",File::Spec->catdir ($htmlfilename,"notices-".$today->ymd().".html");
369
  }
370
371
  print $fh "<html>\n";
372
  print $fh "<head>\n";
373
  print $fh "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
374
  print $fh "<style type='text/css'>\n";
375
  print $fh "pre {page-break-after: always;}\n";
376
  print $fh "pre {white-space: pre-wrap;}\n";
377
  print $fh "pre {white-space: -moz-pre-wrap;}\n";
378
  print $fh "pre {white-space: -o-pre-wrap;}\n";
379
  print $fh "pre {word-wrap: break-work;}\n";
380
  print $fh "</style>\n";
381
  print $fh "</head>\n";
382
  print $fh "<body>\n";
383
}
384
elsif ( defined $text_filename ) {
385
  if ( $text_filename eq '' ) {
386
    $fh = *STDOUT;
387
  } else {
388
    my $today = DateTime->now(time_zone => C4::Context->tz );
389
    open $fh, ">",File::Spec->catdir ($text_filename,"notices-".$today->ymd().".txt");
390
  }
391
}
392
393
foreach my $branchcode (@branchcodes) { #BEGIN BRANCH LOOP
394
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
395
        my $calendar = Koha::Calendar->new( branchcode => $branchcode );
396
        if ( $calendar->is_holiday($date_to_run) ) {
397
            next;
398
        }
399
    }
400
401
    my $letter = C4::Letters::getletter( 'reserves', $lettercode , $branchcode );
402
    unless ($letter) {
403
        $verbose and warn qq|Message '$lettercode' content not found|;
404
        # might as well skip this branch, no borrowers are going to work.
405
        next;
406
    }
407
408
    my $library             = Koha::Libraries->find($branchcode);
409
    my $admin_email_address = $library->branchemail
410
      || C4::Context->preference('KohaAdminEmailAddress');
411
    my @output_chunks;    # may be sent to mail or stdout or csv file.
412
413
    $verbose and warn sprintf "branchcode : '%s' using %s\n", $branchcode, $admin_email_address;
414
415
    my $sth2 = $dbh->prepare( <<"END_SQL" );
416
SELECT biblio.*, items.*, reserves.*, biblioitems.itemtype, branchname
417
  FROM reserves,items,biblio, biblioitems, branches b
418
  WHERE reserves.found = 'W'
419
    AND items.itemnumber=reserves.itemnumber
420
    AND biblio.biblionumber   = items.biblionumber
421
    AND b.branchcode = items.homebranch
422
    AND biblio.biblionumber   = biblioitems.biblionumber
423
    AND reserves.borrowernumber = ?
424
    AND TO_DAYS($date)-TO_DAYS(reserves.waitingdate) >= $days
425
END_SQL
426
427
    my $borrower_sql = <<"END_SQL";
428
SELECT reserves.borrowernumber, firstname, surname, address, address2, city, zipcode, country, email, emailpro, B_email, smsalertnumber, phone, cardnumber, waitingdate
429
FROM   reserves,borrowers,categories
430
WHERE  reserves.borrowernumber=borrowers.borrowernumber
431
AND    borrowers.categorycode=categories.categorycode
432
AND    TO_DAYS($date)-TO_DAYS(reserves.waitingdate) >= 0
433
AND    reserves.branchcode=?
434
END_SQL
435
436
437
    if ($opt_out) {
438
    $borrower_sql = <<"END_SQL";
439
SELECT reserves.borrowernumber, firstname, surname, address, address2, city, zipcode, country, email, emailpro, B_email, smsalertnumber, phone, cardnumber, waitingdate
440
FROM   reserves,borrowers,categories,borrower_message_preferences,borrower_message_transport_preferences
441
WHERE  reserves.borrowernumber=borrowers.borrowernumber
442
AND    borrower_message_preferences.borrowernumber = borrowers.borrowernumber
443
AND    borrower_message_preferences.message_attribute_id = 4
444
AND    borrower_message_preferences.borrower_message_preference_id = borrower_message_transport_preferences.borrower_message_preference_id
445
AND    borrower_message_transport_preferences.message_transport_type= "email"
446
AND    borrowers.categorycode=categories.categorycode
447
AND    TO_DAYS($date)-TO_DAYS(reserves.waitingdate) >= 0
448
AND    reserves.branchcode=?
449
END_SQL
450
    }
451
452
    my @borrower_parameters;
453
    push @borrower_parameters, $branchcode;
454
455
456
    # $sth gets borrower info iff at least one waiting reserve has triggered the action.
457
    my $sth = $dbh->prepare($borrower_sql);
458
    $sth->execute(@borrower_parameters);
459
    $verbose and warn $borrower_sql . "\n $branchcode " . "\n ($date, ".  $date_to_run->datetime() .")\nreturns " . $sth->rows . " rows";
460
    my $borrowernumber;
461
    while ( my $data = $sth->fetchrow_hashref ) { #BEGIN BORROWER LOOP
462
        # check the borrower has at least one item that matches
463
        my $days_waiting;
464
               if ( $use_calendar )
465
                {
466
                    my $calendar =
467
                      Koha::Calendar->new( branchcode => $branchcode );
468
                    $days_waiting =
469
                      $calendar->days_between( dt_from_string($data->{waitingdate}),
470
                        $date_to_run );
471
                }
472
                else {
473
                    $days_waiting =
474
                      $date_to_run->delta_days( dt_from_string($data->{waitingdate}) );
475
                }
476
                $days_waiting = $days_waiting->in_units('days');
477
                if ($days) { next unless ( $days_waiting == $days ); }
478
479
                if (defined $borrowernumber && $borrowernumber eq $data->{'borrowernumber'}){
480
                # we have already dealt with this borrower
481
                    $verbose and warn "already dealt with this borrower $borrowernumber";
482
                    next;
483
                }
484
                $borrowernumber = $data->{'borrowernumber'};
485
                my $borr =
486
                    $data->{'firstname'} . ', '
487
                  . $data->{'surname'} . ' ('
488
                  . $borrowernumber . ')';
489
                $verbose
490
                  and warn "borrower $borr has holds triggering notice.";
491
492
                @emails_to_use = ();
493
                my $notice_email =
494
                    C4::Members::GetNoticeEmailAddress($borrowernumber);
495
                unless ($nomail) {
496
                    if (@emails) {
497
                        foreach (@emails) {
498
                            push @emails_to_use, $data->{$_} if ( $data->{$_} );
499
                        }
500
                    }
501
                    else {
502
                        push @emails_to_use, $notice_email if ($notice_email);
503
                    }
504
                }
505
506
                my @params = ($borrowernumber);
507
                $verbose and warn "STH2 PARAMS: borrowernumber = $borrowernumber";
508
509
                $sth2->execute(@params);
510
                my $holdcount = 0;
511
                my $titles = "";
512
                my @holds = ();
513
514
                my $j = 0;
515
                my $exceededPrintNoticesMaxLines = 0;
516
                while ( my $hold_info = $sth2->fetchrow_hashref() ) { #BEGIN HOLDS LOOP
517
                    if ( $use_calendar ) {
518
                        my $calendar =
519
                          Koha::Calendar->new( branchcode => $branchcode );
520
                        $days_waiting =
521
                          $calendar->days_between(
522
                            dt_from_string( $hold_info->{waitingdate} ), $date_to_run );
523
                    }
524
                    else {
525
                        $days_waiting =
526
                          $date_to_run->delta_days(
527
                            dt_from_string( $hold_info->{waitingdate} ) );
528
                    }
529
                    $days_waiting = $days_waiting->in_units('days');
530
                    unless ($listall) { next unless ( $days_waiting == $days ); }
531
532
                    if ( ( scalar(@emails_to_use) == 0 || $nomail ) && $PrintNoticesMaxLines && $j >= $PrintNoticesMaxLines ) {
533
                      $exceededPrintNoticesMaxLines = 1;
534
                      last;
535
                    }
536
                    $j++;
537
                    my @hold_info = map { $_ =~ /^date|date$/ ?
538
                                           eval { output_pref( { dt => dt_from_string( $hold_info->{$_} ), dateonly => 1 } ); }
539
                                           :
540
                                           $hold_info->{$_} || '' } @item_content_fields;
541
                    $titles .= join("\t", @hold_info) . "\n";
542
                    $holdcount++;
543
                    push @holds, $hold_info;
544
                } #END HOLD LOOP
545
                $sth2->finish;
546
547
                @message_transport_types = ('email') unless ( scalar @message_transport_types > 0 );
548
549
550
                my $print_sent = 0; # A print notice is not yet sent for this patron
551
                for my $mtt ( @message_transport_types ) {
552
                    my $effective_mtt = $mtt;
553
                    if ( ($mtt eq 'email' and not scalar @emails_to_use) or ($mtt eq 'sms' and not $data->{smsalertnumber}) ) {
554
                        # email or sms is requested but not exist, do a print.
555
                        $effective_mtt = 'print';
556
                    }
557
558
                    my $letter_exists = C4::Letters::getletter( 'reserves', $lettercode, $branchcode, $effective_mtt ) ? 1 : 0;
559
                    my $letter = GetPreparedLetter(
560
                           module          => 'reserves',
561
                            letter_code     => $lettercode,
562
                            borrowernumber  => $borrowernumber,
563
                            branchcode      => $branchcode,
564
                            repeat           => { item => \@holds},
565
                            tables          => { 'borrowers' => $borrowernumber, 'branches' => $branchcode },
566
                            substitute      => {
567
                                               'items.content' => $titles,
568
                                               'count'         => $holdcount,
569
                                               },
570
                            # If there is no template defined for the requested letter
571
                            # Fallback on email
572
                            message_transport_type => $letter_exists ? $effective_mtt : 'email',
573
574
                    );
575
                    unless ($letter) {
576
                        $verbose and warn qq|Message '$lettercode' content not found|;
577
                        # this transport doesn't have a configured notice, so try another
578
                        next;
579
                    }
580
581
                    if ( $exceededPrintNoticesMaxLines ) {
582
                      $letter->{'content'} .= "List too long for form; please check your account online for a complete list of your waiting holds.";
583
                    }
584
585
                    my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); } split /\</, $letter->{'content'};
586
                    if (@misses) {
587
                        $verbose and warn "The following terms were not matched and replaced: \n\t" . join "\n\t", @misses;
588
                    }
589
590
                    if ($nomail) {
591
                        push @output_chunks,
592
                          prepare_letter_for_printing(
593
                          {   letter         => $letter,
594
                              borrowernumber => $borrowernumber,
595
                              firstname      => $data->{'firstname'},
596
                              lastname       => $data->{'surname'},
597
                              address1       => $data->{'address'},
598
                              address2       => $data->{'address2'},
599
                              city           => $data->{'city'},
600
                              phone          => $data->{'phone'},
601
                              cardnumber     => $data->{'cardnumber'},
602
                              branchname     => $library->branchname,
603
                              postcode       => $data->{'zipcode'},
604
                              country        => $data->{'country'},
605
                              email          => $notice_email,
606
                              itemcount      => $holdcount,
607
                              titles         => $titles,
608
                              outputformat   => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : defined $text_filename ? 'text' : '',
609
                            }
610
                          );
611
                    } else {
612
                        if ( ($mtt eq 'email' and not scalar @emails_to_use) or ($mtt eq 'sms' and not $data->{smsalertnumber}) ) {
613
                            push @output_chunks,
614
                              prepare_letter_for_printing(
615
                              {   letter         => $letter,
616
                                  borrowernumber => $borrowernumber,
617
                                  firstname      => $data->{'firstname'},
618
                                  lastname       => $data->{'surname'},
619
                                  address1       => $data->{'address'},
620
                                  address2       => $data->{'address2'},
621
                                  city           => $data->{'city'},
622
                                  postcode       => $data->{'zipcode'},
623
                                  country        => $data->{'country'},
624
                                  email          => $notice_email,
625
                                  itemcount      => $holdcount,
626
                                  titles         => $titles,
627
                                  outputformat   => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : defined $text_filename ? 'text' : '',
628
                                }
629
                              );
630
                        }
631
                        unless ( $effective_mtt eq 'print' and $print_sent == 1 ) {
632
                            # Just sent a print if not already done.
633
                            C4::Letters::EnqueueLetter(
634
                                {   letter                 => $letter,
635
                                    borrowernumber         => $borrowernumber,
636
                                    message_transport_type => $effective_mtt,
637
                                    from_address           => $admin_email_address,
638
                                    to_address             => join(',', @emails_to_use),
639
                                }
640
                            );
641
                            # A print notice should be sent only once
642
                            # Without this check, a print could be sent twice or more if the library checks sms and email and print and the patron has no email or sms number.
643
                            $print_sent = 1 if $effective_mtt eq 'print';
644
                        }
645
                    }
646
                }
647
            } #END BORROWER LOOP
648
            $sth->finish;
649
650
    if (@output_chunks) {
651
        if ( defined $csvfilename ) {
652
            print $csv_fh @output_chunks;
653
        }
654
        elsif ( defined $htmlfilename ) {
655
            print $fh @output_chunks;
656
        }
657
        elsif ( defined $text_filename ) {
658
            print $fh @output_chunks;
659
        }
660
        elsif ($nomail){
661
                local $, = "\f";    # pagebreak
662
                print @output_chunks;
663
        }
664
        # Generate the content of the csv with headers
665
        my $content;
666
        if ( defined $csvfilename ) {
667
            my $delimiter = C4::Context->preference('delimiter') || ';';
668
            $content = join($delimiter, qw(title name surname address1 address2 zipcode city country email itemcount itemsinfo due_date issue_date)) . "\n";
669
        }
670
        else {
671
            $content = "";
672
        }
673
        $content .= join( "\n", @output_chunks );
674
675
        my $attachment = {
676
            filename => defined $csvfilename ? 'attachment.csv' : 'attachment.txt',
677
            type => 'text/plain',
678
            content => $content,
679
        };
680
681
        my $letter = {
682
            title   => 'Overdue Notices',
683
            content => 'These messages were not sent directly to the patrons.',
684
        };
685
        C4::Letters::EnqueueLetter(
686
            {   letter                 => $letter,
687
                borrowernumber         => undef,
688
                message_transport_type => 'email',
689
                attachments            => [$attachment],
690
                to_address             => $admin_email_address,
691
            }
692
        );
693
    }
694
695
} #END BRANCH LOOP
696
if ($csvfilename) {
697
    # note that we're not testing on $csv_fh to prevent closing
698
    # STDOUT.
699
    close $csv_fh;
700
}
701
702
if ( defined $htmlfilename ) {
703
  print $fh "</body>\n";
704
  print $fh "</html>\n";
705
  close $fh;
706
} elsif ( defined $text_filename ) {
707
  close $fh;
708
}
709
710
=head1 INTERNAL METHODS
711
712
These methods are internal to the operation of holds_reminder.pl.
713
714
=head2 prepare_letter_for_printing
715
716
returns a string of text appropriate for printing in the event that an
717
holds reminder notice will not be sent to the patron's email
718
address. Depending on the desired output format, this may be a CSV
719
string, or a human-readable representation of the notice.
720
721
required parameters:
722
  letter
723
  borrowernumber
724
725
optional parameters:
726
  outputformat
727
728
=cut
729
730
sub prepare_letter_for_printing {
731
    my $params = shift;
732
733
    return unless ref $params eq 'HASH';
734
735
    foreach my $required_parameter (qw( letter borrowernumber )) {
736
        return unless defined $params->{$required_parameter};
737
    }
738
739
    my $return;
740
    chomp $params->{titles};
741
    if ( exists $params->{'outputformat'} && $params->{'outputformat'} eq 'csv' ) {
742
        if ($csv->combine(
743
                $params->{'firstname'}, $params->{'lastname'}, $params->{'address1'},  $params->{'address2'}, $params->{'postcode'},
744
                $params->{'city'}, $params->{'country'}, $params->{'email'}, $params->{'phone'}, $params->{'cardnumber'},
745
                $params->{'itemcount'}, $params->{'titles'}, $params->{'branchname'}
746
            )
747
          ) {
748
            return $csv->string, "\n";
749
        } else {
750
            $verbose and warn 'combine failed on argument: ' . $csv->error_input;
751
        }
752
    } elsif ( exists $params->{'outputformat'} && $params->{'outputformat'} eq 'html' ) {
753
      $return = "<pre>\n";
754
      $return .= "$params->{'letter'}->{'content'}\n";
755
      $return .= "\n</pre>\n";
756
    } else {
757
        $return .= "$params->{'letter'}->{'content'}\n";
758
759
    }
760
    return $return;
761
}

Return to bug 15986