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

(-)a/misc/cronjobs/overdue_notices.pl (-363 / +463 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-612 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 $item_sql = <<"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
        $item_sql .= ' AND items.homebranch = ? ' unless ($patron_homelibrary);
589
        if ($branchcode) {
518
    } else {
519
        $item_sql .= ' AND issues.branchcode = ? ' unless ($patron_homelibrary);
520
    }
521
    my $item_statement = $dbh->prepare($item_sql);
522
523
    my $query = "SELECT * FROM overduerules WHERE delay1 IS NOT NULL AND branchcode = ? ";
524
    $query .= " AND categorycode IN (" . join( ',', ('?') x @myborcat ) . ") "        if (@myborcat);
525
    $query .= " AND categorycode NOT IN (" . join( ',', ('?') x @myborcatout ) . ") " if (@myborcatout);
526
527
    my $rqoverduerules = $dbh->prepare($query);
528
    $rqoverduerules->execute( $branchcode, @myborcat, @myborcatout );
529
530
    # We get default rules if there is no rule for this branch
531
    if ( $rqoverduerules->rows == 0 ) {
532
        $query = "SELECT * FROM overduerules WHERE delay1 IS NOT NULL AND branchcode = '' ";
533
        $query .= " AND categorycode IN (" . join( ',', ('?') x @myborcat ) . ") "        if (@myborcat);
534
        $query .= " AND categorycode NOT IN (" . join( ',', ('?') x @myborcatout ) . ") " if (@myborcatout);
535
536
        $rqoverduerules = $dbh->prepare($query);
537
        $rqoverduerules->execute( @myborcat, @myborcatout );
538
    }
539
540
    # my $outfile = 'overdues_' . ( $mybranch || $branchcode || 'default' );
541
    while ( my $overdue_rules = $rqoverduerules->fetchrow_hashref ) {
542
    PERIOD: foreach my $i ( 1 .. 3 ) {
543
544
            $verbose and warn "branch '$branchcode', categorycode = $overdue_rules->{categorycode} pass $i\n";
545
            my $notice_branchcode = $branchcode;
546
547
            my $mindays = $overdue_rules->{"delay$i"};    # the notice will be sent after mindays days (grace period)
548
            my $maxdays = (
549
                  $overdue_rules->{ "delay" . ( $i + 1 ) }
550
                ? $overdue_rules->{ "delay" . ( $i + 1 ) } - 1
551
                : ($MAX)
552
            );    # issues being more than maxdays late are managed somewhere else. (borrower probably suspended)
553
554
            next unless defined $mindays;
555
556
            if ( !$overdue_rules->{"letter$i"} ) {
557
                $verbose and warn sprintf "No letter code found for pass %s\n", $i;
558
                next PERIOD;
559
            }
560
            $verbose and warn sprintf "Using letter code '%s' for pass %s\n", $overdue_rules->{"letter$i"}, $i;
561
562
            # $letter->{'content'} is the text of the mail that is sent.
563
            # this text contains fields that are replaced by their value. Those fields must be written between brackets
564
            # The following fields are available :
565
            # 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).
566
            # <date> <itemcount> <firstname> <lastname> <address1> <address2> <address3> <city> <postcode> <country>
567
568
            my $borrower_sql = <<"END_SQL";
569
SELECT issues.borrowernumber, firstname, surname, address, address2, city, zipcode, country, email, emailpro, B_email, smsalertnumber, phone, cardnumber, date_due
570
FROM   issues,borrowers,categories,items
571
WHERE  issues.borrowernumber=borrowers.borrowernumber
572
AND    borrowers.categorycode=categories.categorycode
573
AND    issues.itemnumber = items.itemnumber
574
AND    items.itemlost = 0
575
AND    TO_DAYS($date)-TO_DAYS(issues.date_due) >= 0
576
END_SQL
577
            my @borrower_parameters;
578
579
            if ($owning_library) {
590
            if ($owning_library) {
580
                $borrower_sql .= ' AND items.homebranch=? ';
591
                $borrower_sql .= ' AND items.homebranch=? ';
581
            } else {
592
            } else {
582
                $borrower_sql .= ' AND issues.branchcode=? ';
593
                $borrower_sql .= ' AND issues.branchcode=? ';
583
            }
594
            }
584
            push @borrower_parameters, $branchcode;
595
            push @borrower_parameters, $branchcode;
585
596
        }
586
            if ( $overdue_rules->{categorycode} ) {
597
        if ($category) {
587
                $borrower_sql .= ' AND borrowers.categorycode=? ';
598
            $borrower_sql .= ' AND borrowers.categorycode=? ';
588
                push @borrower_parameters, $overdue_rules->{categorycode};
599
            push @borrower_parameters, $category;
600
        }
601
        if (@itemtypes) {
602
            my $placeholders = join( ", ", ("?") x @itemtypes );
603
            if ( $itype_level eq 'item' ) {
604
                $borrower_sql .= " AND items.itype IN ($placeholders) ";
605
            } else {
606
                $borrower_sql .= " AND biblioitems.itemtype IN ($placeholders) ";
589
            }
607
            }
590
            $borrower_sql .= '  AND categories.overduenoticerequired=1 ORDER BY issues.borrowernumber';
608
            push @borrower_parameters, @itemtypes;
591
609
        }
592
            # $borrower_statement gets borrower info if at least one overdue item has triggered the overdue action.
610
        $borrower_sql .= '  AND categories.overduenoticerequired=1 ORDER BY issues.borrowernumber';
593
            my $borrower_statement = $dbh->prepare($borrower_sql);
611
594
            $borrower_statement->execute(@borrower_parameters);
612
        # $sth gets borrower info if at least one overdue item has triggered the overdue action.
595
613
        my $sth = $dbh->prepare($borrower_sql);
596
            if ( $verbose > 1 ) {
614
        $sth->execute(@borrower_parameters);
597
                warn sprintf "--------Borrower SQL------\n";
615
598
                warn $borrower_sql
616
        if ( $verbose > 1 ) {
599
                    . "\n $branchcode | "
617
            warn sprintf "--------Borrower SQL------\n";
600
                    . $overdue_rules->{'categorycode'}
618
            warn $borrower_sql
601
                    . "\n ($mindays, $maxdays, "
619
                . "\n $branchcode | "
602
                    . $date_to_run->datetime() . ")\n";
620
                . "'$category' | "
603
                warn sprintf "--------------------------\n";
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;
626
627
        my $borrowernumber;
628
        my $borrower_overdues = {};
629
630
        # Iterate over all overdues
631
    OVERDUE: while ( my $data = $sth->fetchrow_hashref ) {
632
            my $itemtype = $data->{itype} // $data->{b_itemtype};
633
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 };
604
            }
649
            }
605
            $verbose and warn sprintf "Found %s borrowers with overdues\n", $borrower_statement->rows;
606
            my $borrowernumber;
607
            while ( my $data = $borrower_statement->fetchrow_hashref ) {
608
650
609
                # check the borrower has at least one item that matches
651
            $verbose
652
                and warn "\nProcessing overdue "
653
                . $data->{issue_id}
654
                . " with branch = '$branchcode', categorycode = '$category' and itemtype = '$itemtype'\n";
655
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
                );
672
673
                if ( !defined( $overdue_rules->{ "overdue_$i" . '_delay' } ) ) {
674
                    last PERIOD;
675
                }
676
677
                my $mindays =
678
                    $overdue_rules->{ "overdue_$i" . '_delay' }
679
                    ;    # the notice will be sent after mindays days (grace period)
680
                my $maxdays = (
681
                      $overdue_rules->{ "overdue_$ii" . '_delay' }
682
                    ? $overdue_rules->{ "overdue_$ii" . '_delay' } - 1
683
                    : ($MAX)
684
                );       # issues being more than maxdays late are managed somewhere else. (borrower probably suspended)
685
686
                if ( !$overdue_rules->{ "overdue_$i" . '_notice' } ) {
687
                    $verbose and warn sprintf "Trigger %s skipped, No letter code found\n", $i;
688
                    next PERIOD;
689
                }
690
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
610
                my $days_between;
698
                my $days_between;
611
                if ( C4::Context->preference('OverdueNoticeCalendar') ) {
699
                if ( C4::Context->preference('OverdueNoticeCalendar') ) {
612
                    $days_between = $calendar->days_between(
700
                    $days_between = $calendar->days_between(
Lines 618-893 END_SQL Link Here
618
                        $date_to_run->delta_days( dt_from_string( $data->{date_due} ) );
706
                        $date_to_run->delta_days( dt_from_string( $data->{date_due} ) );
619
                }
707
                }
620
                $days_between = $days_between->in_units('days');
708
                $days_between = $days_between->in_units('days');
621
                if ($triggered) {
709
                if ($listall) {
622
                    if ( $mindays != $days_between ) {
710
                    unless ( $days_between >= 1 and $days_between <= $MAX ) {
623
                        next;
711
                        next;
624
                    }
712
                    }
625
                } else {
713
                } else {
626
                    unless ( $days_between >= $mindays
714
                    if ($triggered) {
627
                        && $days_between <= $maxdays )
715
                        if ( $mindays != $days_between ) {
628
                    {
716
                            $verbose and warn "Overdue skipped for trigger $i\n";
629
                        next;
717
                            next;
630
                    }
631
                }
632
                if ( defined $borrowernumber && $borrowernumber eq $data->{'borrowernumber'} ) {
633
634
                    # we have already dealt with this borrower
635
                    $verbose and warn "already dealt with this borrower $borrowernumber";
636
                    next;
637
                }
638
                $borrowernumber = $data->{'borrowernumber'};
639
                next if ( $patron_homelibrary && $already_queued{"$borrowernumber$i"} );
640
                my $borr = sprintf(
641
                    "%s%s%s (%s)",
642
                    $data->{'surname'} || '',
643
                    $data->{'firstname'} && $data->{'surname'} ? ', ' : '',
644
                    $data->{'firstname'} || '',
645
                    $borrowernumber
646
                );
647
                $verbose and warn "borrower $borr has items triggering level $i.\n";
648
649
                my $patron = Koha::Patrons->find($borrowernumber);
650
                if ($patron_homelibrary) {
651
                    $notice_branchcode    = $patron->branchcode;
652
                    $library              = Koha::Libraries->find($notice_branchcode);
653
                    $admin_email_address  = $library->from_email_address;
654
                    $branch_email_address = C4::Context->preference('AddressForFailedOverdueNotices')
655
                        || $library->inbound_email_address;
656
                }
657
                @emails_to_use = ();
658
                my $notice_email = $patron->notice_email_address;
659
                unless ($nomail) {
660
                    if (@emails) {
661
                        foreach (@emails) {
662
                            push @emails_to_use, $data->{$_} if ( $data->{$_} );
663
                        }
718
                        }
664
                    } else {
719
                    } else {
665
                        push @emails_to_use, $notice_email if ($notice_email);
720
                        unless ( $days_between >= $mindays
666
                    }
721
                            && $days_between <= $maxdays )
667
                }
668
669
                my $letter = Koha::Notice::Templates->find_effective_template(
670
                    {
671
                        module     => 'circulation',
672
                        code       => $overdue_rules->{"letter$i"},
673
                        branchcode => $notice_branchcode,
674
                        lang       => $patron->lang
675
                    }
676
                );
677
678
                unless ($letter) {
679
                    $verbose and warn qq|Message '$overdue_rules->{"letter$i"}' content not found|;
680
681
                    # might as well skip while PERIOD, no other borrowers are going to work.
682
                    # FIXME : Does this mean a letter must be defined in order to trigger a debar ?
683
                    next PERIOD;
684
                }
685
686
                if ( $overdue_rules->{"debarred$i"} ) {
687
688
                    #action taken is debarring
689
                    AddUniqueDebarment(
690
                        {
722
                        {
691
                            borrowernumber => $borrowernumber,
723
                            $verbose and warn "Overdue skipped for trigger $i\n";
692
                            type           => 'OVERDUES',
693
                            comment        => "OVERDUES_PROCESS " . output_pref( dt_from_string() ),
694
                        }
695
                    ) unless $test_mode;
696
                    $verbose and warn "debarring $borr\n";
697
                }
698
                my @params = $patron_homelibrary ? ($borrowernumber) : ( $borrowernumber, $branchcode );
699
700
                $item_statement->execute(@params);
701
                my $itemcount = 0;
702
                my $titles    = "";
703
                my @items     = ();
704
705
                my $j                            = 0;
706
                my $exceededPrintNoticesMaxLines = 0;
707
                while ( my $item_info = $item_statement->fetchrow_hashref() ) {
708
                    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
709
                        $days_between =
710
                            $calendar->days_between( dt_from_string( $item_info->{date_due} ), $date_to_run );
711
                    } else {
712
                        $days_between = $date_to_run->delta_days( dt_from_string( $item_info->{date_due} ) );
713
                    }
714
                    $days_between = $days_between->in_units('days');
715
                    if ($listall) {
716
                        unless ( $days_between >= 1 and $days_between <= $MAX ) {
717
                            next;
724
                            next;
718
                        }
725
                        }
719
                    } else {
720
                        if ($triggered) {
721
                            if ( $mindays != $days_between ) {
722
                                next;
723
                            }
724
                        } else {
725
                            unless ( $days_between >= $mindays
726
                                && $days_between <= $maxdays )
727
                            {
728
                                next;
729
                            }
730
                        }
731
                    }
732
733
                    if (   ( scalar(@emails_to_use) == 0 || $nomail )
734
                        && $PrintNoticesMaxLines
735
                        && $j >= $PrintNoticesMaxLines )
736
                    {
737
                        $exceededPrintNoticesMaxLines = 1;
738
                        last;
739
                    }
726
                    }
740
                    next if $patron_homelibrary and !grep { $seen{ $item_info->{branchcode} } } @branches;
741
                    $j++;
742
743
                    $titles .= C4::Letters::get_item_content(
744
                        { item => $item_info, item_content_fields => \@item_content_fields, dateonly => 1 } );
745
                    $itemcount++;
746
                    push @items, $item_info;
747
                }
727
                }
748
                $item_statement->finish;
749
750
                my @message_transport_types =
751
                    @{ GetOverdueMessageTransportTypes( $branchcode, $overdue_rules->{categorycode}, $i ) };
752
                @message_transport_types =
753
                    @{ GetOverdueMessageTransportTypes( q{}, $overdue_rules->{categorycode}, $i ) }
754
                    unless @message_transport_types;
755
756
                my $print_sent = 0;    # A print notice is not yet sent for this patron
757
                for my $mtt (@message_transport_types) {
758
                    next if $mtt eq 'itiva';
759
                    my $effective_mtt = $mtt;
760
                    if (   ( $mtt eq 'email' and not scalar @emails_to_use )
761
                        or ( $mtt eq 'sms' and not $data->{smsalertnumber} ) )
762
                    {
763
                        # email or sms is requested but not exist, do a print.
764
                        $effective_mtt = 'print';
765
                    }
766
                    splice @items, $PrintNoticesMaxLines
767
                        if $effective_mtt eq 'print' && $PrintNoticesMaxLines && scalar @items > $PrintNoticesMaxLines;
768
769
                    #catch the case where we are sending a print to someone with an email
770
728
771
                    my $letter_exists = Koha::Notice::Templates->find_effective_template(
729
                if ($verbose) {
772
                        {
730
                    my $borr = sprintf(
773
                            module                 => 'circulation',
731
                        "%s%s%s (%s)",
774
                            code                   => $overdue_rules->{"letter$i"},
732
                        $data->{'surname'} || '',
775
                            message_transport_type => $effective_mtt,
733
                        $data->{'firstname'} && $data->{'surname'} ? ', ' : '',
776
                            branchcode             => $notice_branchcode,
734
                        $data->{'firstname'} || '',
777
                            lang                   => $patron->lang
735
                        $data->{borrowernumber}
778
                        }
779
                    );
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
                }
780
743
781
                    my $letter = parse_overdues_letter(
744
                my @message_transport_types = split( /,/, $overdue_rules->{ "overdue_$i" . '_mtt' } );
782
                        {
745
                for my $mtt (@message_transport_types) {
783
                            letter_code    => $overdue_rules->{"letter$i"},
746
                    push @{ $borrower_overdues->{triggers}->{$i}->{ $overdue_rules->{ "overdue_$i" . '_notice' } }
784
                            borrowernumber => $borrowernumber,
747
                            ->{$mtt} }, $data;
785
                            branchcode     => $notice_branchcode,
748
                }
786
                            items          => \@items,
749
                if ( $overdue_rules->{ "overdue_$i" . '_restrict' } ) {
787
                            substitute => {    # this appears to be a hack to overcome incomplete features in this code.
750
                    $borrower_overdues->{restrict} = 1;
788
                                bib             => $library->branchname,    # maybe 'bib' is a typo for 'lib<rary>'?
789
                                'items.content' => $titles,
790
                                'count'         => $itemcount,
791
                            },
792
793
                            # If there is no template defined for the requested letter
794
                            # Fallback on the original type
795
                            message_transport_type => $letter_exists ? $effective_mtt : $mtt,
796
                        }
797
                    );
798
                    unless ( $letter && $letter->{content} ) {
799
                        $verbose and warn qq|Message '$overdue_rules->{"letter$i"}' content not found|;
800
801
                        # this transport doesn't have a configured notice, so try another
802
                        next;
803
                    }
804
805
                    if ($exceededPrintNoticesMaxLines) {
806
                        $letter->{'content'} .=
807
                            "List too long for form; please check your account online for a complete list of your overdue items.";
808
                    }
809
810
                    my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); } split /\</, $letter->{'content'};
811
                    if (@misses) {
812
                        $verbose
813
                            and warn "The following terms were not matched and replaced: \n\t" . join "\n\t", @misses;
814
                    }
815
816
                    if ($nomail) {
817
                        push @output_chunks,
818
                            prepare_letter_for_printing(
819
                            {
820
                                letter         => $letter,
821
                                borrowernumber => $borrowernumber,
822
                                firstname      => $data->{'firstname'},
823
                                lastname       => $data->{'surname'},
824
                                address1       => $data->{'address'},
825
                                address2       => $data->{'address2'},
826
                                city           => $data->{'city'},
827
                                phone          => $data->{'phone'},
828
                                cardnumber     => $data->{'cardnumber'},
829
                                branchname     => $library->branchname,
830
                                letternumber   => $i,
831
                                postcode       => $data->{'zipcode'},
832
                                country        => $data->{'country'},
833
                                email          => $notice_email,
834
                                itemcount      => $itemcount,
835
                                titles         => $titles,
836
                                outputformat   => defined $csvfilename ? 'csv'
837
                                : defined $htmlfilename  ? 'html'
838
                                : defined $text_filename ? 'text'
839
                                : '',
840
                            }
841
                            );
842
                    } else {
843
                        if (   ( $mtt eq 'email' and not scalar @emails_to_use )
844
                            or ( $mtt eq 'sms' and not $data->{smsalertnumber} ) )
845
                        {
846
                            push @output_chunks,
847
                                prepare_letter_for_printing(
848
                                {
849
                                    letter         => $letter,
850
                                    borrowernumber => $borrowernumber,
851
                                    firstname      => $data->{'firstname'},
852
                                    lastname       => $data->{'surname'},
853
                                    address1       => $data->{'address'},
854
                                    address2       => $data->{'address2'},
855
                                    city           => $data->{'city'},
856
                                    postcode       => $data->{'zipcode'},
857
                                    country        => $data->{'country'},
858
                                    email          => $notice_email,
859
                                    itemcount      => $itemcount,
860
                                    titles         => $titles,
861
                                    outputformat   => defined $csvfilename ? 'csv'
862
                                    : defined $htmlfilename  ? 'html'
863
                                    : defined $text_filename ? 'text'
864
                                    : '',
865
                                }
866
                                );
867
                        }
868
                        unless ( $effective_mtt eq 'print' and $print_sent == 1 ) {
869
870
                            # Just sent a print if not already done.
871
                            C4::Letters::EnqueueLetter(
872
                                {
873
                                    letter                 => $letter,
874
                                    borrowernumber         => $borrowernumber,
875
                                    message_transport_type => $effective_mtt,
876
                                    from_address           => $admin_email_address,
877
                                    to_address             => join( ',', @emails_to_use ),
878
                                    reply_address          => $library->inbound_email_address,
879
                                }
880
                            ) unless $test_mode;
881
882
                            # A print notice should be sent only once per overdue level.
883
                            # 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.
884
                            $print_sent = 1 if $effective_mtt eq 'print';
885
                        }
886
                    }
887
                }
751
                }
888
                $already_queued{"$borrowernumber$i"} = 1;
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'};
889
            }
757
            }
890
            $borrower_statement->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 = {};
891
        }
766
        }
892
    }
767
    }
893
768
Lines 945-952 END_SQL Link Here
945
            ) unless $test_mode;
820
            ) unless $test_mode;
946
        }
821
        }
947
    }
822
    }
948
949
}
823
}
824
950
if ($csvfilename) {
825
if ($csvfilename) {
951
826
952
    # 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 965-970 if ( defined $htmlfilename ) { Link Here
965
840
966
These methods are internal to the operation of overdue_notices.pl.
841
These methods are internal to the operation of overdue_notices.pl.
967
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
968
=head2 prepare_letter_for_printing
1069
=head2 prepare_letter_for_printing
969
1070
970
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
971
- 

Return to bug 10190