Bug 16149: Generate and send custom notices based on report output
authorNick Clemens <nick@bywatersolutions.com>
Mon, 17 Apr 2017 15:20:25 +0000 (11:20 -0400)
committerNick Clemens <nick@bywatersolutions.com>
Fri, 19 Apr 2019 14:37:32 +0000 (14:37 +0000)
Ths patch add an EmailReport function to C4::Reports::Guided

It accepts a notice (module, code, branch) and a report and attempts to
email notices to patron, generating content using report content.

Notice must be in template toolkit syntax, only columns in report are
available for notice.

To test:
1 - Specify various options
2 - Ensure errors are returned if options are incomplete or incorrect
3 - Pass a report containing 'from' and 'email' and 'borrowernumber'
columns and ensure message queue populated as expected

Signed-off-by: Jessica Ofsa <jofsa@vt.edu>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>

C4/Reports/Guided.pm
misc/cronjobs/patron_emailer.pl [new file with mode: 0755]

index c4d783f..eff3a7c 100644 (file)
@@ -27,9 +27,13 @@ use C4::Context;
 use C4::Templates qw/themelanguage/;
 use C4::Koha;
 use Koha::DateUtils;
+use Koha::Patrons;
+use Koha::Reports;
 use C4::Output;
 use C4::Debug;
 use C4::Log;
+use Koha::Notice::Templates;
+use C4::Letters;
 
 use Koha::AuthorisedValues;
 use Koha::Patron::Categories;
@@ -929,6 +933,99 @@ sub ValidateSQLParameters {
     return \@problematic_parameters;
 }
 
