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 (+70 lines)
Lines 29-34 use C4::Context; Link Here
29
use C4::Log;
29
use C4::Log;
30
use Koha::Account;
30
use Koha::Account;
31
use Koha::ArticleRequests;
31
use Koha::ArticleRequests;
32
use C4::Letters qw( GetPreparedLetter EnqueueLetter );
32
use Koha::AuthUtils;
33
use Koha::AuthUtils;
33
use Koha::Checkouts;
34
use Koha::Checkouts;
34
use Koha::Club::Enrollments;
35
use Koha::Club::Enrollments;
Lines 1694-1699 sub to_api_mapping { Link Here
1694
    };
1695
    };
1695
}
1696
}
1696
1697
1698
=head3 send_notice
1699
1700
    Koha::Patrons->send_notice({ letter_params => $letter_params, message_name => 'DUE'});
1701
    Koha::Patrons->send_notice({ letter_params => $letter_params, message_transports => \@message_transports });
1702
    Koha::Patrons->send_notice({ letter_params => $letter_params, message_transports => \@message_transports, test_mode => 1 });
1703
1704
    Queue messages to a patron. Can pass a message that is part of the message_attributes
1705
    table or supply the transport to use.
1706
1707
    If passed a message name we retrieve the patrons preferences for transports
1708
    Otherwise we use the supplied transport. In the case of email or sms we fall back to print if
1709
    we have no address/number for sending
1710
1711
    $letter_params is a hashref of the values to be passed to GetPreparedLetter
1712
1713
    test_mode will only report which notices would be sent, but nothign will be queued
