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

(-)a/C4/Letters.pm (-1 / +1 lines)
Lines 47-53 BEGIN { Link Here
47
    require Exporter;
47
    require Exporter;
48
    @ISA = qw(Exporter);
48
    @ISA = qw(Exporter);
49
    @EXPORT = qw(
49
    @EXPORT = qw(
50
        &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &SendAlerts &GetPrintMessages &GetMessageTransportTypes
50
        &EnqueueLetter &GetLetters &GetLettersAvailableForALibrary &GetLetterTemplates &DelLetter &GetPreparedLetter &GetWrappedLetter &SendAlerts &GetPrintMessages &GetMessageTransportTypes
51
    );
51
    );
52
}
52
}
53
53
(-)a/Koha/Patron.pm (+66 lines)
Lines 27-32 use Text::Unaccent qw( unac_string ); Link Here
27
27
28
use C4::Context;
28
use C4::Context;
29
use C4::Log;
29
use C4::Log;
30
use C4::Letters qw( GetPreparedLetter EnqueueLetter );
30
use Koha::AuthUtils;
31
use Koha::AuthUtils;
31
use Koha::Checkouts;
32
use Koha::Checkouts;
32
use Koha::Database;
33
use Koha::Database;
Lines 1417-1422 sub _anonymize_column { Link Here
1417
    $self->$col($val);
1418
    $self->$col($val);
1418
}
1419
}
1419
1420
1421
=head3 send_notice
1422
1423
    Koha::Patrons->send_notice({ letter_params => $letter_params, message_name => 'DUE'});
1424
    Koha::Patrons->send_notice({ letter_params => $letter_params, message_transports => \@message_transports });
1425
1426
    Queue messages to a patron. Can pass a message that is part of the message_attributes
1427
    table or supply the transport to use.
1428
1429
    If passed a message name we retrieve the patrons preferences for transports
1430
    Otherwise we use the supplied transport. In the case of email or sms we fall back to print if
1431
    we have no address/number for sending
1432
1433
    $letter_params is a hashref of the values to be passed to GetPreparedLetter
1434
1435
=cut
1436
1437
sub send_notice {
1438
    my ( $self, $params ) = @_;
1439
    my $letter_params = $params->{letter_params};
1440
1441
    return unless $letter_params;
1442
    return unless exists $params->{message_name} xor $params->{message_transports}; # We only want one of these
1443
1444
    my $library = Koha::Libraries->find( $letter_params->{branchcode} )->unblessed;
1445
    my $admin_email_address = $library->{branchemail} || C4::Context->preference('KohaAdminEmailAddress');
1446
1447
    my @message_transports;
1448
    my $letter_code;
1449
    $letter_code = $letter_params->{letter_code};
1450
    if( $params->{message_name} ){
1451
        my $messaging_prefs = C4::Members::Messaging::GetMessagingPreferences( {
1452
                borrowernumber => $letter_params->{borrowernumber},
1453
                message_name => $params->{message_name}
1454
        } );
1455
        @message_transports = ( keys %{ $messaging_prefs->{transports} } );
1456
        $letter_code = $messaging_prefs->{transports}->{$message_transports[0]} unless $letter_code;
1457
    } else {
1458
        @message_transports = @{$params->{message_transports}};
1459
    }
1460
    return unless defined $letter_code;
1461
    $letter_params->{letter_code} = $letter_code;
1462
    my $print_sent = 0;
1463
    my %return;
1464
    foreach my $mtt (@message_transports){
1465
        next if ($mtt eq 'phone' and C4::Context->preference('TalkingTechItivaPhoneNotification') );
1466
        # Phone notices are handled by TalkingTech_itiva_outbound.pl
1467
        if( ($mtt eq 'email' and not $self->notice_email_address) or ($mtt eq 'sms' and not $self->smsalertnumber) ){
1468
            push @{$return{fallback}}, $mtt;
1469
            $mtt = 'print';
1470
        }
1471
        next if $mtt eq 'print' && $print_sent;
1472
        $letter_params->{message_transport_type} = $mtt;
1473
        my $letter = C4::Letters::GetPreparedLetter( %$letter_params );
1474
        C4::Letters::EnqueueLetter({
1475
            letter => $letter,
1476
            borrowernumber => $self->borrowernumber,
1477
            from_address   => $admin_email_address,
1478
            message_transport_type => $mtt
1479
        });
1480
        push @{$return{sent}}, $mtt;
1481
        $print_sent = 1 if $mtt eq 'print';
1482
    }
1483
    return \%return;
1484
}
1485
1420
=head2 Internal methods
1486
=head2 Internal methods
1421
1487
1422
=head3 _type
1488
=head3 _type
(-)a/misc/cronjobs/holds_reminder.pl (+290 lines)
Line 0 Link Here
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
   -holidays                      use the calendar to not count holidays as waiting days