+=head2 EmailReport
+
+    my ( $emails, $arrayrefs ) = EmailReport($report_id, $letter_code, $module, $branch, $email)
+
+Take a report and use it to process a Template Toolkit formatted notice
+Returns arrayrefs containing prepared letters and errors respectively
+
+=cut
+
+sub EmailReport {
+
+    my $params     = shift;
+    my $report_id  = $params->{report_id};
+    my $from       = $params->{from};
+    my $email      = $params->{email};
+    my $module     = $params->{module};
+    my $code       = $params->{code};
+    my $branch     = $params->{branch} || "";
+
+    my @errors = ();
+    my @emails = ();
+
+    return ( undef, [{ FATAL => "MISSING_PARAMS" }] ) unless ($report_id && $module && $code);
+
+    return ( undef, [{ FATAL => "NO_LETTER" }] ) unless
+    my $letter = Koha::Notice::Templates->find({
+        module     => $module,
+        code       => $code,
+        branchcode => $branch,
+        message_transport_type => 'email',
+    });
+    $letter = $letter->unblessed;
+
+    my $report = Koha::Reports->find( $report_id );
+    my $sql = $report->savedsql;
+    return ( { FATAL => "NO_REPORT" } ) unless $sql;
+
+    my ( $sth, $errors ) = execute_query( $sql ); #don't pass offset or limit, hardcoded limit of 999,999 will be used
+    return ( undef, [{ FATAL => "REPORT_FAIL" }] ) if $errors;
+
+    my $counter = 1;
+    my $template = $letter->{content};
+
+    while ( my $row = $sth->fetchrow_hashref() ) {
+        my $email;
+        my $err_count = scalar @errors;
+        push ( @errors, { NO_BOR_COL => $counter } ) unless defined $row->{borrowernumber};
+        push ( @errors, { NO_EMAIL_COL => $counter } ) unless ( (defined $email && defined $row->{$email}) || defined $row->{email} );
+        push ( @errors, { NO_FROM_COL => $counter } ) unless defined ( $from || $row->{from} );
+        push ( @errors, { NO_BOR => $row->{borrowernumber} } ) unless Koha::Patrons->find({borrowernumber=>$row->{borrowernumber}});
+
+        my $from_address = $from || $row->{from};
+        my $to_address = $email ? $row->{$email} : $row->{email};
+        push ( @errors, { NOT_PARSE => $counter } ) unless my $content = _process_row_TT( $row, $template );
+        $counter++;
+        next if scalar @errors > $err_count; #If any problems, try next
+
+        $letter->{content}       = $content;
+        $email->{borrowernumber} = $row->{borrowernumber};
+        $email->{letter}         = $letter;
+        $email->{from_address}   = $from_address;
+        $email->{to_address}     = $to_address;
+
+        push ( @emails, $email );
+    }
+
+    return ( \@emails, \@errors );
+
+}
+
+
+
+=head2 ProcessRowTT
+
+   my $content = ProcessRowTT($row_hashref, $template);
+
+Accepts a hashref containing values and processes them against Template Toolkit
+to produce content
+
+=cut
+
+sub _process_row_TT {
+
+    my ($row, $template) = @_;
+
+    return 0 unless ($row && $template);
+    my $content;
+    my $processor = Template->new();
+    $processor->process( \$template, $row, \$content);
+    return $content;
+
+}
+
 sub _get_display_value {
     my ( $original_value, $column ) = @_;
     if ( $column eq 'periodicity' ) {
diff --git a/misc/cronjobs/patron_emailer.pl b/misc/cronjobs/patron_emailer.pl
new file mode 100755 (executable)
index 0000000..dbdf2a5
--- /dev/null
@@ -0,0 +1,175 @@
+#!/usr/bin/perl
+
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+BEGIN {
+    # find Koha's Perl modules
+    # test carefully before changing this
+    use FindBin;
+    eval { require "$FindBin::Bin/../kohalib.pl" };
+}
+
+use Getopt::Long;
+use Pod::Usage;
+
+use C4::Log;
+use C4::Reports::Guided;
+
+cronlogaction();
+
+=head1 NAME
+
+patron_emailer.pl
+
+=head1 SYNOPSIS
+
+patron_emailer.pl
+    [--report ][--notice][--module] --library  --from
+
+ Options:
+    --help                       brief help
+    --report                     report ID to use as data for email template
+    --notice                     specific notice code to use
+    --module                     which module to find the above notice in
+    --library                    specified branch for selecting notice, will use all libraries by default
+    --from                       specified email for 'from' address, report column 'from' used if not specified
+    --email                      specified column to use as 'to' email address, report column 'email' used if not specified
+    --verbose                    increased verbosity, will print notices and errors
+    --commit                     send emails, without this script will only report
+
+head1 OPTIONS
+
+=over 8
+
+=item B<-help>
+
+Print brief help and exit.
+
+=item B<-man>
+
+Print full documentation and exit.
+
+=item B<-report>
+
+Specify a saved SQL report id in the Koha system to user for the emails. All, and only,
+    columns in the report will be available for notice template variables
+
+=item B<-notice>
+
+Specific notice (CODE) to select
+
+=item B<-module>
+
+Which module to find the specified notice in
+
+=item B<-library>
+
+Option to specify which branches notice should be used, 'All libraries' is used if not specified
+
+=item B<-from>
+
+Specify the sender address of the email, if not specified a 'from' column in the report will be used.
+
+=item B<-email>
+
+Specify the column to find recipient address of the email, if not specified an 'email' column in the report will be used.
+
+=item B<-verbose>
+
+Increased verbosity, reports successes and errors.
+
+=item B<-commit>
+
+Send emails, if omitted script will report as verbose.
+
+=back
+
+=cut
+
+my $help     = 0;
+my $report_id;
+my $notice;
+my $module;  #this is only for selecting correct notice - report itself defines available columns, not module
+my $library; #as above, determines which notice to use, will use 'all libraries' if not specified
+my $email;   #to specify which column should be used as email in report will use 'email' from borrwers table
+my $from;    #to specify from address, will expect 'from' column in report if not specified
+my $verbose  = 0;
+my $commit   = 0;
+
+my $error_msgs = {
+    MISSING_PARAMS => "You must supply a report ID, letter module and code at minimum\n",
+    NO_LETTER      => "The specified letter was not found, please check your input\n",
+    NO_REPORT      => "The specified report was not found, please check your input\n",
+    REPORT_FAIL    => "There was an error running the report, please check your SQL\n",
+    NO_BOR_COL     => "There was no borrowernumber found for row ",
+    NO_EMAIL_COL   => "There was no email found for row ",
+    NO_FROM_COL    => "No from email was specified for row ",
+    NO_BOR         => "There is no borrower with borrowernumber "
+};
+
+GetOptions(
+    'help|?'      => \$help,
+    'report=i'    => \$report_id,
+    'notice=s'    => \$notice,
+    'module=s'    => \$module,
+    'library=s'   => \$library,
+    'email=s'     => \$email,
+    'from=s'      => \$from,
+    'verbose'     => \$verbose,
+    'commit'      => \$commit
+) or pod2usage(1);
+pod2usage(1) if $help;
+pod2usage(1) unless $report_id && $notice && $module;
+
+my ( $emails, $errors ) = C4::Reports::Guided::EmailReport({
+    email      => $email,
+    from       => $from,
+    report_id  => $report_id,
+    module     => $module,
+    code       => $notice,
+    branch     => $library,
+    verbose    => $verbose,
+    commit     => $commit,
+});
+
+foreach my $email (@$emails){
+    print "No emails will be sent!\n" unless $commit;
+    if( $verbose || !$commit ){
+        print "Email generated to $email->{to_address} from $email->{from_address}\n";
+        print "Content:\n";
+        print $email->{letter}->{content} ."\n";
+    }
+    C4::Letters::EnqueueLetter({
+        letter => $email->{letter},
+        borrowernumber         => $email->{borrowernumber},
+        message_transport_type => 'email',
+        from_address           => $email->{from_address},
+        to_address             => $email->{to_address},
+    }) if $commit;
+}
+
+if( $verbose || !$commit ){
+    foreach my $error ( @$errors ){
+        foreach ( keys %{$error} ){
+            print "$_\n";
+            if ( $_ eq 'FATAL' ) { print $error_msgs->{ ${$error}{$_} } }
+            else { print $error_msgs->{$_} . ${$error}{$_} . "\n" }
+        }
+    }
+}