Bug 31854: (QA follow-up) Terminology: loans > checkouts
[srvgit] / misc / cronjobs / advance_notices.pl
index 16800f0..efa0f49 100755 (executable)
 
 =head1 NAME
 
-advance_notices.pl - cron script to put item due reminders into message queue
+advance_notices.pl - prepare messages to be sent to patrons for nearly due, or due, items
 
 =head1 SYNOPSIS
 
-./advance_notices.pl -c
-
-or, in crontab:
-0 1 * * * advance_notices.pl -c
+       advance_notices.pl
+         [ -n ][ -m <number of days> ][ --itemscontent <comma separated field list> ][ -c ]
 
 =head1 DESCRIPTION
 
@@ -40,32 +38,17 @@ the OPAC.
 
 use strict;
 use warnings;
-use Getopt::Long;
-use Pod::Usage;
-use Data::Dumper;
-BEGIN {
-    # find Koha's Perl modules
-    # test carefully before changing this
-    use FindBin;
-    eval { require "$FindBin::Bin/../kohalib.pl" };
-}
-use C4::Biblio;
+use Getopt::Long qw( GetOptions );
+use Pod::Usage qw( pod2usage );
+use Koha::Script -cron;
 use C4::Context;
 use C4::Letters;
 use C4::Members;
 use C4::Members::Messaging;
-use C4::Overdues;
-use Koha::DateUtils;
-use C4::Log;
-
-=head1 NAME
-
-advance_notices.pl - prepare messages to be sent to patrons for nearly due, or due, items
-
-=head1 SYNOPSIS
-
-advance_notices.pl
-  [ -n ][ -m <number of days> ][ --itemscontent <comma separated field list> ][ -c ]
+use C4::Log qw( cronlogaction );
+use Koha::Items;
+use Koha::Libraries;
+use Koha::Patrons;
 
 =head1 OPTIONS
 
@@ -101,16 +84,39 @@ statement otherwise.
 
 comma separated list of fields that get substituted into templates in
 places of the E<lt>E<lt>items.contentE<gt>E<gt> placeholder. This
-defaults to due date,title,author,barcode
+defaults to date_due,title,author,barcode
 
 Other possible values come from fields in the biblios, items and
 issues tables.
 
-=back
+=item B<--digest-per-branch>
 
-=head1 DESCRIPTION
+Flag to indicate that generation of message digests should be
+performed separately for each branch.
+
+A patron could potentially have loans at several different branches
+There is no natural branch to set as the sender on the aggregated
+message in this situation so the default behavior is to use the
+borrowers home branch.  This could surprise to the borrower when
+message sender is a library where they have not borrowed anything.
+
+Enabling this flag ensures that the issuing library is the sender of
+the digested message.  It has no effect unless the borrower has
+chosen 'Digests only' on the advance messages.
+
+=item B<--library>
 
-This script is designed to alert patrons when items are due, or coming due
+select notices for one specific library. Use the value in the
+branches.branchcode table. This option can be repeated in order
+to select notices for a group of libraries.
+
+=item B<--frombranch>
+
+Set the from address for the notice to one of 'item-homebranch' or 'item-issuebranch'.
+
+Defaults to 'item-issuebranch'
+
+=back
 
 =head2 Configuration
 
@@ -144,7 +150,7 @@ are:
 =item E<lt>E<lt>items.contentE<gt>E<gt>
 
 one line for each item, each line containing a tab separated list of
-title, author, barcode, issuedate
+date due, title, author, barcode
 
 =item E<lt>E<lt>borrowers.*E<gt>E<gt>
 
@@ -160,35 +166,46 @@ any field from the branches table
 
 The F<misc/cronjobs/overdue_notices.pl> program allows you to send
 messages to patrons when their messages are overdue.
+
 =cut
 
+binmode( STDOUT, ':encoding(UTF-8)' );
+
 # These are defaults for command line options.
 my $confirm;                                                        # -c: Confirm that the user has read and configured this script.
 my $nomail;                                                         # -n: No mail. Will not send any emails.
 my $mindays     = 0;                                                # -m: Maximum number of days in advance to send notices
 my $maxdays     = 30;                                               # -e: the End of the time period
 my $verbose     = 0;                                                # -v: verbose