61
   -mtt          <message_transport_type> type of messages to send, default is to use patrons messaging preferences for Hold filled
62
                                  populating this will force send even if patron has not chosen to receive hold notices
63
                                  email and sms will fallback to print if borrower does not have an address/phone
64
   -date                          Send notices as would have been sent on a specific date
65
66
=head1 OPTIONS
67
68
=over 8
69
70
=item B<-help>
71
72
Print a brief help message and exits.
73
74
=item B<-man>
75
76
Prints the manual page and exits.
77
78
=item B<-v>
79
80
Verbose. Without this flag set, only fatal errors are reported.
81
82
=item B<-n>
83
84
Do not send any email. Reminder notices that would have been sent to
85
the patrons are printed to standard out.
86
87
=item B<-days>
88
89
Optional parameter, number of days an items has been 'waiting' on hold
90
to send a message for. If not included a notice will be sent to all
91
patrons with waiting holds.
92
93
=item B<-library>
94
95
select notices for one specific library. Use the value in the
96
branches.branchcode table. This option can be repeated in order
97
to select notices for a group of libraries.
98
99
=item B<-holidays>
100
101
This option determines whether library holidays are used when calculating how
102
long an item has been waiting. If enabled the count will skip closed days.
103
104
=item B<-date>
105
106
use it in order to send notices on a specific date and not Now. Format: YYYY-MM-DD.
107
108
=item B<-mtt>
109
110
send a notices via a specific transport, this can be repeated to send various notices.
111
If omitted the patron's messaging preferences for Hold notices will be used.
112
If supplied the notice types will be force sent even if patron has not selected hold notices
113
Email and SMS will fall back to print if there is no valid info in the patron's account
114
115
116
=back
117
118
=head1 DESCRIPTION
119
120
This script is designed to alert patrons of waiting
121
holds.
122
123
=head2 Configuration
124
125
This script sends reminders to patrons with waiting holds using a notice
126
defined in the Tools->Notices & slips module within Koha. The lettercode
127
is passed into this script and, along with other options, determine the content
128
of the notices sent to patrons.
129
130
131
=head1 USAGE EXAMPLES
132
133
C<holds_reminder.pl> - With no arguments the simple help is printed
134
135
C<holds_reminder.pl -lettercode CODE > In this most basic usage all
136
libraries are processed individually, and notices are prepared for
137
all patrons with waiting holds for whom we have email addresses.
138
Messages for those patrons for whom we have no email
139
address are sent in a single attachment to the library administrator's
140
email address, or to the address in the KohaAdminEmailAddress system
141
preference.
142
143
C<holds_reminder.pl -lettercode CODE -n -csv /tmp/holds_reminder.csv> - sends no email and
144
populates F</tmp/holds_reminder.csv> with information about all waiting holds
145
items.
146
147
C<holds_reminder.pl -lettercode CODE -library MAIN -days 14> - prepare notices of
148
holds waiting for 2 weeks for the MAIN library.
149
150
C<holds_reminder.pl -library MAIN -days 14 -list-all> - prepare notices
151
of holds waiting for 2 weeks for the MAIN library and include all the
152
patron's waiting hold
153
154
=cut
155
156
# These variables are set by command line options.
157
# They are initially set to default values.
158
my $dbh = C4::Context->dbh();
159
my $help    = 0;
160
my $man     = 0;
161
my $verbose = 0;
162
my $nomail  = 0;
163
my $days    ;
164
my $lettercode;
165
my @branchcodes; # Branch(es) passed as parameter
166
my $use_calendar = 0;
167
my ( $date_input, $today );
168
my $opt_out = 0;
169
my @mtts;
170
171
GetOptions(
172
    'help|?'         => \$help,
173
    'man'            => \$man,
174
    'v'              => \$verbose,
175
    'n'              => \$nomail,
176
    'days=s'         => \$days,
177
    'lettercode=s'   => \$lettercode,
178
    'library=s'      => \@branchcodes,
179
    'date=s'         => \$date_input,
180
    'holidays'       => \$use_calendar,
181
    'mtt=s'          => \@mtts
182
);
183
pod2usage(1) if $help;
184
pod2usage( -verbose => 2 ) if $man;
185
186
if ( !$lettercode ) {
187
    pod2usage({
188
        -exitval => 1,
189
        -msg => qq{\nError: You must specify a lettercode to send reminders.\n},
190
    });
191
}
192
193
194
cronlogaction();
195
196
# Unless a delay is specified by the user we target all waiting holds
197
unless (defined $days) {
198
    $days=0;
199
}
200
201
# Unless one ore more branchcodes are passed we use all the branches
202
if (scalar @branchcodes > 0) {
203
    my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch';
204
    $verbose and warn "$branchcodes_word @branchcodes passed on parameter\n";
205
}
206
else {
207
    @branchcodes = Koha::Libraries->search()->get_column('branchcode');
208
}
209
210
# If provided we run the report as if it had run on a specified date
211
my $date_to_run;
212
if ( $date_input ){
213
    eval {
214
        $date_to_run = dt_from_string( $date_input, 'iso' );
215
    };
216
    die "$date_input is not a valid date, aborting! Use a date in format YYYY-MM-DD."
217
        if $@ or not $date_to_run;
218
}
219
else {
220
    $date_to_run = dt_from_string();
221
}
222
223
# Loop through each branch
224
foreach my $branchcode (@branchcodes) { #BEGIN BRANCH LOOP
225
    # Check that this branch has the letter code specified or skip this branch
226
    my $letter = C4::Letters::getletter( 'reserves', $lettercode , $branchcode );
227
    unless ($letter) {
228
        $verbose and print qq|Message '$lettercode' content not found for $branchcode\n|;
229
        next;
230
    }
231
232
    # If respecting calendar get the correct waiting since date
233
    my $waiting_date;
234
    if( $use_calendar ){
235
        my $calendar = Koha::Calendar->new( branchcode => $branchcode );
236
        $waiting_date = $calendar->addDays($date_to_run,-$days); #Add negative of days
237
    } else {
238
        $waiting_date = $date_to_run->subtract( days => $days );
239
    }
240
241
    # Find all the holds waiting since this date for the current branch
242
    my $dtf = Koha::Database->new->schema->storage->datetime_parser;
243
    my $waiting_since = $dtf->format_date( $waiting_date );
244
    my $reserves = Koha::Holds->search({
245
        waitingdate => {'<=' => $waiting_since },
246
        branchcode  => $branchcode,
247
    });
248
249
    $verbose and warn "No reserves found for $branchcode\n" unless $reserves->count;
250
    next unless $reserves->count;
251
    $verbose and warn $reserves->count . " reserves waiting since $waiting_since for $branchcode\n";
252
253
    # We only want to send one notice per patron per branch - this variable will hold the completed borrowers
254
    my %done;
255
256
    # If passed message transports we force use those, otherwise we will use the patrons preferences
257
    # for the 'Hold_Filled' notice
258
    my $sending_params = @mtts ? { message_transports => \@mtts } : { message_name => "Hold_Filled" };
259
260
261
    while ( my $reserve = $reserves->next ) {
262
263
        my $patron = $reserve->borrower;
264
        # Skip if we already dealt with this borrower
265
        next if ( $done{$patron->borrowernumber} );
266
        $verbose and print "  borrower " . $patron->surname . ", " . $patron->firstname . " has holds triggering notice.\n";
267
268
        # Setup the notice information
269
        my $letter_params = {
270
            module          => 'reserves',
271
            letter_code     => $lettercode,
272
            borrowernumber  => $patron->borrowernumber,
273
            branchcode      => $branchcode,
274
            tables          => {
275
                 borrowers  => $patron->borrowernumber,
276
                 branches   => $reserve->branchcode,
277
                 reserves   => $reserve->unblessed
278
            },
279
        };
280
        $sending_params->{letter_params} = $letter_params;
281
        # send_notice queues the notices, falling back to print for email or SMS, and ignores phone (they are handled by Itiva)
282
        my $result = $patron->send_notice( $sending_params );
283
        $verbose and print "   borrower " . $patron->surname . ", " . $patron->firstname . " was sent notices via: @{$result->{sent}}\n" if defined $result->{sent};
284
        $verbose and print "   borrower " . $patron->surname . ", " . $patron->firstname . " fellback to print for: @{$result->{sent}}\n" if defined $result->{fallback};
285
        # Mark this borrower as completed
286
        $done{$patron->borrowernumber} = 1;
287
    }
288
289
290
} #END BRANCH LOOP
(-)a/t/db_dependent/Koha/Patrons.t (-2 / +88 lines)
Lines 19-25 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 40;
22
use Test::More tests => 41;
23
use Test::Warn;
23
use Test::Warn;
24
use Test::Exception;
24
use Test::Exception;
25
use Test::MockModule;
25
use Test::MockModule;
Lines 38-43 use Koha::Patron::Categories; Link Here
38
use Koha::Database;
38
use Koha::Database;
39
use Koha::DateUtils;
39
use Koha::DateUtils;
40
use Koha::Virtualshelves;
40
use Koha::Virtualshelves;
41
use Koha::Notice::Messages;
41
42
42
use t::lib::TestBuilder;
43
use t::lib::TestBuilder;
43
use t::lib::Mocks;
44
use t::lib::Mocks;
Lines 1865-1868 subtest 'anonymize' => sub { Link Here
1865
    $patron2->discard_changes; # refresh
1866
    $patron2->discard_changes; # refresh
1866
    is( $patron2->firstname, undef, 'First name patron2 cleared' );
1867
    is( $patron2->firstname, undef, 'First name patron2 cleared' );
1867
};
1868
};
1869
1870
subtest 'send_notice' => sub {
1871
    plan tests => 9;
1872
1873
    my $dbh = C4::Context->dbh;
1874
    t::lib::Mocks::mock_preference( 'AutoEmailPrimaryAddress', 'email' );
1875
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1876
    my $branch = $builder->build_object( { class => 'Koha::Libraries' } );
1877
    my $letter_e = $builder->build_object( {
1878
        class => 'Koha::Notice::Templates',
1879
        value => {
1880
            branchcode => $branch->branchcode,
1881
            message_transport_type => 'email',
1882
            lang => 'default'
1883
        }
1884
    });
1885
    my $letter_p = $builder->build_object( {
1886
        class => 'Koha::Notice::Templates',
1887
        value => {
1888
            code => $letter_e->code,
1889
            module => $letter_e->module,
1890
            branchcode => $branch->branchcode,
1891
            message_transport_type => 'print',
1892
            lang => 'default'
1893
        }
1894
    });
1895
    my $letter_s = $builder->build_object( {
1896
        class => 'Koha::Notice::Templates',
1897
        value => {
1898
            code => $letter_e->code,
1899
            module => $letter_e->module,
1900
            branchcode => $branch->branchcode,
1901
            message_transport_type => 'sms',
1902
            lang => 'default'
1903
        }
1904
    });
1905
1906
    my $letter_params = {
1907
        letter_code => $letter_e->code,
1908
        branchcode  => $letter_e->branchcode,
1909
        module      => $letter_e->module,
1910
        borrowernumber => $patron->borrowernumber,
1911
        tables => {
1912
            borrowers => $patron->borrowernumber,
1913
        }
1914
    };
1915
    my @mtts = ('email');
1916
1917
    is( $patron->send_notice(), undef, "Nothing is done if no params passed");
1918
    is( $patron->send_notice({ letter_params => $letter_params }),undef, "Nothing done if only letter");
1919
    is_deeply(
1920
        $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }),