1714
1715
=cut
1716
1717
sub send_notice {
1718
    my ( $self, $params ) = @_;
1719
    my $letter_params = $params->{letter_params};
1720
    my $test_mode = $params->{test_mode};
1721
1722
    return unless $letter_params;
1723
    return unless exists $params->{message_name} xor $params->{message_transports}; # We only want one of these
1724
1725
    my $library = Koha::Libraries->find( $letter_params->{branchcode} )->unblessed;
1726
    my $admin_email_address = $library->{branchemail} || C4::Context->preference('KohaAdminEmailAddress');
1727
1728
    my @message_transports;
1729
    my $letter_code;
1730
    $letter_code = $letter_params->{letter_code};
1731
    if( $params->{message_name} ){
1732
        my $messaging_prefs = C4::Members::Messaging::GetMessagingPreferences( {
1733
                borrowernumber => $letter_params->{borrowernumber},
1734
                message_name => $params->{message_name}
1735
        } );
1736
        @message_transports = ( keys %{ $messaging_prefs->{transports} } );
1737
        $letter_code = $messaging_prefs->{transports}->{$message_transports[0]} unless $letter_code;
1738
    } else {
1739
        @message_transports = @{$params->{message_transports}};
1740
    }
1741
    return unless defined $letter_code;
1742
    $letter_params->{letter_code} = $letter_code;
1743
    my $print_sent = 0;
1744
    my %return;
1745
    foreach my $mtt (@message_transports){
1746
        next if ($mtt eq 'phone' and C4::Context->preference('TalkingTechItivaPhoneNotification') );
1747
        # Phone notices are handled by TalkingTech_itiva_outbound.pl
1748
        if( ($mtt eq 'email' and not $self->notice_email_address) or ($mtt eq 'sms' and not $self->smsalertnumber) ){
1749
            push @{$return{fallback}}, $mtt;
1750
            $mtt = 'print';
1751
        }
1752
        next if $mtt eq 'print' && $print_sent;
1753
        $letter_params->{message_transport_type} = $mtt;
1754
        my $letter = C4::Letters::GetPreparedLetter( %$letter_params );
1755
        C4::Letters::EnqueueLetter({
1756
            letter => $letter,
1757
            borrowernumber => $self->borrowernumber,
1758
            from_address   => $admin_email_address,
1759
            message_transport_type => $mtt
1760
        }) unless $test_mode;
1761
        push @{$return{sent}}, $mtt;
1762
        $print_sent = 1 if $mtt eq 'print';
1763
    }
1764
    return \%return;
1765
}
1766
1697
=head2 Internal methods
1767
=head2 Internal methods
1698
1768
1699
=head3 _type
1769
=head3 _type
(-)a/misc/cronjobs/holds_reminder.pl (+294 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
use Koha::Script -cron;
41
42
=head1 NAME
43
44
holds_reminder.pl - prepare reminder messages to be sent to patrons with waiting holds
45
46
=head1 SYNOPSIS
47
48
holds_reminder.pl
49
  [ -n ][ -library <branchcode> ][ -library <branchcode> ... ]
50
  [ -days <number of days> ][ -csv [<filename>] ][ -itemscontent <field list> ]
51
  [ -email <email_type> ... ]
52
53
 Options:
54
   -help                          brief help message
55
   -man                           full documentation
56
   -v                             verbose
57
   -n                             No email will be sent
58
   -days          <days>          days waiting to deal with
59
   -lettercode   <lettercode>     predefined notice to use
60
   -library      <branchname>     only deal with holds from this library (repeatable : several libraries can be given)
61
   -holidays                      use the calendar to not count holidays as waiting days
62
   -mtt          <message_transport_type> type of messages to send, default is to use patrons messaging preferences for Hold filled
63
                                  populating this will force send even if patron has not chosen to receive hold notices
64
                                  email and sms will fallback to print if borrower does not have an address/phone
65
   -date                          Send notices as would have been sent on a specific date
66
67
=head1 OPTIONS
68
69
=over 8
70
71
=item B<-help>
72
73
Print a brief help message and exits.
74
75
=item B<-man>
76
77
Prints the manual page and exits.
78
79
=item B<-v>
80
81
Verbose. Without this flag set, only fatal errors are reported.
82
83
=item B<-n>
84
85
Do not send any email (test-mode) . If verbose a list of notices that would have been sent to
86
the patrons are printed to standard out.
87
88
=item B<-days>
89
90
Optional parameter, number of days an items has been 'waiting' on hold
91
to send a message for. If not included a notice will be sent to all
92
patrons with waiting holds.
93
94
=item B<-library>
95
96
select notices for one specific library. Use the value in the
97
branches.branchcode table. This option can be repeated in order
98
to select notices for a group of libraries.
99
100
=item B<-holidays>
101
102
This option determines whether library holidays are used when calculating how
103
long an item has been waiting. If enabled the count will skip closed days.
104
105
=item B<-date>
106
107
use it in order to send notices on a specific date and not Now. Format: YYYY-MM-DD.
108
109
=item B<-mtt>
110
111
send a notices via a specific transport, this can be repeated to send various notices.
112
If omitted the patron's messaging preferences for Hold notices will be used.
113
If supplied the notice types will be force sent even if patron has not selected hold notices
114
Email and SMS will fall back to print if there is no valid info in the patron's account
115
116
117
=back
118
119
=head1 DESCRIPTION
120
121
This script is designed to alert patrons of waiting
122
holds.
123
124
=head2 Configuration
125
126
This script sends reminders to patrons with waiting holds using a notice
127
defined in the Tools->Notices & slips module within Koha. The lettercode
128
is passed into this script and, along with other options, determine the content
129
of the notices sent to patrons.
130
131
132
=head1 USAGE EXAMPLES
133
134
C<holds_reminder.pl> - With no arguments the simple help is printed
135
136
C<holds_reminder.pl -lettercode CODE > In this most basic usage all
137
libraries are processed individually, and notices are prepared for
138
all patrons with waiting holds for whom we have email addresses.
139
Messages for those patrons for whom we have no email
140
address are sent in a single attachment to the library administrator's
141
email address, or to the address in the KohaAdminEmailAddress system
142
preference.
143
144
C<holds_reminder.pl -lettercode CODE -n -csv /tmp/holds_reminder.csv> - sends no email and
145
populates F</tmp/holds_reminder.csv> with information about all waiting holds
146
items.
147
148
C<holds_reminder.pl -lettercode CODE -library MAIN -days 14> - prepare notices of
149
holds waiting for 2 weeks for the MAIN library.
150
151
C<holds_reminder.pl -library MAIN -days 14 -list-all> - prepare notices
152
of holds waiting for 2 weeks for the MAIN library and include all the
153
patron's waiting hold
154
155
=cut
156
157
# These variables are set by command line options.
158
# They are initially set to default values.
159
my $dbh = C4::Context->dbh();
160
my $help    = 0;
161
my $man     = 0;
162
my $verbose = 0;
163
my $nomail  = 0;
164
my $days    ;
165
my $lettercode;
166
my @branchcodes; # Branch(es) passed as parameter
167
my $use_calendar = 0;
168
my $date_input;
169
my $opt_out = 0;
170
my @mtts;
171
172
GetOptions(
173
    'help|?'         => \$help,
174
    'man'            => \$man,
175
    'v'              => \$verbose,
176
    'n'              => \$nomail,
177
    'days=s'         => \$days,
178
    'lettercode=s'   => \$lettercode,
179
    'library=s'      => \@branchcodes,
180
    'date=s'         => \$date_input,
181
    'holidays'       => \$use_calendar,
182
    'mtt=s'          => \@mtts
183
);
184
pod2usage(1) if $help;
185
pod2usage( -verbose => 2 ) if $man;
186
187
if ( !$lettercode ) {
188
    pod2usage({
189
        -exitval => 1,
190
        -msg => qq{\nError: You must specify a lettercode to send reminders.\n},
191
    });
192
}
193
194
195
cronlogaction();
196
197
# Unless a delay is specified by the user we target all waiting holds
198
unless (defined $days) {
199
    $days=0;
200
}
201
202
# Unless one ore more branchcodes are passed we use all the branches
203
if (scalar @branchcodes > 0) {
204
    my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch';
205
    $verbose and warn "$branchcodes_word @branchcodes passed on parameter\n";
206
}
207
else {
208
    @branchcodes = Koha::Libraries->search()->get_column('branchcode');
209
}
210
211
# If provided we run the report as if it had run on a specified date
212
my $date_to_run;
213
if ( $date_input ){
214
    eval {
215
        $date_to_run = dt_from_string( $date_input, 'iso' );
216
    };
217
    die "$date_input is not a valid date, aborting! Use a date in format YYYY-MM-DD."
218
        if $@ or not $date_to_run;
219
}
220
else {
221
    $date_to_run = dt_from_string();
222
}
223
224
# Loop through each branch
225
foreach my $branchcode (@branchcodes) { #BEGIN BRANCH LOOP
226
    # Check that this branch has the letter code specified or skip this branch
227
    my $letter = C4::Letters::getletter( 'reserves', $lettercode , $branchcode );
228
    unless ($letter) {
229
        $verbose and print qq|Message '$lettercode' content not found for $branchcode\n|;
230
        next;
231
    }
232
233
    # If respecting calendar get the correct waiting since date
234
    my $waiting_date;
235
    if( $use_calendar ){
236
        my $calendar = Koha::Calendar->new( branchcode => $branchcode );
237
        my $duration = DateTime::Duration->new( days => -$days );
238
        $waiting_date = $calendar->addDays($date_to_run,$duration); #Add negative of days
239
    } else {
240
        $waiting_date = $date_to_run->subtract( days => $days );
241
    }
242
243
    # Find all the holds waiting since this date for the current branch
244
    my $dtf = Koha::Database->new->schema->storage->datetime_parser;
245
    my $waiting_since = $dtf->format_date( $waiting_date );
246
    my $reserves = Koha::Holds->search({
247
        waitingdate => {'<=' => $waiting_since },
248
        branchcode  => $branchcode,
249
    });
250
251
    $verbose and warn "No reserves found for $branchcode\n" unless $reserves->count;
252
    next unless $reserves->count;
253
    $verbose and warn $reserves->count . " reserves waiting since $waiting_since for $branchcode\n";
254
255
    # We only want to send one notice per patron per branch - this variable will hold the completed borrowers
256
    my %done;
257
258
    # If passed message transports we force use those, otherwise we will use the patrons preferences
259
    # for the 'Hold_Filled' notice
260
    my $sending_params = @mtts ? { message_transports => \@mtts } : { message_name => "Hold_Filled" };
261
262
263
    while ( my $reserve = $reserves->next ) {
264
265
        my $patron = $reserve->borrower;
266
        # Skip if we already dealt with this borrower
267
        next if ( $done{$patron->borrowernumber} );
268
        $verbose and print "  borrower " . $patron->surname . ", " . $patron->firstname . " has holds triggering notice.\n";
269
270
        # Setup the notice information
271
        my $letter_params = {
272
            module          => 'reserves',
273
            letter_code     => $lettercode,
274
            borrowernumber  => $patron->borrowernumber,
275
            branchcode      => $branchcode,
276
            tables          => {
277
                 borrowers  => $patron->borrowernumber,
278
                 branches   => $reserve->branchcode,
279
                 reserves   => $reserve->unblessed
280
            },
281
        };
282
        $sending_params->{letter_params} = $letter_params;
283
        $sending_params->{test_mode} = $nomail;
284
        my $result_text = $nomail ? "would have been sent" : "was sent";
285
        # send_notice queues the notices, falling back to print for email or SMS, and ignores phone (they are handled by Itiva)
286
        my $result = $patron->send_notice( $sending_params );
287
        $verbose and print "   borrower " . $patron->surname . ", " . $patron->firstname . " $result_text notices via: @{$result->{sent}}\n" if defined $result->{sent};
288
        $verbose and print "   borrower " . $patron->surname . ", " . $patron->firstname . " $result_text print fallback for: @{$result->{fallback}}\n" if defined $result->{fallback};
289
        # Mark this borrower as completed
290
        $done{$patron->borrowernumber} = 1;
291
    }
292
293
294
} #END BRANCH LOOP
(-)a/t/db_dependent/Koha/Patrons.t (-2 / +94 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 44-49 use Koha::Patron::Relationship; Link Here
44
use Koha::Database;
44
use Koha::Database;
45
use Koha::DateUtils;
45
use Koha::DateUtils;
46
use Koha::Virtualshelves;
46
use Koha::Virtualshelves;
47
use Koha::Notice::Messages;
47
48
48
use t::lib::TestBuilder;
49
use t::lib::TestBuilder;
49
use t::lib::Mocks;
50
use t::lib::Mocks;
Lines 2018-2023 subtest 'anonymize' => sub { Link Here
2018
    $patron2->discard_changes; # refresh
2019
    $patron2->discard_changes; # refresh
2019
    is( $patron2->firstname, undef, 'First name patron2 cleared' );
2020
    is( $patron2->firstname, undef, 'First name patron2 cleared' );
2020
};
2021
};
2022
2023
subtest 'send_notice' => sub {
2024
    plan tests => 11;
2025
2026
    my $dbh = C4::Context->dbh;
2027
    t::lib::Mocks::mock_preference( 'AutoEmailPrimaryAddress', 'email' );
2028
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
2029
    my $branch = $builder->build_object( { class => 'Koha::Libraries' } );
2030
    my $letter_e = $builder->build_object( {
2031
        class => 'Koha::Notice::Templates',
2032
        value => {
2033
            branchcode => $branch->branchcode,
2034
            message_transport_type => 'email',
2035
            lang => 'default'
2036
        }
2037
    });
2038
    my $letter_p = $builder->build_object( {
2039
        class => 'Koha::Notice::Templates',
2040
        value => {
2041
            code => $letter_e->code,
2042
            module => $letter_e->module,
2043
            branchcode => $branch->branchcode,
2044
            message_transport_type => 'print',
2045
            lang => 'default'
2046
        }
2047
    });
2048
    my $letter_s = $builder->build_object( {
2049
        class => 'Koha::Notice::Templates',
2050
        value => {
2051
            code => $letter_e->code,
2052
            module => $letter_e->module,
2053
            branchcode => $branch->branchcode,
2054
            message_transport_type => 'sms',
2055
            lang => 'default'
2056
        }
2057
    });
2058
2059
    my $letter_params = {
2060
        letter_code => $letter_e->code,
2061
        branchcode  => $letter_e->branchcode,
2062
        module      => $letter_e->module,
2063
        borrowernumber => $patron->borrowernumber,
2064
        tables => {
2065
            borrowers => $patron->borrowernumber,
2066
        }
2067
    };
2068
    my @mtts = ('email');
2069
2070
    is( $patron->send_notice(), undef, "Nothing is done if no params passed");
2071
    is( $patron->send_notice({ letter_params => $letter_params }),undef, "Nothing done if only letter");
2072
    is_deeply(
2073
        $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }),
2074
        {sent => ['email'] }, "Email sent"
2075
    );
