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

(-)a/misc/cronjobs/overdue_notices.pl (-363 / +464 lines)
Lines 29-35 use DateTime::Duration; Link Here
29
use Koha::Script -cron;
29
use Koha::Script -cron;
30
use C4::Context;
30
use C4::Context;
31
use C4::Letters;
31
use C4::Letters;
32
use C4::Overdues             qw( GetOverdueMessageTransportTypes parse_overdues_letter );
32
use C4::Overdues             qw( parse_overdues_letter );
33
use C4::Log                  qw( cronlogaction );
33
use C4::Log                  qw( cronlogaction );
34
use Koha::Patron::Debarments qw( AddUniqueDebarment );
34
use Koha::Patron::Debarments qw( AddUniqueDebarment );
35
use Koha::DateUtils          qw( dt_from_string output_pref );
35
use Koha::DateUtils          qw( dt_from_string output_pref );
Lines 142-147 defaults to due date,title,barcode,author,itemnumber Link Here
142
Other possible values come from fields in the biblios, items and
142
Other possible values come from fields in the biblios, items and
143
issues tables.
143
issues tables.
144
144
145
=item B<--itemtypes>
146
147
Repeatable field, that permits to select only some item types.
148
149
=item B<--itemtypesout>
150
151
Repeatable field, that permits to exclude some item types.
152
145
=item B<--borcat>
153
=item B<--borcat>
146
154
147
Repeatable field that permits selecting only specific patron categories.
155
Repeatable field that permits selecting only specific patron categories.
Lines 156-162 patrons in the STAFF category. Link Here
156
164
157
=item B<-t> | B<--triggered>
165
=item B<-t> | B<--triggered>
158
166
159
This option causes a notice to be generated if and only if 
167
This option causes a notice to be generated if and only if
160
an item is overdue by the number of days defined in a notice trigger.
168
an item is overdue by the number of days defined in a notice trigger.
161
169
162
By default, a notice is sent each time the script runs, which is suitable for 
170
By default, a notice is sent each time the script runs, which is suitable for 
Lines 175-181 production data. Link Here
175
183
176
=item B<--list-all>
184
=item B<--list-all>
177
185
178
Default items.content lists only those items that fall in the 
186
Default items.content lists only those items that fall in the
179
range of the currently processing notice.
187
range of the currently processing notice.
180
Choose --list-all to include all overdue items in the list (limited by B<--max> setting).
188
Choose --list-all to include all overdue items in the list (limited by B<--max> setting).
181
189
Lines 321-334 alert them of items that have just become due. Link Here
321
329
322
# These variables are set by command line options.
330
# These variables are set by command line options.
323
# They are initially set to default values.
331
# They are initially set to default values.
324
my $dbh        = C4::Context->dbh();
332
my $dbh         = C4::Context->dbh();
325
my $help       = 0;
333
my $help        = 0;
326
my $man        = 0;
334
my $man         = 0;
327
my $verbose    = 0;
335
my $verbose     = 0;
328
my $nomail     = 0;
336
my $nomail      = 0;
329
my $MAX        = 90;
337
my $MAX         = 90;
330
my $test_mode  = 0;
338
my $test_mode   = 0;
331
my $frombranch = 'item-issuebranch';
339
my $frombranch  = 'item-issuebranch';
340
my $itype_level = C4::Context->preference('item-level_itypes') ? 'item' : 'biblioitem';
332
my @branchcodes;      # Branch(es) passed as parameter
341
my @branchcodes;      # Branch(es) passed as parameter
333
my @emails_to_use;    # Emails to use for messaging
342
my @emails_to_use;    # Emails to use for messaging
334
my @emails;           # Emails given in command-line parameters
343
my @emails;           # Emails given in command-line parameters
Lines 338-343 my $text_filename; Link Here
338
my $triggered    = 0;
347
my $triggered    = 0;
339
my $listall      = 0;
348
my $listall      = 0;
340
my $itemscontent = join( ',', qw( date_due title barcode author itemnumber ) );
349
my $itemscontent = join( ',', qw( date_due title barcode author itemnumber ) );
350
my @myitemtypes;
351
my @myitemtypesout;
341
my @myborcat;
352
my @myborcat;
342
my @myborcatout;
353
my @myborcatout;
343
my ( $date_input, $today );
354
my ( $date_input, $today );
Lines 356-361 GetOptions( Link Here
356
    'html:s'         => \$htmlfilename,     # this optional argument gets '' if not supplied.
367
    'html:s'         => \$htmlfilename,     # this optional argument gets '' if not supplied.
357
    'text:s'         => \$text_filename,    # this optional argument gets '' if not supplied.
368
    'text:s'         => \$text_filename,    # this optional argument gets '' if not supplied.
358
    'itemscontent=s' => \$itemscontent,
369
    'itemscontent=s' => \$itemscontent,
370
    'itemtypes=s'    => \@myitemtypes,
371
    'itemtypeouts=s' => \@myitemtypesout,
359
    'list-all'       => \$listall,
372
    'list-all'       => \$listall,
360
    't|triggered'    => \$triggered,
373
    't|triggered'    => \$triggered,
361
    'test'           => \$test_mode,
374
    'test'           => \$test_mode,
Lines 462-467 if ( defined $csvfilename ) { Link Here
462
}
475
}
463
476
464
@branches = @overduebranches unless @branches;
477
@branches = @overduebranches unless @branches;
478
479
# Setup output file if requested
465
our $fh;
480
our $fh;
466
if ( defined $htmlfilename ) {
481
if ( defined $htmlfilename ) {
467
    if ( $htmlfilename eq '' ) {
482
    if ( $htmlfilename eq '' ) {
Lines 481-488 if ( defined $htmlfilename ) { Link Here
481
    }
496
    }
482
}
497
}
483
498
499
# Setup category list
500
my @categories;
501
if (@myborcat) {
502
    @categories = @myborcat;
503
} elsif (@myborcatout) {
504
    @categories = Koha::Patron::Categories->search( { catagorycode => { 'not_in' => \@myborcatout } } )
505
        ->get_column('categorycode');
506
} else {
507
    @categories = Koha::Patron::Categories->search()->get_column('categorycode');
508
}
509
510
# Setup itemtype list
511
my @itemtypes;
512
if (@myitemtypes) {
513
    @itemtypes = @myitemtypes;
514
} elsif (@myitemtypesout) {
515
    @itemtypes =
516
        Koha::ItemTypes->search( { itemtype => { 'not_in' => \@myitemtypesout } } )->get_column('itemtype');
517
} else {
518
    @itemtypes = Koha::ItemTypes->search()->get_column('itemtype');
519
}
520
484
my %already_queued;
521
my %already_queued;
485
my %seen = map { $_ => 1 } @branches;
522
my %seen = map { $_ => 1 } @branches;
523
524
# Work through branches
525
my @output_chunks;
486
foreach my $branchcode (@branches) {
526
foreach my $branchcode (@branches) {
487
    my $calendar;
527
    my $calendar;
488
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
528
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
Lines 496-611 foreach my $branchcode (@branches) { Link Here
496
    my $admin_email_address  = $library->from_email_address;
536
    my $admin_email_address  = $library->from_email_address;
497
    my $branch_email_address = C4::Context->preference('AddressForFailedOverdueNotices')
537
    my $branch_email_address = C4::Context->preference('AddressForFailedOverdueNotices')
498
        || $library->inbound_email_address;
538
        || $library->inbound_email_address;
499
    my @output_chunks;    # may be sent to mail or stdout or csv file.
539
    @output_chunks = ();    # may be sent to mail or stdout or csv file.
500
540
501
    $verbose and print "======================================\n";
541
    $verbose and print "======================================\n";
502
    $verbose and warn sprintf "branchcode : '%s' using %s\n", $branchcode, $branch_email_address;
542
    $verbose and warn sprintf "branchcode : '%s' using %s\n", $branchcode, $branch_email_address;
503
543
504
    my $sql2 = <<"END_SQL";
544
    # Work through patron categories
505
SELECT biblio.*, items.*, issues.*, biblioitems.itemtype, branchname
545
    for my $category (@categories) {
506
  FROM issues,items,biblio, biblioitems, branches b
546
507
  WHERE items.itemnumber=issues.itemnumber
547
        # Fetch all overdues for patron categories that want overdues and where the item is not lost and group by borrower
508
    AND biblio.biblionumber   = items.biblionumber
548
        my $borrower_sql = <<"END_SQL";
509
    AND b.branchcode = items.homebranch
549
SELECT
510
    AND biblio.biblionumber   = biblioitems.biblionumber
550
    issues.borrowernumber,
511
    AND issues.borrowernumber = ?
551
    borrowers.firstname,
512
    AND items.itemlost = 0
552
    borrowers.surname,
553
    borrowers.address,
554
    borrowers.address2,
555
    borrowers.city,
556
    borrowers.zipcode,
557
    borrowers.country,
558
    borrowers.email,
559
    borrowers.emailpro,
560
    borrowers.B_email,
561
    borrowers.smsalertnumber,
562
    borrowers.phone,
563
    borrowers.cardnumber,
564
    biblio.*,
565
    biblioitems.itemtype AS b_itemtype,
566
    items.*,
567
    issues.*,
568
    branches.branchname
569
FROM
570
    issues
571
JOIN
572
    borrowers ON issues.borrowernumber = borrowers.borrowernumber
573
JOIN
574
    categories ON borrowers.categorycode = categories.categorycode
575
JOIN
576
    items ON issues.itemnumber = items.itemnumber
577
JOIN
578
    biblio ON biblio.biblionumber = items.biblionumber
579
JOIN
580
    biblioitems ON biblio.biblionumber = biblioitems.biblionumber
581
JOIN
582
    branches ON branches.branchcode = items.homebranch
583
WHERE
584
    items.itemlost = 0
513
    AND TO_DAYS($date)-TO_DAYS(issues.date_due) >= 0
585
    AND TO_DAYS($date)-TO_DAYS(issues.date_due) >= 0
514
END_SQL
586
END_SQL
515
587
516
    if ($owning_library) {
588
        my @borrower_parameters;
517
        $sql2 .= ' AND items.homebranch = ? ' unless ($patron_homelibrary);
589
        if ($branchcode) {
518
    } else {
590
            if ($owning_library) {
519
        $sql2 .= ' AND issues.branchcode = ? ' unless ($patron_homelibrary);
591
                $borrower_sql .= ' AND items.homebranch=? ';
520
    }
592
            } else {
521
    my $sth2 = $dbh->prepare($sql2);
593
                $borrower_sql .= ' AND issues.branchcode=? ';
522
594
            }
523
    my $query = "SELECT * FROM overduerules WHERE delay1 IS NOT NULL AND branchcode = ? ";
595
            push @borrower_parameters, $branchcode;
524
    $query .= " AND categorycode IN (" . join( ',', ('?') x @myborcat ) . ") "        if (@myborcat);
596
        }
525
    $query .= " AND categorycode NOT IN (" . join( ',', ('?') x @myborcatout ) . ") " if (@myborcatout);
597
        if ($category) {
526
598
            $borrower_sql .= ' AND borrowers.categorycode=? ';
527
    my $rqoverduerules = $dbh->prepare($query);
599
            push @borrower_parameters, $category;
528
    $rqoverduerules->execute( $branchcode, @myborcat, @myborcatout );
600
        }
529
601
        if (@itemtypes) {
530
    # We get default rules if there is no rule for this branch
602
            my $placeholders = join( ", ", ("?") x @itemtypes );
531
    if ( $rqoverduerules->rows == 0 ) {
603
            if ( $itype_level eq 'item' ) {
532
        $query = "SELECT * FROM overduerules WHERE delay1 IS NOT NULL AND branchcode = '' ";
604
                $borrower_sql .= " AND items.itype IN ($placeholders) ";
533
        $query .= " AND categorycode IN (" . join( ',', ('?') x @myborcat ) . ") "        if (@myborcat);
605
            } else {
534
        $query .= " AND categorycode NOT IN (" . join( ',', ('?') x @myborcatout ) . ") " if (@myborcatout);
606
                $borrower_sql .= " AND biblioitems.itemtype IN ($placeholders) ";
607
            }
608
            push @borrower_parameters, @itemtypes;
609
        }
610
        $borrower_sql .= '  AND categories.overduenoticerequired=1 ORDER BY issues.borrowernumber';
611
612
        # $sth gets borrower info if at least one overdue item has triggered the overdue action.
613
        my $sth = $dbh->prepare($borrower_sql);
614
        $sth->execute(@borrower_parameters);
615
616
        if ( $verbose > 1 ) {
617
            warn sprintf "--------Borrower SQL------\n";
618
            warn $borrower_sql
619
                . "\n $branchcode | "
620
                . "'$category' | "
621
                . join( "','", @itemtypes ) . " | "
622
                . $date_to_run->datetime() . ")\n";
623
            warn sprintf "--------------------------\n";
624
        }
625
        $verbose and warn sprintf "Found %s overdues for $category on $date_to_run\n", $sth->rows;
535
626
536
        $rqoverduerules = $dbh->prepare($query);
627
        my $borrowernumber;
537
        $rqoverduerules->execute( @myborcat, @myborcatout );
628
        my $borrower_overdues = {};
538
    }
539
629
540
    # my $outfile = 'overdues_' . ( $mybranch || $branchcode || 'default' );
630
        # Iterate over all overdues
541
    while ( my $overdue_rules = $rqoverduerules->fetchrow_hashref ) {
631
    OVERDUE: while ( my $data = $sth->fetchrow_hashref ) {
542
    PERIOD: foreach my $i ( 1 .. 3 ) {
632
            my $itemtype = $data->{itype} // $data->{b_itemtype};
543
633
544
            $verbose and warn "branch '$branchcode', categorycode = $overdue_rules->{categorycode} pass $i\n";
634
            # Collect triggers or act on them if we're switching borrower
635
            if ( !defined $borrower_overdues->{borrowernumber} ) {
636
                if ($verbose) {
637
                    warn "\n-----------------------------------------\n";
638
                    warn "Collecting overdue triggers for borrower " . $data->{borrowernumber} . "\n";
639
                }
640
                $borrower_overdues = { borrowernumber => $data->{borrowernumber}, branchcode => $branchcode };
641
            } elsif ( $borrower_overdues->{borrowernumber} ne $data->{borrowernumber} ) {
642
                $verbose and warn "Collected overdue triggers for " . $borrower_overdues->{borrowernumber} . "\n";
643
                _enact_trigger($borrower_overdues);
644
                if ($verbose) {
645
                    warn "\n-----------------------------------------\n";
646
                    warn "Collecting overdue triggers for borrower " . $data->{borrowernumber} . "\n";
647
                }
648
                $borrower_overdues = { borrowernumber => $data->{borrowernumber}, branchcode => $branchcode };
649
            }
545
650
546
            my $mindays = $overdue_rules->{"delay$i"};    # the notice will be sent after mindays days (grace period)
651
            $verbose
547
            my $maxdays = (
652
                and warn "\nProcessing overdue "
548
                  $overdue_rules->{ "delay" . ( $i + 1 ) }
653
                . $data->{issue_id}
549
                ? $overdue_rules->{ "delay" . ( $i + 1 ) } - 1
654
                . " with branch = '$branchcode', categorycode = '$category' and itemtype = '$itemtype'\n";
550
                : ($MAX)
655
551
            );    # issues being more than maxdays late are managed somewhere else. (borrower probably suspended)
656
            # Work through triggers until we run out of rules or find a match
657
            my $i = 0;
658
        PERIOD: while (1) {
659
                $i++;
660
                my $ii            = $i + 1;
661
                my $overdue_rules = Koha::CirculationRules->get_effective_rules(
662
                    {
663
                        rules => [
664
                            "overdue_$i" . '_delay',    "overdue_$i" . '_notice', "overdue_$i" . '_mtt',
665
                            "overdue_$i" . '_restrict', "overdue_$ii" . '_delay'
666
                        ],
667
                        categorycode => $category,
668
                        branchcode   => $branchcode,
669
                        itemtype     => $itemtype,
670
                    }
671
                );
552
672
553
            next unless defined $mindays;
673
                if ( !defined( $overdue_rules->{ "overdue_$i" . '_delay' } ) ) {
674
                    last PERIOD;
675
                }
554
676
555
            if ( !$overdue_rules->{"letter$i"} ) {
677
                my $mindays =
556
                $verbose and warn sprintf "No letter code found for pass %s\n", $i;
678
                    $overdue_rules->{ "overdue_$i" . '_delay' }
557
                next PERIOD;
679
                    ;    # the notice will be sent after mindays days (grace period)
558
            }
680
                my $maxdays = (
559
            $verbose and warn sprintf "Using letter code '%s' for pass %s\n", $overdue_rules->{"letter$i"}, $i;
681
                      $overdue_rules->{ "overdue_$ii" . '_delay' }
560
682
                    ? $overdue_rules->{ "overdue_$ii" . '_delay' } - 1
561
            # $letter->{'content'} is the text of the mail that is sent.
683
                    : ($MAX)
562
            # this text contains fields that are replaced by their value. Those fields must be written between brackets
684
                );       # issues being more than maxdays late are managed somewhere else. (borrower probably suspended)
563
            # The following fields are available :
685
564
            # itemcount is interpreted here as the number of items in the overdue range defined by the current notice or all overdues < max if(-list-all).
686
                if ( !$overdue_rules->{ "overdue_$i" . '_notice' } ) {
565
            # <date> <itemcount> <firstname> <lastname> <address1> <address2> <address3> <city> <postcode> <country>
687
                    $verbose and warn sprintf "Trigger %s skipped, No letter code found\n", $i;
566
688
                    next PERIOD;
567
            my $borrower_sql = <<"END_SQL";
568
SELECT issues.borrowernumber, firstname, surname, address, address2, city, zipcode, country, email, emailpro, B_email, smsalertnumber, phone, cardnumber, date_due
569
FROM   issues,borrowers,categories,items
570
WHERE  issues.borrowernumber=borrowers.borrowernumber
571
AND    borrowers.categorycode=categories.categorycode
572
AND    issues.itemnumber = items.itemnumber
573
AND    items.itemlost = 0
574
AND    TO_DAYS($date)-TO_DAYS(issues.date_due) >= 0
575
END_SQL
576
            my @borrower_parameters;
577
            if ($branchcode) {
578
                if ($owning_library) {
579
                    $borrower_sql .= ' AND items.homebranch=? ';
580
                } else {
581
                    $borrower_sql .= ' AND issues.branchcode=? ';
582
                }
689
                }
583
                push @borrower_parameters, $branchcode;
584
            }
585
            if ( $overdue_rules->{categorycode} ) {
586
                $borrower_sql .= ' AND borrowers.categorycode=? ';
587
                push @borrower_parameters, $overdue_rules->{categorycode};
588
            }
589
            $borrower_sql .= '  AND categories.overduenoticerequired=1 ORDER BY issues.borrowernumber';
590
591
            # $sth gets borrower info iff at least one overdue item has triggered the overdue action.
592
            my $sth = $dbh->prepare($borrower_sql);
593
            $sth->execute(@borrower_parameters);
594
595
            if ( $verbose > 1 ) {
596
                warn sprintf "--------Borrower SQL------\n";
597
                warn $borrower_sql
598
                    . "\n $branchcode | "
599
                    . $overdue_rules->{'categorycode'}
600
                    . "\n ($mindays, $maxdays, "
601
                    . $date_to_run->datetime() . ")\n";
602
                warn sprintf "--------------------------\n";
603
            }
604
            $verbose and warn sprintf "Found %s borrowers with overdues\n", $sth->rows;
605
            my $borrowernumber;
606
            while ( my $data = $sth->fetchrow_hashref ) {
607
690
608
                # check the borrower has at least one item that matches
691
                # $letter->{'content'} is the text of the mail that is sent.
692
                # this text contains fields that are replaced by their value. Those fields must be written between brackets
693
                # The following fields are available :
694
                # itemcount is interpreted here as the number of items in the overdue range defined by the current notice or all overdues < max if(-list-all).
695
                # <date> <itemcount> <firstname> <lastname> <address1> <address2> <address3> <city> <postcode> <country>
696
697
                # Check the overdue period matches
609
                my $days_between;
698
                my $days_between;
610
                if ( C4::Context->preference('OverdueNoticeCalendar') ) {
699
                if ( C4::Context->preference('OverdueNoticeCalendar') ) {
611
                    $days_between = $calendar->days_between(
700
                    $days_between = $calendar->days_between(
Lines 617-892 END_SQL Link Here
617
                        $date_to_run->delta_days( dt_from_string( $data->{date_due} ) );
706
                        $date_to_run->delta_days( dt_from_string( $data->{date_due} ) );
618
                }
707
                }
619
                $days_between = $days_between->in_units('days');
708
                $days_between = $days_between->in_units('days');
620
                if ($triggered) {
709
                if ($listall) {
621
                    if ( $mindays != $days_between ) {
710
                    unless ( $days_between >= 1 and $days_between <= $MAX ) {
622
                        next;
711
                        next;
623
                    }
712
                    }
624
                } else {
713
                } else {
625
                    unless ( $days_between >= $mindays
714
                    if ($triggered) {
626
                        && $days_between <= $maxdays )
715
                        if ( $mindays != $days_between ) {
627
                    {
716
                            $verbose and warn "Overdue skipped for trigger $i\n";
628
                        next;
717
                            next;
629
                    }
630
                }
631
                if ( defined $borrowernumber && $borrowernumber eq $data->{'borrowernumber'} ) {
632
633
                    # we have already dealt with this borrower
634
                    $verbose and warn "already dealt with this borrower $borrowernumber";
635
                    next;
636
                }
637
                $borrowernumber = $data->{'borrowernumber'};
638
                next if ( $patron_homelibrary && $already_queued{"$borrowernumber$i"} );
639
                my $borr = sprintf(
640
                    "%s%s%s (%s)",
641
                    $data->{'surname'} || '',
642
                    $data->{'firstname'} && $data->{'surname'} ? ', ' : '',
643
                    $data->{'firstname'} || '',
644
                    $borrowernumber
645
                );
646
                $verbose and warn "borrower $borr has items triggering level $i.\n";
647
648
                my $patron = Koha::Patrons->find($borrowernumber);
649
                if ($patron_homelibrary) {
650
                    $branchcode           = $patron->branchcode;
651
                    $library              = Koha::Libraries->find($branchcode);
652
                    $admin_email_address  = $library->from_email_address;
653
                    $branch_email_address = C4::Context->preference('AddressForFailedOverdueNotices')
654
                        || $library->inbound_email_address;
655
                }
656
                @emails_to_use = ();
657
                my $notice_email = $patron->notice_email_address;
658
                unless ($nomail) {
659
                    if (@emails) {
660
                        foreach (@emails) {
661
                            push @emails_to_use, $data->{$_} if ( $data->{$_} );
662
                        }
718
                        }
663
                    } else {
719
                    } else {
664
                        push @emails_to_use, $notice_email if ($notice_email);
720
                        unless ( $days_between >= $mindays
665
                    }
721
                            && $days_between <= $maxdays )
666
                }
667
668
                my $letter = Koha::Notice::Templates->find_effective_template(
669
                    {
670
                        module     => 'circulation',
671
                        code       => $overdue_rules->{"letter$i"},
672
                        branchcode => $branchcode,
673
                        lang       => $patron->lang
674
                    }
675
                );
676
677
                unless ($letter) {
678
                    $verbose and warn qq|Message '$overdue_rules->{"letter$i"}' content not found|;
679
680
                    # might as well skip while PERIOD, no other borrowers are going to work.
681
                    # FIXME : Does this mean a letter must be defined in order to trigger a debar ?
682
                    next PERIOD;
683
                }
684
685
                if ( $overdue_rules->{"debarred$i"} ) {
686
687
                    #action taken is debarring
688
                    AddUniqueDebarment(
689
                        {
722
                        {
690
                            borrowernumber => $borrowernumber,
723
                            $verbose and warn "Overdue skipped for trigger $i\n";
691
                            type           => 'OVERDUES',
692
                            comment        => "OVERDUES_PROCESS " . output_pref( dt_from_string() ),
693
                        }
694
                    ) unless $test_mode;
695
                    $verbose and warn "debarring $borr\n";
696
                }
697
                my @params = $patron_homelibrary ? ($borrowernumber) : ( $borrowernumber, $branchcode );
698
699
                $sth2->execute(@params);
700
                my $itemcount = 0;
701
                my $titles    = "";
702
                my @items     = ();
703
704
                my $j                            = 0;
705
                my $exceededPrintNoticesMaxLines = 0;
706
                while ( my $item_info = $sth2->fetchrow_hashref() ) {
707
                    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
708
                        $days_between =
709
                            $calendar->days_between( dt_from_string( $item_info->{date_due} ), $date_to_run );
710
                    } else {
711
                        $days_between = $date_to_run->delta_days( dt_from_string( $item_info->{date_due} ) );
712
                    }
713
                    $days_between = $days_between->in_units('days');
714
                    if ($listall) {
715
                        unless ( $days_between >= 1 and $days_between <= $MAX ) {
716
                            next;
724
                            next;
717
                        }
725
                        }
718
                    } else {
719
                        if ($triggered) {
720
                            if ( $mindays != $days_between ) {
721
                                next;
722
                            }
723
                        } else {
724
                            unless ( $days_between >= $mindays
725
                                && $days_between <= $maxdays )
726
                            {
727
                                next;
728
                            }
729
                        }
730
                    }
731
732
                    if (   ( scalar(@emails_to_use) == 0 || $nomail )
733
                        && $PrintNoticesMaxLines
734
                        && $j >= $PrintNoticesMaxLines )
735
                    {
736
                        $exceededPrintNoticesMaxLines = 1;
737
                        last;
738
                    }
726
                    }
739
                    next if $patron_homelibrary and !grep { $seen{ $item_info->{branchcode} } } @branches;
740
                    $j++;
741
742
                    $titles .= C4::Letters::get_item_content(
743
                        { item => $item_info, item_content_fields => \@item_content_fields, dateonly => 1 } );
744
                    $itemcount++;
745
                    push @items, $item_info;
746
                }
727
                }
747
                $sth2->finish;
748
728
749
                my @message_transport_types =
729
                if ($verbose) {
750
                    @{ GetOverdueMessageTransportTypes( $branchcode, $overdue_rules->{categorycode}, $i ) };
730
                    my $borr = sprintf(
751
                @message_transport_types =
731
                        "%s%s%s (%s)",
752
                    @{ GetOverdueMessageTransportTypes( q{}, $overdue_rules->{categorycode}, $i ) }
732
                        $data->{'surname'} || '',
753
                    unless @message_transport_types;
733
                        $data->{'firstname'} && $data->{'surname'} ? ', ' : '',
754
734
                        $data->{'firstname'} || '',
755
                my $print_sent = 0;    # A print notice is not yet sent for this patron
735
                        $data->{borrowernumber}
756
                for my $mtt (@message_transport_types) {
757
                    next if $mtt eq 'itiva';
758
                    my $effective_mtt = $mtt;
759
                    if (   ( $mtt eq 'email' and not scalar @emails_to_use )
760
                        or ( $mtt eq 'sms' and not $data->{smsalertnumber} ) )
761
                    {
762
                        # email or sms is requested but not exist, do a print.
763
                        $effective_mtt = 'print';
764
                    }
765
                    splice @items, $PrintNoticesMaxLines
766
                        if $effective_mtt eq 'print' && $PrintNoticesMaxLines && scalar @items > $PrintNoticesMaxLines;
767
768
                    #catch the case where we are sending a print to someone with an email
769
770
                    my $letter_exists = Koha::Notice::Templates->find_effective_template(
771
                        {
772
                            module                 => 'circulation',
773
                            code                   => $overdue_rules->{"letter$i"},
774
                            message_transport_type => $effective_mtt,
775
                            branchcode             => $branchcode,
776
                            lang                   => $patron->lang
777
                        }
778
                    );
736
                    );
737
                    warn sprintf "Overdue matched trigger %s with delay of %s days and overdue due date of %s\n",
738
                        $i,
739
                        $triggered, $overdue_rules->{ "overdue_$i" . '_delay' }, $data->{date_due};
740
                    warn sprintf "Using letter code '%s'\n",
741
                        $overdue_rules->{ "overdue_$i" . '_notice' };
742
                }
779
743
780
                    my $letter = parse_overdues_letter(
744
                my @message_transport_types = split( /,/, $overdue_rules->{ "overdue_$i" . '_mtt' } );
781
                        {
745
                for my $mtt (@message_transport_types) {
782
                            letter_code    => $overdue_rules->{"letter$i"},
746
                    push @{ $borrower_overdues->{triggers}->{$i}->{ $overdue_rules->{ "overdue_$i" . '_notice' } }
783
                            borrowernumber => $borrowernumber,
747
                            ->{$mtt} }, $data;
784
                            branchcode     => $branchcode,
785
                            items          => \@items,
786
                            substitute => {    # this appears to be a hack to overcome incomplete features in this code.
787
                                bib             => $library->branchname,    # maybe 'bib' is a typo for 'lib<rary>'?
788
                                'items.content' => $titles,
789
                                'count'         => $itemcount,
790
                            },
791
792
                            # If there is no template defined for the requested letter
793
                            # Fallback on the original type
794
                            message_transport_type => $letter_exists ? $effective_mtt : $mtt,
795
                        }
796
                    );
797
                    unless ( $letter && $letter->{content} ) {
798
                        $verbose and warn qq|Message '$overdue_rules->{"letter$i"}' content not found|;
799
800
                        # this transport doesn't have a configured notice, so try another
801
                        next;
802
                    }
803
804
                    if ($exceededPrintNoticesMaxLines) {
805
                        $letter->{'content'} .=
806
                            "List too long for form; please check your account online for a complete list of your overdue items.";
807
                    }
808
809
                    my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); } split /\</, $letter->{'content'};
810
                    if (@misses) {
811
                        $verbose
812
                            and warn "The following terms were not matched and replaced: \n\t" . join "\n\t", @misses;
813
                    }
814
815
                    if ($nomail) {
816
                        push @output_chunks,
817
                            prepare_letter_for_printing(
818
                            {
819
                                letter         => $letter,
820
                                borrowernumber => $borrowernumber,
821
                                firstname      => $data->{'firstname'},
822
                                lastname       => $data->{'surname'},
823
                                address1       => $data->{'address'},
824
                                address2       => $data->{'address2'},
825
                                city           => $data->{'city'},
826
                                phone          => $data->{'phone'},
827
                                cardnumber     => $data->{'cardnumber'},
828
                                branchname     => $library->branchname,
829
                                letternumber   => $i,
830
                                postcode       => $data->{'zipcode'},
831
                                country        => $data->{'country'},
832
                                email          => $notice_email,
833
                                itemcount      => $itemcount,
834
                                titles         => $titles,
835
                                outputformat   => defined $csvfilename ? 'csv'
836
                                : defined $htmlfilename  ? 'html'
837
                                : defined $text_filename ? 'text'
838
                                : '',
839
                            }
840
                            );
841
                    } else {
842
                        if (   ( $mtt eq 'email' and not scalar @emails_to_use )
843
                            or ( $mtt eq 'sms' and not $data->{smsalertnumber} ) )
844
                        {
845
                            push @output_chunks,
846
                                prepare_letter_for_printing(
847
                                {
848
                                    letter         => $letter,
849
                                    borrowernumber => $borrowernumber,
850
                                    firstname      => $data->{'firstname'},
851
                                    lastname       => $data->{'surname'},
852
                                    address1       => $data->{'address'},
853
                                    address2       => $data->{'address2'},
854
                                    city           => $data->{'city'},
855
                                    postcode       => $data->{'zipcode'},
856
                                    country        => $data->{'country'},
857
                                    email          => $notice_email,
858
                                    itemcount      => $itemcount,
859
                                    titles         => $titles,
860
                                    outputformat   => defined $csvfilename ? 'csv'
861
                                    : defined $htmlfilename  ? 'html'
862
                                    : defined $text_filename ? 'text'
863
                                    : '',
864
                                }
865
                                );
866
                        }
867
                        unless ( $effective_mtt eq 'print' and $print_sent == 1 ) {
868
869
                            # Just sent a print if not already done.
870
                            C4::Letters::EnqueueLetter(
871
                                {
872
                                    letter                 => $letter,
873
                                    borrowernumber         => $borrowernumber,
874
                                    message_transport_type => $effective_mtt,
875
                                    from_address           => $admin_email_address,
876
                                    to_address             => join( ',', @emails_to_use ),
877
                                    reply_address          => $library->inbound_email_address,
878
                                }
879
                            ) unless $test_mode;
880
881
                            # A print notice should be sent only once per overdue level.
882
                            # 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.
883
                            $print_sent = 1 if $effective_mtt eq 'print';
884
                        }
885
                    }
886
                }
748
                }
887
                $already_queued{"$borrowernumber$i"} = 1;
749
                if ( $overdue_rules->{ "overdue_$i" . '_restrict' } ) {
750
                    $borrower_overdues->{restrict} = 1;
751
                }
752
                $borrower_overdues->{'email'}          = $data->{'email'};
753
                $borrower_overdues->{'emailpro'}       = $data->{'emailpro'};
754
                $borrower_overdues->{'B_email'}        = $data->{'B_email'};
755
                $borrower_overdues->{'smsalertnumber'} = $data->{'smsalertnumber'};
756
                $borrower_overdues->{'phone'}          = $data->{'phone'};
888
            }
757
            }
889
            $sth->finish;
758
        }
759
        $sth->finish;
760
761
        # Catch final trigger
762
        if ( $borrower_overdues->{borrowernumber} ) {
763
            $verbose and warn "Collected overdue triggers for " . $borrower_overdues->{borrowernumber} . "\n";
764
            _enact_trigger($borrower_overdues);
765
            $borrower_overdues = {};
890
        }
766
        }
891
    }
767
    }
892
768
Lines 944-951 END_SQL Link Here
944
            ) unless $test_mode;
820
            ) unless $test_mode;
945
        }