1921
        {sent => ['email'] }, "Email sent"
1922
    );
1923
    $patron->email("")->store;
1924
    is_deeply(
1925
        $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }),
1926
        {sent => ['print'],fallback => ['email']}, "Email fallsback to print if no email"
1927
    );
1928
    push @mtts, 'sms';
1929
    is_deeply(
1930
        $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }),
1931
        {sent => ['print','sms'],fallback => ['email']}, "Email fallsback to print if no email, sms sent"
1932
    );
1933
    $patron->smsalertnumber("")->store;
1934
    my $counter = Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count;
1935
    is_deeply(
1936
        $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }),
1937
        {sent => ['print'],fallback => ['email','sms']}, "Email fallsback to print if no emai, sms fallsback to print if no sms, only one print sent"
1938
    );
1939
    is( Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count, $counter+1,"Count of queued notices went up by one");
1940
1941
    # Enable notification for Hold_Filled - Things are hardcoded here but should work with default data
1942
    $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ?)|, undef, $patron->borrowernumber, 4 );
1943
    my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
1944
    $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
1945
1946
    is( $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts, message_name => 'Hold_Filled' }),undef, "Nothing done if transports and name sent");
1947
1948
    $patron->email(q|awesome@ismymiddle.name|)->store;
1949
    is_deeply(
1950
        $patron->send_notice({ letter_params => $letter_params, message_name => 'Hold_Filled' }),
1951
        {sent => ['email'] }, "Email sent when using borrower preferences"
1952
    );
1953
};
1954
1868
$schema->storage->txn_rollback;
1955
$schema->storage->txn_rollback;
1869
- 

Return to bug 15986