2076
    $patron->email("")->store;
2077
    is_deeply(
2078
        $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }),
2079
        {sent => ['print'],fallback => ['email']}, "Email fallsback to print if no email"
2080
    );
2081
    push @mtts, 'sms';
2082
    is_deeply(
2083
        $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }),
2084
        {sent => ['print','sms'],fallback => ['email']}, "Email fallsback to print if no email, sms sent"
2085
    );
2086
    $patron->smsalertnumber("")->store;
2087
    my $counter = Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count;
2088
    is_deeply(
2089
        $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts }),
2090
        {sent => ['print'],fallback => ['email','sms']}, "Email fallsback to print if no emai, sms fallsback to print if no sms, only one print sent"
2091
    );
2092
    is( Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count, $counter+1,"Count of queued notices went up by one");
2093
2094
    # Enable notification for Hold_Filled - Things are hardcoded here but should work with default data
2095
    $dbh->do(q|INSERT INTO borrower_message_preferences( borrowernumber, message_attribute_id ) VALUES ( ?, ?)|, undef, $patron->borrowernumber, 4 );
2096
    my $borrower_message_preference_id = $dbh->last_insert_id(undef, undef, "borrower_message_preferences", undef);
2097
    $dbh->do(q|INSERT INTO borrower_message_transport_preferences( borrower_message_preference_id, message_transport_type) VALUES ( ?, ? )|, undef, $borrower_message_preference_id, 'email' );
2098
2099
    is( $patron->send_notice({ letter_params => $letter_params, message_transports => \@mtts, message_name => 'Hold_Filled' }),undef, "Nothing done if transports and name sent");
2100
2101
    $patron->email(q|awesome@ismymiddle.name|)->store;
2102
    is_deeply(
2103
        $patron->send_notice({ letter_params => $letter_params, message_name => 'Hold_Filled' }),
2104
        {sent => ['email'] }, "Email sent when using borrower preferences"
2105
    );
2106
    $counter = Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count;
2107
    is_deeply(
2108
        $patron->send_notice({ letter_params => $letter_params, message_name => 'Hold_Filled', test_mode => 1 }),
2109
        {sent => ['email'] }, "Report that email sent when using borrower preferences in test_mode"
2110
    );
2111
    is( Koha::Notice::Messages->search({borrowernumber => $patron->borrowernumber })->count, $counter,"Count of queued notices not increased in test mode");
2112
};
2113
2021
$schema->storage->txn_rollback;
2114
$schema->storage->txn_rollback;
2022
2115
2023
subtest 'extended_attributes' => sub {
2116
subtest 'extended_attributes' => sub {
2024
- 

Return to bug 15986