821
        }
946
    }
822
    }
947
948
}
823
}
824
949
if ($csvfilename) {
825
if ($csvfilename) {
950
826
951
    # note that we're not testing on $csv_fh to prevent closing
827
    # note that we're not testing on $csv_fh to prevent closing
Lines 964-969 if ( defined $htmlfilename ) { Link Here
964
840
965
These methods are internal to the operation of overdue_notices.pl.
841
These methods are internal to the operation of overdue_notices.pl.
966
842
843
=cut
844
845
sub _enact_trigger {
846
    my ($borrower_overdues) = @_;
847
848
    my $borrowernumber = $borrower_overdues->{borrowernumber};
849
    my $branchcode     = $borrower_overdues->{branchcode};
850
    my $patron         = Koha::Patrons->find($borrowernumber);
851
    my ( $library, $admin_email_address, $branch_email_address );
852
    $library = Koha::Libraries->find($branchcode);
853
854
    if ($patron_homelibrary) {
855
        $branchcode           = $patron->branchcode;
856
        $library              = Koha::Libraries->find($branchcode);
857
        $admin_email_address  = $library->from_email_address;
858
        $branch_email_address = C4::Context->preference('AddressForFailedOverdueNotices')
859
            || $library->inbound_email_address;
860
    }
861
    @emails_to_use = ();
862
    my $notice_email = $patron->notice_email_address;
863
    unless ($nomail) {
864
        if (@emails) {
865
            foreach (@emails) {
866
                push @emails_to_use, $borrower_overdues->{$_} if ( $borrower_overdues->{$_} );
867
            }
868
        } else {
869
            push @emails_to_use, $notice_email if ($notice_email);
870
        }
871
    }
872
873
    for my $trigger ( sort keys %{ $borrower_overdues->{triggers} } ) {
874
        for my $notice ( keys %{ $borrower_overdues->{triggers}->{$trigger} } ) {
875
            my $print_sent = 0;    # A print notice is not yet sent for this patron
876
            for my $mtt ( keys %{ $borrower_overdues->{triggers}->{$trigger}->{$notice} } ) {
877
878
                next if $mtt eq 'itiva';
879
                my $effective_mtt = $mtt;
880
                if (   ( $mtt eq 'email' and not scalar @emails_to_use )
881
                    or ( $mtt eq 'sms' and not $borrower_overdues->{smsalertnumber} ) )
882
                {
883
                    # email or sms is requested but not exist, do a print.
884
                    $effective_mtt = 'print';
885
                }
886
887
                my $j                            = 0;
888
                my $exceededPrintNoticesMaxLines = 0;
889
890
                # Get each overdue item for this trigger
891
                my $itemcount = 0;
892
                my $titles    = "";
893
                my @items     = ();
894
                for my $item_info ( @{ $borrower_overdues->{triggers}->{$trigger}->{$notice}->{$effective_mtt} } ) {
895
                    if (   ( scalar(@emails_to_use) == 0 || $nomail )
896
                        && $PrintNoticesMaxLines
897
                        && $j >= $PrintNoticesMaxLines )
898
                    {
899
                        $exceededPrintNoticesMaxLines = 1;
900
                        last;
901
                    }
902
                    next if $patron_homelibrary and !grep { $seen{ $item_info->{branchcode} } } @branches;
903
                    $j++;
904
905
                    $titles .= C4::Letters::get_item_content(
906
                        { item => $item_info, item_content_fields => \@item_content_fields, dateonly => 1 } );
907
                    $itemcount++;
908
                    push @items, $item_info;
909
                }
910
911
                splice @items, $PrintNoticesMaxLines
912
                    if $effective_mtt eq 'print'
913
                    && $PrintNoticesMaxLines
914
                    && scalar @items > $PrintNoticesMaxLines;
915
916
                #catch the case where we are sending a print to someone with an email
917
918
                my $letter_exists = Koha::Notice::Templates->find_effective_template(
919
                    {
920
                        module                 => 'circulation',
921
                        code                   => $notice,
922
                        message_transport_type => $effective_mtt,
923
                        branchcode             => $branchcode,
924
                        lang                   => $patron->lang
925
                    }
926
                );
927
928
                unless ($letter_exists) {
929
                    $verbose and warn qq|Message '$notice' for '$effective_mtt' content not found|;
930
                    next;
931
                }
932
933
                my $letter = parse_overdues_letter(
934
                    {
935
                        letter_code    => $notice,
936
                        borrowernumber => $borrowernumber,
937
                        branchcode     => $branchcode,
938
                        items          => \@items,
939
                        substitute     => {    # this appears to be a hack to overcome incomplete features in this code.
940
                            bib             => $library->branchname,    # maybe 'bib' is a typo for 'lib<rary>'?
941
                            'items.content' => $titles,
942
                            'count'         => $itemcount,
943
                        },
944
945
                        # If there is no template defined for the requested letter
946
                        # Fallback on the original type
947
                        message_transport_type => $letter_exists ? $effective_mtt : $mtt,
948
                    }
949
                );
950
                unless ( $letter && $letter->{content} ) {
951
                    $verbose and warn qq|Message '$notice' content not found|;
952
953
                    # this transport doesn't have a configured notice, so try another
954
                    next;
955
                }
956
957
                if ($exceededPrintNoticesMaxLines) {
958
                    $letter->{'content'} .=
959
                        "List too long for form; please check your account online for a complete list of your overdue items.";
960
                }
961
962
                my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); } split /\</,
963
                    $letter->{'content'};
964
                if (@misses) {
965
                    $verbose
966
                        and warn "The following terms were not matched and replaced: \n\t" . join "\n\t",
967
                        @misses;
968
                }
969
970
                if ($nomail) {
971
                    push @output_chunks,
972
                        prepare_letter_for_printing(
973
                        {
974
                            letter         => $letter,
975
                            borrowernumber => $borrowernumber,
976
                            firstname      => $borrower_overdues->{'firstname'},
977
                            lastname       => $borrower_overdues->{'surname'},
978
                            address1       => $borrower_overdues->{'address'},
979
                            address2       => $borrower_overdues->{'address2'},
980
                            city           => $borrower_overdues->{'city'},
981
                            phone          => $borrower_overdues->{'phone'},
982
                            cardnumber     => $borrower_overdues->{'cardnumber'},
983
                            branchname     => $library->branchname,
984
                            letternumber   => $trigger,
985
                            postcode       => $borrower_overdues->{'zipcode'},
986
                            country        => $borrower_overdues->{'country'},
987
                            email          => $notice_email,
988
                            itemcount      => $itemcount,
989
                            titles         => $titles,
990
                            outputformat   => defined $csvfilename ? 'csv'
991
                            : defined $htmlfilename  ? 'html'
992
                            : defined $text_filename ? 'text'
993
                            : '',
994
                        }
995
                        );
996
                } else {
997
                    if (   ( $mtt eq 'email' and not scalar @emails_to_use )
998
                        or ( $mtt eq 'sms' and not $borrower_overdues->{smsalertnumber} ) )
999
                    {
1000
                        push @output_chunks,
1001
                            prepare_letter_for_printing(
1002
                            {
1003
                                letter         => $letter,
1004
                                borrowernumber => $borrowernumber,
1005
                                firstname      => $borrower_overdues->{'firstname'},
1006
                                lastname       => $borrower_overdues->{'surname'},
1007
                                address1       => $borrower_overdues->{'address'},
1008
                                address2       => $borrower_overdues->{'address2'},
1009
                                city           => $borrower_overdues->{'city'},
1010
                                postcode       => $borrower_overdues->{'zipcode'},
1011
                                country        => $borrower_overdues->{'country'},
1012
                                email          => $notice_email,
1013
                                itemcount      => $itemcount,
1014
                                titles         => $titles,
1015
                                outputformat   => defined $csvfilename ? 'csv'
1016
                                : defined $htmlfilename  ? 'html'
1017
                                : defined $text_filename ? 'text'
1018
                                : '',
1019
                            }
1020
                            );
1021
                    }
1022
                    unless ( $effective_mtt eq 'print' and $print_sent == 1 ) {
1023
1024
                        # Just sent a print if not already done.
1025
                        C4::Letters::EnqueueLetter(
1026
                            {
1027
                                letter                 => $letter,
1028
                                borrowernumber         => $borrowernumber,
1029
                                message_transport_type => $effective_mtt,
1030
                                from_address           => $admin_email_address,
1031
                                to_address             => join( ',', @emails_to_use ),
1032
                                reply_address          => $library->inbound_email_address,
1033
                            }
1034
                        ) unless $test_mode;
1035
1036
                        # A print notice should be sent only once per overdue level.
1037
                        # 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.
1038
                        $print_sent = 1 if $effective_mtt eq 'print';
1039
                    }
1040
                }
1041
            }
1042
1043
            $already_queued{"$borrowernumber$trigger"} = 1;
1044
        }
1045
    }
1046
1047
    if ( $borrower_overdues->{restrict} ) {
1048
1049
        #action taken is debarring
1050
        AddUniqueDebarment(
1051
            {
1052
                borrowernumber => $borrowernumber,
1053
                type           => 'OVERDUES',
1054
                comment        => "OVERDUES_PROCESS " . output_pref( dt_from_string() ),
1055
            }
1056
        ) unless $test_mode;
1057
1058
        my $borr = sprintf(
1059
            "%s%s%s (%s)",
1060
            $borrower_overdues->{'surname'} || '',
1061
            $borrower_overdues->{'firstname'} && $borrower_overdues->{'surname'} ? ', ' : '',
1062
            $borrower_overdues->{'firstname'} || '',
1063
            $borrower_overdues->{borrowernumber}
1064
        );
1065
        $verbose and warn "debarring $borr\n";
1066
    }
1067
}
1068
967
=head2 prepare_letter_for_printing
1069
=head2 prepare_letter_for_printing
968
1070
969
returns a string of text appropriate for printing in the event that an
1071
returns a string of text appropriate for printing in the event that an
970
- 

Return to bug 10190