+my $digest_per_branch = 0;                                          # -digest-per-branch: Prepare and send digests per branch
+my @branchcodes; # Branch(es) passed as parameter
+my $frombranch   = 'item-issuebranch';
 my $itemscontent = join(',',qw( date_due title author barcode ));
 
 my $help    = 0;
 my $man     = 0;
 
+my $command_line_options = join(" ",@ARGV);
+
 GetOptions(
             'help|?'         => \$help,
             'man'            => \$man,
+            'library=s'      => \@branchcodes,
+            'frombranch=s'   => \$frombranch,
             'c'              => \$confirm,
             'n'              => \$nomail,
             'm:i'            => \$maxdays,
             'v'              => \$verbose,
+            'digest-per-branch' => \$digest_per_branch,
             'itemscontent=s' => \$itemscontent,
        )or pod2usage(2);
 pod2usage(1) if $help;
-pod2usage( -verbose => 2 ) if $man;;
+pod2usage( -verbose => 2 ) if $man;
 
 # Since advance notice options are not visible in the web-interface
 # unless EnhancedMessagingPreferences is on, let the user know that
 # this script probably isn't going to do much
-if ( ! C4::Context->preference('EnhancedMessagingPreferences') ) {
+if ( ! C4::Context->preference('EnhancedMessagingPreferences') && $verbose ) {
     warn <<'END_WARN';
 
 The "EnhancedMessagingPreferences" syspref is off.
@@ -200,20 +217,32 @@ END_WARN
 unless ($confirm) {
      pod2usage(1);
 }
+cronlogaction({ info => $command_line_options });
+
+my %branches = ();
+if (@branchcodes) {
+    %branches = map { $_ => 1 } @branchcodes;
+}
 
-cronlogaction();
+die "--frombranch takes item-homebranch or item-issuebranch only"
+  unless ( $frombranch eq 'item-issuebranch'
+    || $frombranch eq 'item-homebranch' );
+my $owning_library = ( $frombranch eq 'item-homebranch' ) ? 1 : 0;
 
 # The fields that will be substituted into <<items.content>>
 my @item_content_fields = split(/,/,$itemscontent);
 
 warn 'getting upcoming due issues' if $verbose;
-my $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( { days_in_advance => $maxdays } );
+my $upcoming_dues = C4::Circulation::GetUpcomingDueIssues( {
+    days_in_advance => $maxdays,
+    owning_library => $owning_library
+ } );
 warn 'found ' . scalar( @$upcoming_dues ) . ' issues' if $verbose;
 
 # hash of borrowernumber to number of items upcoming
 # for patrons wishing digests only.
-my $upcoming_digest;
-my $due_digest;
+my $upcoming_digest = {};
+my $due_digest = {};
 
 my $dbh = C4::Context->dbh();
 my $sth = $dbh->prepare(<<'END_SQL');
@@ -244,29 +273,43 @@ UPCOMINGITEM: foreach my $upcoming ( @$upcoming_dues ) {
         
         if ( $borrower_preferences->{'wants_digest'} ) {
             # cache this one to process after we've run through all of the items.
-            $due_digest->{ $upcoming->{borrowernumber} }->{email} = $from_address;
-            $due_digest->{ $upcoming->{borrowernumber} }->{count}++;
+            if ($digest_per_branch) {
+                $due_digest->{ $upcoming->{branchcode} }->{ $upcoming->{borrowernumber} }->{email} = $from_address;
+                $due_digest->{ $upcoming->{branchcode} }->{ $upcoming->{borrowernumber} }->{count}++;
+            } else {
+                $due_digest->{ $upcoming->{borrowernumber} }->{email} = $from_address;
+                $due_digest->{ $upcoming->{borrowernumber} }->{count}++;
+            }
         } else {
-            my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
+            my $branchcode;
+            if($owning_library) {
+                $branchcode = $upcoming->{'homebranch'};
+            } else {
+                $branchcode = $upcoming->{'branchcode'};
+            }
+            # Skip this DUE if we specify list of libraries and this one is not part of it
+            next if (@branchcodes && !$branches{$branchcode});
+
+            my $item = Koha::Items->find( $upcoming->{itemnumber} );
             my $letter_type = 'DUE';
             $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},'0');
             my $titles = "";
             while ( my $item_info = $sth->fetchrow_hashref()) {
-              my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
-              $titles .= join("\t",@item_info) . "\n";
+                $titles .= C4::Letters::get_item_content( { item => $item_info, item_content_fields => \@item_content_fields } );
             }
 
             ## Get branch info for borrowers home library.
             foreach my $transport ( keys %{$borrower_preferences->{'transports'}} ) {
+                next if $transport eq 'itiva';
                 my $letter = parse_letter( { letter_code    => $letter_type,
                                       borrowernumber => $upcoming->{'borrowernumber'},
-                                      branchcode     => $upcoming->{'branchcode'},
-                                      biblionumber   => $biblio->{'biblionumber'},
+                                      branchcode     => $branchcode,
+                                      biblionumber   => $item->biblionumber,
                                       itemnumber     => $upcoming->{'itemnumber'},
                                       substitute     => { 'items.content' => $titles },
                                       message_transport_type => $transport,
                                     } )
-                    or warn "no letter of type '$letter_type' found. Please see sample_notices.sql";
+                    or warn "no letter of type '$letter_type' found for borrowernumber ".$upcoming->{'borrowernumber'}.". Please see sample_notices.sql";
                 push @letters, $letter if $letter;
             }
         }
@@ -278,29 +321,43 @@ UPCOMINGITEM: foreach my $upcoming ( @$upcoming_dues ) {
 
         if ( $borrower_preferences->{'wants_digest'} ) {
             # cache this one to process after we've run through all of the items.
-            $upcoming_digest->{ $upcoming->{borrowernumber} }->{email} = $from_address;
-            $upcoming_digest->{ $upcoming->{borrowernumber} }->{count}++;
+            if ($digest_per_branch) {
+                $upcoming_digest->{ $upcoming->{branchcode} }->{ $upcoming->{borrowernumber} }->{email} = $from_address;
+                $upcoming_digest->{ $upcoming->{branchcode} }->{ $upcoming->{borrowernumber} }->{count}++;
+            } else {
+                $upcoming_digest->{ $upcoming->{borrowernumber} }->{email} = $from_address;
+                $upcoming_digest->{ $upcoming->{borrowernumber} }->{count}++;
+            }
         } else {
-            my $biblio = C4::Biblio::GetBiblioFromItemNumber( $upcoming->{'itemnumber'} );
+            my $branchcode;
+            if($owning_library) {
+            $branchcode = $upcoming->{'homebranch'};
+            } else {
+            $branchcode = $upcoming->{'branchcode'};
+            }
+            # Skip this PREDUE if we specify list of libraries and this one is not part of it
+            next if (@branchcodes && !$branches{$branchcode});
+
+            my $item = Koha::Items->find( $upcoming->{itemnumber} );
             my $letter_type = 'PREDUE';
             $sth->execute($upcoming->{'borrowernumber'},$upcoming->{'itemnumber'},$borrower_preferences->{'days_in_advance'});
             my $titles = "";
             while ( my $item_info = $sth->fetchrow_hashref()) {
-              my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
-              $titles .= join("\t",@item_info) . "\n";
+                $titles .= C4::Letters::get_item_content( { item => $item_info, item_content_fields => \@item_content_fields } );
             }
 
             ## Get branch info for borrowers home library.
             foreach my $transport ( keys %{$borrower_preferences->{'transports'}} ) {
+                next if $transport eq 'itiva';
                 my $letter = parse_letter( { letter_code    => $letter_type,
                                       borrowernumber => $upcoming->{'borrowernumber'},
-                                      branchcode     => $upcoming->{'branchcode'},
-                                      biblionumber   => $biblio->{'biblionumber'},
+                                      branchcode     => $branchcode,
+                                      biblionumber   => $item->biblionumber,
                                       itemnumber     => $upcoming->{'itemnumber'},
                                       substitute     => { 'items.content' => $titles },
                                       message_transport_type => $transport,
                                     } )
-                    or warn "no letter of type '$letter_type' found. Please see sample_notices.sql";
+                    or warn "no letter of type '$letter_type' found for borrowernumber ".$upcoming->{'borrowernumber'}.". Please see sample_notices.sql";
                 push @letters, $letter if $letter;
             }
         }
@@ -311,7 +368,7 @@ UPCOMINGITEM: foreach my $upcoming ( @$upcoming_dues ) {
       if ($nomail) {
         for my $letter ( @letters ) {
             local $, = "\f";
-            print $letter->{'content'};
+            print $letter->{'content'}."\n";
         }
       }
       else {
@@ -329,7 +386,7 @@ UPCOMINGITEM: foreach my $upcoming ( @$upcoming_dues ) {
 
 # Now, run through all the people that want digests and send them
 
-$sth = $dbh->prepare(<<'END_SQL');
+my $sth_digest = $dbh->prepare(<<'END_SQL');
 SELECT biblio.*, items.*, issues.*
   FROM issues,items,biblio
   WHERE items.itemnumber=issues.itemnumber
@@ -337,120 +394,71 @@ SELECT biblio.*, items.*, issues.*
     AND issues.borrowernumber = ?
     AND (TO_DAYS(date_due)-TO_DAYS(NOW()) = ?)
 END_SQL
-PATRON: while ( my ( $borrowernumber, $digest ) = each %$upcoming_digest ) {
-    @letters = ();
-    my $count = $digest->{count};
-    my $from_address = $digest->{email};
-
-    my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
-                                                                                  message_name   => 'advance_notice' } );
-    next PATRON unless $borrower_preferences; # how could this happen?
-
-
-    my $letter_type = 'PREDUEDGST';
-
-    $sth->execute($borrowernumber,$borrower_preferences->{'days_in_advance'});
-    my $titles = "";
-    while ( my $item_info = $sth->fetchrow_hashref()) {
-      my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
-      $titles .= join("\t",@item_info) . "\n";
-    }
 
-    ## Get branch info for borrowers home library.
-    my %branch_info = get_branch_info( $borrowernumber );
-
-    foreach my $transport ( keys %{ $borrower_preferences->{'transports'} } ) {
-        my $letter = parse_letter(
-            {
-                letter_code    => $letter_type,
-                borrowernumber => $borrowernumber,
-                substitute     => {
-                    count           => $count,
-                    'items.content' => $titles,
-                    %branch_info,
-                },
-                branchcode             => $branch_info{"branches.branchcode"},
-                message_transport_type => $transport,
+if ($digest_per_branch) {
+    while (my ($branchcode, $digests) = each %$upcoming_digest) {
+        send_digests({
+            sth => $sth_digest,
+            digests => $digests,
+            letter_code => 'PREDUEDGST',
+            message_name => 'advance_notice',
+            branchcode => $branchcode,
+            get_item_info => sub {
+                my $params = shift;
+                $params->{sth}->execute($params->{borrowernumber},
+                                        $params->{borrower_preferences}->{'days_in_advance'});
+                return sub {
+                    $params->{sth}->fetchrow_hashref;
+                };
             }
-          )
-          or warn "no letter of type '$letter_type' found. Please see sample_notices.sql";
-        push @letters, $letter if $letter;
-    }
-
-    if ( @letters ) {
-      if ($nomail) {
-        for my $letter ( @letters ) {
-            local $, = "\f";
-            print $letter->{'content'};
-        }
-      }
-      else {
-        for my $letter ( @letters ) {
-            C4::Letters::EnqueueLetter( { letter                 => $letter,
-                                          borrowernumber         => $borrowernumber,
-                                          from_address           => $from_address,
-                                          message_transport_type => $letter->{message_transport_type} } );
-        }
-      }
+        });
     }
-}
 
-# Now, run through all the people that want digests and send them
-PATRON: while ( my ( $borrowernumber, $digest ) = each %$due_digest ) {
-    @letters = ();
-    my $count = $digest->{count};
-    my $from_address = $digest->{email};
-
-    my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber,
-                                                                                  message_name   => 'item_due' } );
-    next PATRON unless $borrower_preferences; # how could this happen?
-
-    my $letter_type = 'DUEDGST';
-    $sth->execute($borrowernumber,'0');
-    my $titles = "";
-    while ( my $item_info = $sth->fetchrow_hashref()) {
-      my @item_info = map { $_ =~ /^date|date$/ ? format_date($item_info->{$_}) : $item_info->{$_} || '' } @item_content_fields;
-      $titles .= join("\t",@item_info) . "\n";
-    }
-
-    ## Get branch info for borrowers home library.
-    my %branch_info = get_branch_info( $borrowernumber );
-
-    for my $transport ( keys %{ $borrower_preferences->{'transports'} } ) {
-        my $letter = parse_letter(
-            {
-                letter_code    => $letter_type,
-                borrowernumber => $borrowernumber,
-                substitute     => {
-                    count           => $count,
-                    'items.content' => $titles,
-                    %branch_info,
-                },
-                branchcode             => $branch_info{"branches.branchcode"},
-                message_transport_type => $transport,
+    while (my ($branchcode, $digests) = each %$due_digest) {
+        send_digests({
+            sth => $sth_digest,
+            digests => $due_digest,
+            letter_code => 'DUEDGST',
+            branchcode => $branchcode,
+            message_name => 'item_due',
+            get_item_info => sub {
+                my $params = shift;
+                $params->{sth}->execute($params->{borrowernumber}, 0);
+                return sub {
+                    $params->{sth}->fetchrow_hashref;
+                };
             }
-          )
-          or warn "no letter of type '$letter_type' found. Please see sample_notices.sql";
-        push @letters, $letter if $letter;
+        });
     }
-
-    if ( @letters ) {
-      if ($nomail) {
-        for my $letter ( @letters ) {
-            local $, = "\f";
-            print $letter->{'content'};
+} else {
+    send_digests({
+        sth => $sth_digest,
+        digests => $upcoming_digest,
+        letter_code => 'PREDUEDGST',
+        message_name => 'advance_notice',
+        get_item_info => sub {
+            my $params = shift;
+            $params->{sth}->execute($params->{borrowernumber},
+                                    $params->{borrower_preferences}->{'days_in_advance'});
+            return sub {
+                $params->{sth}->fetchrow_hashref;
+            };
         }
-      }
-      else {
-        for my $letter ( @letters ) {
-            C4::Letters::EnqueueLetter( { letter                 => $letter,
-                                          borrowernumber         => $borrowernumber,
-                                          from_address           => $from_address,
-                                          message_transport_type => $letter->{message_transport_type} } );
+    });
+
+    send_digests({
+        sth => $sth_digest,
+        digests => $due_digest,
+        letter_code => 'DUEDGST',
+        message_name => 'item_due',
+        get_item_info => sub {
+            my $params = shift;
+            $params->{sth}->execute($params->{borrowernumber}, 0);
+            return sub {
+                $params->{sth}->fetchrow_hashref;
+            };
         }
-      }
-    }
-
+    });
 }
 
 =head1 METHODS
@@ -461,9 +469,11 @@ PATRON: while ( my ( $borrowernumber, $digest ) = each %$due_digest ) {
 
 sub parse_letter {
     my $params = shift;
+
     foreach my $required ( qw( letter_code borrowernumber ) ) {
         return unless exists $params->{$required};
     }
+    my $patron = Koha::Patrons->find( $params->{borrowernumber} );
 
     my %table_params = ( 'borrowers' => $params->{'borrowernumber'} );
 
@@ -483,18 +493,14 @@ sub parse_letter {
         module => 'circulation',
         letter_code => $params->{'letter_code'},
         branchcode => $table_params{'branches'},
+        lang => $patron->lang,
         substitute => $params->{'substitute'},
         tables     => \%table_params,
+        ( $params->{itemnumbers} ? ( loops => { items => $params->{itemnumbers} } ) : () ),
         message_transport_type => $params->{message_transport_type},
     );
 }
 
-sub format_date {
-    my $date_string = shift;
-    my $dt=dt_from_string($date_string);
-    return output_pref($dt);
-}
-
 =head2 get_branch_info
 
 =cut
@@ -503,9 +509,8 @@ sub get_branch_info {
     my ( $borrowernumber ) = @_;
 
     ## Get branch info for borrowers home library.
-    my $borrower_details = C4::Members::GetMember( borrowernumber => $borrowernumber );
-    my $borrower_branchcode = $borrower_details->{'branchcode'};
-    my $branch = C4::Branch::GetBranchDetail( $borrower_branchcode );
+    my $patron = Koha::Patrons->find( $borrowernumber );
+    my $branch = $patron->library->unblessed;
     my %branch_info;
     foreach my $key( keys %$branch ) {
         $branch_info{"branches.$key"} = $branch->{$key};
@@ -514,6 +519,129 @@ sub get_branch_info {
     return %branch_info;
 }
 
+=head2 send_digests
+
+    send_digests({
+        digests => ...,
+        sth => ...,
+        letter_code => ...,
+        get_item_info => ...,
+    })
+
+Enqueue digested letters (or print them if -n was passed at command line).
+
+Parameters:
+
+=over 4
+
+=item C<$digests>
+
+Reference to the array of digested messages.
+
+=item C<$sth>
+
+Prepared statement handle for fetching overdue issues.
+
+=item C<$letter_code>
+
+String that denote the letter code.
+
+=item C<$get_item_info>
+
+Subroutine for executing prepared statement.  Takes parameters $sth,
+$borrowernumber and $borrower_parameters and return a generator
+function that produce the matching rows.
+
+=back
+
+=cut
+
+sub send_digests {
+    my $params = shift;
+
+    PATRON: while ( my ( $borrowernumber, $digest ) = each %{$params->{digests}} ) {
+        @letters = ();
+        my $count = $digest->{count};
+        my $from_address = $digest->{email};
+
+        my %branch_info;
+        my $branchcode;
+
+        if (defined($params->{branchcode})) {
+            %branch_info = ();
+            $branchcode = $params->{branchcode};
+        } else {
+            ## Get branch info for borrowers home library.
+            %branch_info = get_branch_info( $borrowernumber );
+            $branchcode = $branch_info{'branches.branchcode'};
+        }
+
+        my $borrower_preferences =
+            C4::Members::Messaging::GetMessagingPreferences(
+                {
+                    borrowernumber => $borrowernumber,
+                    message_name   => $params->{message_name}
+                }
+            );
+
+        next PATRON unless $borrower_preferences; # how could this happen?
+
+        my $next_item_info = $params->{get_item_info}->({
+            sth => $params->{sth},
+            borrowernumber => $borrowernumber,
+            borrower_preferences => $borrower_preferences
+        });
+        my $titles = "";
+        my @itemnumbers;
+        while ( my $item_info = $next_item_info->()) {
+            push @itemnumbers, $item_info->{itemnumber};
+            $titles .= C4::Letters::get_item_content( { item => $item_info, item_content_fields => \@item_content_fields } );
+        }
+
+        foreach my $transport ( keys %{ $borrower_preferences->{'transports'} } ) {
+            next if $transport eq 'itiva';
+            my $letter = parse_letter(
+                {
+                    letter_code    => $params->{letter_code},
+                    borrowernumber => $borrowernumber,
+                    substitute     => {
+                        count           => $count,
+                        'items.content' => $titles,
+                        %branch_info
+                    },
+                    itemnumbers    => \@itemnumbers,
+                    branchcode     => $branchcode,
+                    message_transport_type => $transport
+                }
+            );
+            unless ( $letter ){
+                warn "no letter of type '$params->{letter_type}' found for borrowernumber $borrowernumber. Please see sample_notices.sql";
+                next;
+            }
+            push @letters, $letter if $letter;
+        }
+
+        if ( @letters ) {
+            if ($nomail) {
+                for my $letter ( @letters ) {
+                    local $, = "\f";
+                    print $letter->{'content'};
+                }
+            }
+            else {
+                for my $letter ( @letters ) {
+                    C4::Letters::EnqueueLetter( { letter                 => $letter,
+                                                  borrowernumber         => $borrowernumber,
+                                                  from_address           => $from_address,
+                                                  message_transport_type => $letter->{message_transport_type} } );
+                }
+            }
+        }
+    }
+}
+
+cronlogaction({ action => 'End', info => "COMPLETED" });
+
 1;
 
 __END__