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 |
- |
|
|