Bugzilla – Attachment 171748 Details for
Bug 10190
Overdue notice triggers based on item type
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 10190: Rework logic of overdue_notices
Bug-10190-Rework-logic-of-overduenotices.patch (text/plain), 53.32 KB, created by
Martin Renvoize (ashimema)
on 2024-09-19 13:21:31 UTC
(
hide
)
Description:
Bug 10190: Rework logic of overdue_notices
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2024-09-19 13:21:31 UTC
Size:
53.32 KB
patch
obsolete
>From 95380df0899f0dfee6ada3734f8282362ac30059 Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@ptfs-europe.com> >Date: Mon, 19 Aug 2024 17:21:09 +0100 >Subject: [PATCH] Bug 10190: Rework logic of overdue_notices > >The original logic for digesting overdues in the overdue notices script >was flawed given the additional itemtype handling introduced in this >patchset and the way in which it handles effective fallbacks for rules. > >This patch tidies the file and reworks the logic so that we fetch >overdues by patron category and branch and then iterate through those >comparing to the correct effective rules and combing them into digest >groups based on notice + trigger level. >--- > misc/cronjobs/overdue_notices.pl | 917 +++++++++++++++++-------------- > 1 file changed, 505 insertions(+), 412 deletions(-) > >diff --git a/misc/cronjobs/overdue_notices.pl b/misc/cronjobs/overdue_notices.pl >index 204bef008f3..6452f35f7e1 100755 >--- a/misc/cronjobs/overdue_notices.pl >+++ b/misc/cronjobs/overdue_notices.pl >@@ -29,10 +29,10 @@ use DateTime::Duration; > use Koha::Script -cron; > use C4::Context; > use C4::Letters; >-use C4::Overdues qw( parse_overdues_letter ); >-use C4::Log qw( cronlogaction ); >+use C4::Overdues qw( parse_overdues_letter ); >+use C4::Log qw( cronlogaction ); > use Koha::Patron::Debarments qw( AddUniqueDebarment ); >-use Koha::DateUtils qw( dt_from_string output_pref ); >+use Koha::DateUtils qw( dt_from_string output_pref ); > use Koha::Calendar; > use Koha::Libraries; > use Koha::Acquisition::Currencies; >@@ -298,22 +298,23 @@ alert them of items that have just become due. > > # These variables are set by command line options. > # They are initially set to default values. >-my $dbh = C4::Context->dbh(); >-my $help = 0; >-my $man = 0; >-my $verbose = 0; >-my $nomail = 0; >-my $MAX = 90; >-my $test_mode = 0; >-my $frombranch = 'item-issuebranch'; >-my @branchcodes; # Branch(es) passed as parameter >+my $dbh = C4::Context->dbh(); >+my $help = 0; >+my $man = 0; >+my $verbose = 0; >+my $nomail = 0; >+my $MAX = 90; >+my $test_mode = 0; >+my $frombranch = 'item-issuebranch'; >+my $itype_level = C4::Context->preference('item-level_itypes') ? 'item' : 'biblioitem'; >+my @branchcodes; # Branch(es) passed as parameter > my @emails_to_use; # Emails to use for messaging > my @emails; # Emails given in command-line parameters > my $csvfilename; > my $htmlfilename; > my $text_filename; >-my $triggered = 0; >-my $listall = 0; >+my $triggered = 0; >+my $listall = 0; > my $itemscontent = join( ',', qw( date_due title barcode author itemnumber ) ); > my @myitemtypes; > my @myitemtypesout; >@@ -321,7 +322,7 @@ my @myborcat; > my @myborcatout; > my ( $date_input, $today ); > >-my $command_line_options = join(" ",@ARGV); >+my $command_line_options = join( " ", @ARGV ); > > GetOptions( > 'help|?' => \$help, >@@ -359,20 +360,22 @@ die "--frombranch takes item-homebranch or item-issuebranch or patron-homebranch > || $frombranch eq 'patron-homebranch' ); > $frombranch = > C4::Context->preference('OverdueNoticeFrom') ne 'cron' ? C4::Context->preference('OverdueNoticeFrom') : $frombranch; >-my $owning_library = ( $frombranch eq 'item-homebranch' ) ? 1 : 0; >+my $owning_library = ( $frombranch eq 'item-homebranch' ) ? 1 : 0; > my $patron_homelibrary = ( $frombranch eq 'patron-homebranch' ) ? 1 : 0; > >-my @overduebranches = C4::Overdues::GetBranchcodesWithOverdueRules(); # Branches with overdue rules >-my @branches; # Branches passed as parameter with overdue rules >+my @overduebranches = C4::Overdues::GetBranchcodesWithOverdueRules(); # Branches with overdue rules >+my @branches; # Branches passed as parameter with overdue rules > my $branchcount = scalar(@overduebranches); > > my $overduebranch_word = scalar @overduebranches > 1 ? 'branches' : 'branch'; >-my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch'; >+my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch'; > > my $PrintNoticesMaxLines = C4::Context->preference('PrintNoticesMaxLines'); > > if ($branchcount) { >- $verbose and warn "Found $branchcount $overduebranch_word with first message enabled: " . join( ', ', map { "'$_'" } @overduebranches ), "\n"; >+ $verbose >+ and warn "Found $branchcount $overduebranch_word with first message enabled: " >+ . join( ', ', map { "'$_'" } @overduebranches ), "\n"; > } else { > die 'No branches with active overduerules'; > } >@@ -384,36 +387,32 @@ if (@branchcodes) { > my %seen = map { $_ => 1 } @branchcodes; > @branches = grep { $seen{$_} } @overduebranches; > >- > if (@branches) { > > my $branch_word = scalar @branches > 1 ? 'branches' : 'branch'; >- $verbose and warn "$branch_word @branches have overdue rules\n"; >+ $verbose and warn "$branch_word @branches have overdue rules\n"; > > } else { > > $verbose and warn "No active overduerules for $branchcodes_word '@branchcodes'\n"; > ( scalar grep { '' eq $_ } @branches ) >- or die "No active overduerules for DEFAULT either!"; >+ or die "No active overduerules for DEFAULT either!"; > $verbose and warn "Falling back on default rules for @branchcodes\n"; > @branches = (''); > } > } > my $date_to_run; > my $date; >-if ( $date_input ){ >- eval { >- $date_to_run = dt_from_string( $date_input, 'iso' ); >- }; >+if ($date_input) { >+ eval { $date_to_run = dt_from_string( $date_input, 'iso' ); }; > die "$date_input is not a valid date, aborting! Use a date in format YYYY-MM-DD." > if $@ or not $date_to_run; > > # It's certainly useless to escape $date_input > # dt_from_string should not return something if $date_input is not correctly set. >- $date = $dbh->quote( $date_input ); >-} >-else { >- $date="NOW()"; >+ $date = $dbh->quote($date_input); >+} else { >+ $date = "NOW()"; > $date_to_run = dt_from_string(); > } > >@@ -422,18 +421,22 @@ my @item_content_fields = split( /,/, $itemscontent ); > > binmode( STDOUT, ':encoding(UTF-8)' ); > >- > our $csv; # the Text::CSV_XS object > our $csv_fh; # the filehandle to the CSV file. > if ( defined $csvfilename ) { > my $sep_char = C4::Context->csv_delimiter; >- $csv = Text::CSV_XS->new( { binary => 1 , sep_char => $sep_char } ); >+ $csv = Text::CSV_XS->new( { binary => 1, sep_char => $sep_char } ); > if ( $csvfilename eq '' ) { > $csv_fh = *STDOUT; > } else { > open $csv_fh, ">", $csvfilename or die "unable to open $csvfilename: $!"; > } >- if ( $csv->combine(qw(name surname address1 address2 zipcode city country email phone cardnumber itemcount itemsinfo branchname letternumber)) ) { >+ if ( >+ $csv->combine( >+ qw(name surname address1 address2 zipcode city country email phone cardnumber itemcount itemsinfo branchname letternumber) >+ ) >+ ) >+ { > print $csv_fh $csv->string, "\n"; > } else { > $verbose and warn 'combine failed on argument: ' . $csv->error_input; >@@ -441,30 +444,54 @@ if ( defined $csvfilename ) { > } > > @branches = @overduebranches unless @branches; >+ >+# Setup output file if requested > our $fh; > if ( defined $htmlfilename ) { >- if ( $htmlfilename eq '' ) { >- $fh = *STDOUT; >- } else { >- my $today = dt_from_string(); >- open $fh, ">:encoding(UTF-8)",File::Spec->catdir ($htmlfilename,"notices-".$today->ymd().".html"); >- } >- >- print $fh _get_html_start(); >+ if ( $htmlfilename eq '' ) { >+ $fh = *STDOUT; >+ } else { >+ my $today = dt_from_string(); >+ open $fh, ">:encoding(UTF-8)", File::Spec->catdir( $htmlfilename, "notices-" . $today->ymd() . ".html" ); >+ } >+ >+ print $fh _get_html_start(); >+} elsif ( defined $text_filename ) { >+ if ( $text_filename eq '' ) { >+ $fh = *STDOUT; >+ } else { >+ my $today = dt_from_string(); >+ open $fh, ">:encoding(UTF-8)", File::Spec->catdir( $text_filename, "notices-" . $today->ymd() . ".txt" ); >+ } >+} >+ >+# Setup category list >+my @categories; >+if (@myborcat) { >+ @categories = @myborcat; >+} elsif (@myborcatout) { >+ @categories = Koha::Patron::Categories->search( { catagorycode => { 'not_in' => \@myborcatout } } ) >+ ->get_column('categorycode'); >+} else { >+ @categories = Koha::Patron::Categories->search()->get_column('categorycode'); > } >-elsif ( defined $text_filename ) { >- if ( $text_filename eq '' ) { >- $fh = *STDOUT; >- } else { >- my $today = dt_from_string(); >- open $fh, ">:encoding(UTF-8)",File::Spec->catdir ($text_filename,"notices-".$today->ymd().".txt"); >- } >+ >+# Setup itemtype list >+my @itemtypes; >+if (@myitemtypes) { >+ @itemtypes = @myitemtypes; >+} elsif (@myitemtypesout) { >+ @itemtypes = >+ Koha::ItemTypes->search( { itemtype => { 'not_in' => \@myitemtypesout } } )->get_column('itemtype'); >+} else { >+ @itemtypes = Koha::ItemTypes->search()->get_column('itemtype'); > } > > my %already_queued; > my %seen = map { $_ => 1 } @branches; > > # Work through branches >+my @output_chunks; > foreach my $branchcode (@branches) { > my $calendar; > if ( C4::Context->preference('OverdueNoticeCalendar') ) { >@@ -474,62 +501,132 @@ foreach my $branchcode (@branches) { > } > } > >- my $library = Koha::Libraries->find($branchcode); >- my $admin_email_address = $library->from_email_address; >+ my $library = Koha::Libraries->find($branchcode); >+ my $admin_email_address = $library->from_email_address; > my $branch_email_address = C4::Context->preference('AddressForFailedOverdueNotices') >- || $library->inbound_email_address; >- my @output_chunks; # may be sent to mail or stdout or csv file. >+ || $library->inbound_email_address; >+ @output_chunks = (); # may be sent to mail or stdout or csv file. > > $verbose and print "======================================\n"; > $verbose and warn sprintf "branchcode : '%s' using %s\n", $branchcode, $branch_email_address; > >- my $sql2 = <<"END_SQL"; >-SELECT biblio.*, items.*, issues.*, biblioitems.itemtype, branchname >- FROM issues,items,biblio, biblioitems, branches b >- WHERE items.itemnumber=issues.itemnumber >- AND biblio.biblionumber = items.biblionumber >- AND b.branchcode = items.homebranch >- AND biblio.biblionumber = biblioitems.biblionumber >- AND issues.borrowernumber = ? >- AND items.itemlost = 0 >+ # Work through patron categories >+ for my $category (@categories) { >+ >+ # Fetch all overdues for patron categories that want overdues and where the item is not lost and group by borrower >+ my $borrower_sql = <<"END_SQL"; >+SELECT >+ issues.borrowernumber, >+ borrowers.firstname, >+ borrowers.surname, >+ borrowers.address, >+ borrowers.address2, >+ borrowers.city, >+ borrowers.zipcode, >+ borrowers.country, >+ borrowers.email, >+ borrowers.emailpro, >+ borrowers.B_email, >+ borrowers.smsalertnumber, >+ borrowers.phone, >+ borrowers.cardnumber, >+ biblio.*, >+ biblioitems.itemtype AS b_itemtype, >+ items.*, >+ issues.*, >+ branches.branchname >+FROM >+ issues >+JOIN >+ borrowers ON issues.borrowernumber = borrowers.borrowernumber >+JOIN >+ categories ON borrowers.categorycode = categories.categorycode >+JOIN >+ items ON issues.itemnumber = items.itemnumber >+JOIN >+ biblio ON biblio.biblionumber = items.biblionumber >+JOIN >+ biblioitems ON biblio.biblionumber = biblioitems.biblionumber >+JOIN >+ branches ON branches.branchcode = items.homebranch >+WHERE >+ items.itemlost = 0 > AND TO_DAYS($date)-TO_DAYS(issues.date_due) >= 0 > END_SQL > >- if ($owning_library) { >- $sql2 .= ' AND items.homebranch = ? ' unless ($patron_homelibrary); >- } else { >- $sql2 .= ' AND issues.branchcode = ? ' unless ($patron_homelibrary); >- } >- my $sth2 = $dbh->prepare($sql2); >- >- my @categories; >- if (@myborcat) { >- @categories = @myborcat; >- } elsif (@myborcatout) { >- @categories = Koha::Patron::Categories->search( { catagorycode => { 'not_in' => \@myborcatout } } ) >- ->get_column('categorycode'); >- } else { >- @categories = Koha::Patron::Categories->search()->get_column('categorycode'); >- } >+ my @borrower_parameters; >+ if ($branchcode) { >+ if ($owning_library) { >+ $borrower_sql .= ' AND items.homebranch=? '; >+ } else { >+ $borrower_sql .= ' AND issues.branchcode=? '; >+ } >+ push @borrower_parameters, $branchcode; >+ } >+ if ($category) { >+ $borrower_sql .= ' AND borrowers.categorycode=? '; >+ push @borrower_parameters, $category; >+ } >+ if (@itemtypes) { >+ my $placeholders = join( ", ", ("?") x @itemtypes ); >+ if ( $itype_level eq 'item' ) { >+ $borrower_sql .= " AND items.itype IN ($placeholders) "; >+ } else { >+ $borrower_sql .= " AND biblioitems.itemtype IN ($placeholders) "; >+ } >+ push @borrower_parameters, @itemtypes; >+ } >+ $borrower_sql .= ' AND categories.overduenoticerequired=1 ORDER BY issues.borrowernumber'; >+ >+ # $sth gets borrower info if at least one overdue item has triggered the overdue action. >+ my $sth = $dbh->prepare($borrower_sql); >+ $sth->execute(@borrower_parameters); >+ >+ if ( $verbose > 1 ) { >+ warn sprintf "--------Borrower SQL------\n"; >+ warn $borrower_sql >+ . "\n $branchcode | " >+ . "'$category' | " >+ . join( "','", @itemtypes ) . " | " >+ . $date_to_run->datetime() . ")\n"; >+ warn sprintf "--------------------------\n"; >+ } >+ $verbose and warn sprintf "Found %s overdues for $category on $date_to_run\n", $sth->rows; > >- my @itemtypes; >- if (@myitemtypes) { >- @itemtypes = @myitemtypes; >- } elsif (@myitemtypesout) { >- @itemtypes = >- Koha::ItemTypes->search( { itemtype => { 'not_in' => \@myitemtypesout } } )->get_column('itemtype'); >- } else { >- @itemtypes = Koha::ItemTypes->search()->get_column('itemtype'); >- } >+ my $borrowernumber; >+ my $borrower_overdues = {}; > >- # Work through category and itemtype combinations to catch overdue rules for each >- for my $category (@categories) { >- for my $itemtype (@itemtypes) { >+ # Iterate over all overdues >+ OVERDUE: while ( my $data = $sth->fetchrow_hashref ) { >+ my $itemtype = $data->{itype} // $data->{b_itemtype}; >+ >+ # Collect triggers or act on them if we're switching borrower >+ if ( !defined $borrower_overdues->{borrowernumber} ) { >+ if ($verbose) { >+ warn "\n-----------------------------------------\n"; >+ warn "Collecting overdue triggers for borrower " . $data->{borrowernumber} . "\n"; >+ } >+ $borrower_overdues = { borrowernumber => $data->{borrowernumber}, branchcode => $branchcode }; >+ } elsif ( $borrower_overdues->{borrowernumber} ne $data->{borrowernumber} ) { >+ $verbose and warn "Collected overdue triggers for " . $borrower_overdues->{borrowernumber} . "\n"; >+ _enact_trigger($borrower_overdues); >+ if ($verbose) { >+ warn "\n-----------------------------------------\n"; >+ warn "Collecting overdue triggers for borrower " . $data->{borrowernumber} . "\n"; >+ } >+ $borrower_overdues = { borrowernumber => $data->{borrowernumber}, branchcode => $branchcode }; >+ } >+ >+ $verbose >+ and warn "\nProcessing overdue " >+ . $data->{issue_id} >+ . " with branch = '$branchcode', categorycode = '$category' and itemtype = '$itemtype'\n"; >+ >+ # Work through triggers until we run out of rules or find a match > my $i = 0; >- # Work through triggers until we run out of rules >- PERIOD: while (1) { >+ PERIOD: while (1) { > $i++; >- my $ii = $i + 1; >+ my $ii = $i + 1; > my $overdue_rules = Koha::CirculationRules->get_effective_rules( > { > rules => [ >@@ -541,361 +638,123 @@ END_SQL > itemtype => $itemtype, > } > ); >- $verbose and warn "branch '$branchcode', categorycode = $category, itemtype = $itemtype, pass $i\n"; > > if ( !defined( $overdue_rules->{ "overdue_$i" . '_delay' } ) ) { > last PERIOD; > } > > my $mindays = >- $overdue_rules->{ "overdue_$i" . '_delay' }; # the notice will be sent after mindays days (grace period) >+ $overdue_rules->{ "overdue_$i" . '_delay' } >+ ; # the notice will be sent after mindays days (grace period) > my $maxdays = ( > $overdue_rules->{ "overdue_$ii" . '_delay' } > ? $overdue_rules->{ "overdue_$ii" . '_delay' } - 1 > : ($MAX) >- ); # issues being more than maxdays late are managed somewhere else. (borrower probably suspended) >- >- next unless defined $mindays; >+ ); # issues being more than maxdays late are managed somewhere else. (borrower probably suspended) > >- if ( !$overdue_rules->{"overdue_$i".'_notice'} ) { >- $verbose and warn sprintf "No letter code found for pass %s\n", $i; >+ if ( !$overdue_rules->{ "overdue_$i" . '_notice' } ) { >+ $verbose and warn sprintf "Trigger %s skipped, No letter code found\n", $i; > next PERIOD; > } >- $verbose and warn sprintf "Using letter code '%s' for pass %s\n", $overdue_rules->{"overdue_$i".'_notice'}, $i; > > # $letter->{'content'} is the text of the mail that is sent. > # this text contains fields that are replaced by their value. Those fields must be written between brackets > # The following fields are available : >- # 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). >+ # 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). > # <date> <itemcount> <firstname> <lastname> <address1> <address2> <address3> <city> <postcode> <country> > >- # Fetch all overdue issues where the item is not lost and the borrower category wants overdue notices grouping by borrower >- my $borrower_sql = <<"END_SQL"; >-SELECT issues.borrowernumber, firstname, surname, address, address2, city, zipcode, country, email, emailpro, B_email, smsalertnumber, phone, cardnumber, date_due >-FROM issues,borrowers,categories,items >-WHERE issues.borrowernumber=borrowers.borrowernumber >-AND borrowers.categorycode=categories.categorycode >-AND issues.itemnumber = items.itemnumber >-AND items.itemlost = 0 >-AND TO_DAYS($date)-TO_DAYS(issues.date_due) >= 0 >-END_SQL >- my @borrower_parameters; >- if ($branchcode) { >- if($owning_library) { >- $borrower_sql .= ' AND items.homebranch=? '; >- } else { >- $borrower_sql .= ' AND issues.branchcode=? '; >- } >- push @borrower_parameters, $branchcode; >- } >- if ( $category ) { >- $borrower_sql .= ' AND borrowers.categorycode=? '; >- push @borrower_parameters, $category; >- } >- $borrower_sql .= ' AND categories.overduenoticerequired=1 ORDER BY issues.borrowernumber'; >- >- # $sth gets borrower info if at least one overdue item has triggered the overdue action. >- my $sth = $dbh->prepare($borrower_sql); >- $sth->execute(@borrower_parameters); >- >- if ( $verbose > 1 ){ >- warn sprintf "--------Borrower SQL------\n"; >- warn $borrower_sql . "\n $branchcode | " . $category . "\n ($mindays, $maxdays, ". $date_to_run->datetime() .")\n"; >- warn sprintf "--------------------------\n"; >+ # Check the overdue period matches >+ my $days_between; >+ if ( C4::Context->preference('OverdueNoticeCalendar') ) { >+ $days_between = $calendar->days_between( >+ dt_from_string( $data->{date_due} ), >+ $date_to_run >+ ); >+ } else { >+ $days_between = >+ $date_to_run->delta_days( dt_from_string( $data->{date_due} ) ); > } >- $verbose and warn sprintf "Found %s borrowers with overdues\n", $sth->rows; >- my $borrowernumber; >- # Iterate over all overdue issues >- while ( my $data = $sth->fetchrow_hashref ) { >- >- # check the borrower has at least one item that matches >- my $days_between; >- if ( C4::Context->preference('OverdueNoticeCalendar') ) >- { >- $days_between = >- $calendar->days_between( dt_from_string($data->{date_due}), >- $date_to_run ); >- } >- else { >- $days_between = >- $date_to_run->delta_days( dt_from_string($data->{date_due}) ); >+ $days_between = $days_between->in_units('days'); >+ if ($listall) { >+ unless ( $days_between >= 1 and $days_between <= $MAX ) { >+ next; > } >- $days_between = $days_between->in_units('days'); >+ } else { > if ($triggered) { > if ( $mindays != $days_between ) { >+ $verbose and warn "Overdue skipped for trigger $i\n"; > next; > } >- } >- else { >- unless ( $days_between >= $mindays >+ } else { >+ unless ( $days_between >= $mindays > && $days_between <= $maxdays ) > { >+ $verbose and warn "Overdue skipped for trigger $i\n"; > next; > } > } >- if (defined $borrowernumber && $borrowernumber eq $data->{'borrowernumber'}){ >-# we have already dealt with this borrower >- $verbose and warn "already dealt with this borrower $borrowernumber"; >- next; >- } >- $borrowernumber = $data->{'borrowernumber'}; >- >- # Skip duplicate borrowers as we'll have already handled them >- next if ( $patron_homelibrary && $already_queued{"$borrowernumber$i"} ); >+ } > >- my $borr = sprintf( "%s%s%s (%s)", >+ if ($verbose) { >+ my $borr = sprintf( >+ "%s%s%s (%s)", > $data->{'surname'} || '', > $data->{'firstname'} && $data->{'surname'} ? ', ' : '', > $data->{'firstname'} || '', >- $borrowernumber ); >- $verbose and warn "borrower $borr has items triggering level $i.\n"; >- >- my $patron = Koha::Patrons->find( $borrowernumber ); >- if ($patron_homelibrary) { >- $branchcode = $patron->branchcode; >- $library = Koha::Libraries->find($branchcode); >- $admin_email_address = $library->from_email_address; >- $branch_email_address = C4::Context->preference('AddressForFailedOverdueNotices') >- || $library->inbound_email_address; >- } >- @emails_to_use = (); >- my $notice_email = $patron->notice_email_address; >- unless ($nomail) { >- if (@emails) { >- foreach (@emails) { >- push @emails_to_use, $data->{$_} if ( $data->{$_} ); >- } >- } >- else { >- push @emails_to_use, $notice_email if ($notice_email); >- } >- } >- >- my $letter = Koha::Notice::Templates->find_effective_template( >- { >- module => 'circulation', >- code => $overdue_rules->{"overdue_$i".'_notice'}, >- branchcode => $branchcode, >- lang => $patron->lang >- } >+ $data->{borrowernumber} > ); >+ warn sprintf "Overdue matched trigger %s with delay of %s days and overdue due date of %s\n", >+ $i, >+ $triggered, $overdue_rules->{ "overdue_$i" . '_delay' }, $data->{date_due}; >+ warn sprintf "Using letter code '%s'\n", >+ $overdue_rules->{ "overdue_$i" . '_notice' }; >+ } > >- unless ($letter) { >- $verbose and warn qq|Message '$overdue_rules->{"overdue_$i"."_notice"}' content not found|; >- >- # might as well skip while PERIOD, no other borrowers are going to work. >- # FIXME : Does this mean a letter must be defined in order to trigger a debar ? >- next PERIOD; >- } >- >- if ( $overdue_rules->{"overdue_$i".'_restrict'} ) { >- >- #action taken is debarring >- AddUniqueDebarment( >- { >- borrowernumber => $borrowernumber, >- type => 'OVERDUES', >- comment => "OVERDUES_PROCESS " . output_pref( dt_from_string() ), >- } >- ) unless $test_mode; >- $verbose and warn "debarring $borr\n"; >- } >- my @params = $patron_homelibrary ? ($borrowernumber) : ( $borrowernumber, $branchcode ); >- >- $sth2->execute(@params); >- my $itemcount = 0; >- my $titles = ""; >- my @items = (); >- >- my $j = 0; >- my $exceededPrintNoticesMaxLines = 0; >- # Get each overdue item for this patron >- while ( my $item_info = $sth2->fetchrow_hashref() ) { >- if ( C4::Context->preference('OverdueNoticeCalendar') ) { >- $days_between = >- $calendar->days_between( >- dt_from_string( $item_info->{date_due} ), $date_to_run ); >- } >- else { >- $days_between = >- $date_to_run->delta_days( >- dt_from_string( $item_info->{date_due} ) ); >- } >- $days_between = $days_between->in_units('days'); >- if ($listall){ >- unless ($days_between >= 1 and $days_between <= $MAX){ >- next; >- } >- } >- else { >- if ($triggered) { >- if ( $mindays != $days_between ) { >- next; >- } >- } >- else { >- unless ( $days_between >= $mindays >- && $days_between <= $maxdays ) >- { >- next; >- } >- } >- } >- >- if ( ( scalar(@emails_to_use) == 0 || $nomail ) && $PrintNoticesMaxLines && $j >= $PrintNoticesMaxLines ) { >- $exceededPrintNoticesMaxLines = 1; >- last; >- } >- next if $patron_homelibrary and !grep { $seen{ $item_info->{branchcode} } } @branches; >- $j++; >- >- $titles .= C4::Letters::get_item_content( { item => $item_info, item_content_fields => \@item_content_fields, dateonly => 1 } ); >- $itemcount++; >- push @items, $item_info; >- } >- $sth2->finish; >- >- my @message_transport_types = split( /,/, $overdue_rules->{ "overdue_$i" . '_mtt' } ); >- >- my $print_sent = 0; # A print notice is not yet sent for this patron >- for my $mtt ( @message_transport_types ) { >- next if $mtt eq 'itiva'; >- my $effective_mtt = $mtt; >- if ( ($mtt eq 'email' and not scalar @emails_to_use) or ($mtt eq 'sms' and not $data->{smsalertnumber}) ) { >- # email or sms is requested but not exist, do a print. >- $effective_mtt = 'print'; >- } >- splice @items, $PrintNoticesMaxLines if $effective_mtt eq 'print' && $PrintNoticesMaxLines && scalar @items > $PrintNoticesMaxLines; >- #catch the case where we are sending a print to someone with an email >- >- my $letter_exists = Koha::Notice::Templates->find_effective_template( >- { >- module => 'circulation', >- code => $overdue_rules->{ "overdue_$i" . '_notice' }, >- message_transport_type => $effective_mtt, >- branchcode => $branchcode, >- lang => $patron->lang >- } >- ); >- >- my $letter = parse_overdues_letter( >- { >- letter_code => $overdue_rules->{ "overdue_$i" . '_notice' }, >- borrowernumber => $borrowernumber, >- branchcode => $branchcode, >- items => \@items, >- substitute => { # this appears to be a hack to overcome incomplete features in this code. >- bib => $library->branchname, # maybe 'bib' is a typo for 'lib<rary>'? >- 'items.content' => $titles, >- 'count' => $itemcount, >- }, >- >- # If there is no template defined for the requested letter >- # Fallback on the original type >- message_transport_type => $letter_exists ? $effective_mtt : $mtt, >- } >- ); >- unless ( $letter && $letter->{content} ) { >- $verbose and warn qq|Message '$overdue_rules->{"overdue_$i"."_notice"}' content not found|; >- >- # this transport doesn't have a configured notice, so try another >- next; >- } >- >- if ( $exceededPrintNoticesMaxLines ) { >- $letter->{'content'} .= "List too long for form; please check your account online for a complete list of your overdue items."; >- } >- >- my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); } split /\</, $letter->{'content'}; >- if (@misses) { >- $verbose and warn "The following terms were not matched and replaced: \n\t" . join "\n\t", @misses; >- } >- >- if ($nomail) { >- push @output_chunks, >- prepare_letter_for_printing( >- { letter => $letter, >- borrowernumber => $borrowernumber, >- firstname => $data->{'firstname'}, >- lastname => $data->{'surname'}, >- address1 => $data->{'address'}, >- address2 => $data->{'address2'}, >- city => $data->{'city'}, >- phone => $data->{'phone'}, >- cardnumber => $data->{'cardnumber'}, >- branchname => $library->branchname, >- letternumber => $i, >- postcode => $data->{'zipcode'}, >- country => $data->{'country'}, >- email => $notice_email, >- itemcount => $itemcount, >- titles => $titles, >- outputformat => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : defined $text_filename ? 'text' : '', >- } >- ); >- } else { >- if ( ($mtt eq 'email' and not scalar @emails_to_use) or ($mtt eq 'sms' and not $data->{smsalertnumber}) ) { >- push @output_chunks, >- prepare_letter_for_printing( >- { letter => $letter, >- borrowernumber => $borrowernumber, >- firstname => $data->{'firstname'}, >- lastname => $data->{'surname'}, >- address1 => $data->{'address'}, >- address2 => $data->{'address2'}, >- city => $data->{'city'}, >- postcode => $data->{'zipcode'}, >- country => $data->{'country'}, >- email => $notice_email, >- itemcount => $itemcount, >- titles => $titles, >- outputformat => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : defined $text_filename ? 'text' : '', >- } >- ); >- } >- unless ( $effective_mtt eq 'print' and $print_sent == 1 ) { >- # Just sent a print if not already done. >- C4::Letters::EnqueueLetter( >- { letter => $letter, >- borrowernumber => $borrowernumber, >- message_transport_type => $effective_mtt, >- from_address => $admin_email_address, >- to_address => join(',', @emails_to_use), >- reply_address => $library->inbound_email_address, >- } >- ) unless $test_mode; >- # A print notice should be sent only once per overdue level. >- # 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. >- $print_sent = 1 if $effective_mtt eq 'print'; >- } >- } >- } >- $already_queued{"$borrowernumber$i"} = 1; >+ my @message_transport_types = split( /,/, $overdue_rules->{ "overdue_$i" . '_mtt' } ); >+ for my $mtt (@message_transport_types) { >+ push @{ $borrower_overdues->{triggers}->{$i}->{ $overdue_rules->{ "overdue_$i" . '_notice' } } >+ ->{$mtt} }, $data; > } >- $sth->finish; >+ if ( $overdue_rules->{ "overdue_$i" . '_restrict' } ) { >+ $borrower_overdues->{restrict} = 1; >+ } >+ $borrower_overdues->{'email'} = $data->{'email'}; >+ $borrower_overdues->{'emailpro'} = $data->{'emailpro'}; >+ $borrower_overdues->{'B_email'} = $data->{'B_email'}; >+ $borrower_overdues->{'smsalertnumber'} = $data->{'smsalertnumber'}; >+ $borrower_overdues->{'phone'} = $data->{'phone'}; > } > } >+ $sth->finish; >+ >+ # Catch final trigger >+ if ( $borrower_overdues->{borrowernumber} ) { >+ $verbose and warn "Collected overdue triggers for " . $borrower_overdues->{borrowernumber} . "\n"; >+ _enact_trigger($borrower_overdues); >+ $borrower_overdues = {}; >+ } > } > > if (@output_chunks) { > if ( defined $csvfilename ) { > print $csv_fh @output_chunks; >- } >- elsif ( defined $htmlfilename ) { >+ } elsif ( defined $htmlfilename ) { > print $fh @output_chunks; >- } >- elsif ( defined $text_filename ) { >+ } elsif ( defined $text_filename ) { > print $fh @output_chunks; >+ } elsif ($nomail) { >+ local $, = "\f"; # pagebreak >+ print @output_chunks; > } >- elsif ($nomail){ >- local $, = "\f"; # pagebreak >- print @output_chunks; >- } >+ > # Generate the content of the csv with headers > my $content; > if ( defined $csvfilename ) { > my $delimiter = C4::Context->csv_delimiter; >- $content = join($delimiter, qw(title name surname address1 address2 zipcode city country email itemcount itemsinfo due_date issue_date)) . "\n"; >+ $content = join( >+ $delimiter, >+ qw(title name surname address1 address2 zipcode city country email itemcount itemsinfo due_date issue_date) >+ ) . "\n"; > $content .= join( "\n", @output_chunks ); > } elsif ( defined $htmlfilename ) { > $content = _get_html_start(); >@@ -907,8 +766,10 @@ END_SQL > > if ( C4::Context->preference('EmailOverduesNoEmail') ) { > my $attachment = { >- filename => defined $csvfilename ? 'attachment.csv' : defined $htmlfilename ? 'attachment.html' : 'attachment.txt', >- type => defined $htmlfilename ? 'text/html' : 'text/plain', >+ filename => defined $csvfilename ? 'attachment.csv' >+ : defined $htmlfilename ? 'attachment.html' >+ : 'attachment.txt', >+ type => defined $htmlfilename ? 'text/html' : 'text/plain', > content => $content, > }; > >@@ -918,7 +779,8 @@ END_SQL > }; > > C4::Letters::EnqueueLetter( >- { letter => $letter, >+ { >+ letter => $letter, > borrowernumber => undef, > message_transport_type => 'email', > attachments => [$attachment], >@@ -927,25 +789,252 @@ END_SQL > ) unless $test_mode; > } > } >- > } >+ > if ($csvfilename) { >+ > # note that we're not testing on $csv_fh to prevent closing > # STDOUT. > close $csv_fh; > } > > if ( defined $htmlfilename ) { >- print $fh _get_html_end(); >- close $fh; >+ print $fh _get_html_end(); >+ close $fh; > } elsif ( defined $text_filename ) { >- close $fh; >+ close $fh; > } > > =head1 INTERNAL METHODS > > These methods are internal to the operation of overdue_notices.pl. > >+=cut >+ >+sub _enact_trigger { >+ my ($borrower_overdues) = @_; >+ >+ my $borrowernumber = $borrower_overdues->{borrowernumber}; >+ my $branchcode = $borrower_overdues->{branchcode}; >+ my $patron = Koha::Patrons->find($borrowernumber); >+ my ( $library, $admin_email_address, $branch_email_address ); >+ $library = Koha::Libraries->find($branchcode); >+ >+ if ($patron_homelibrary) { >+ $branchcode = $patron->branchcode; >+ $library = Koha::Libraries->find($branchcode); >+ $admin_email_address = $library->from_email_address; >+ $branch_email_address = C4::Context->preference('AddressForFailedOverdueNotices') >+ || $library->inbound_email_address; >+ } >+ @emails_to_use = (); >+ my $notice_email = $patron->notice_email_address; >+ unless ($nomail) { >+ if (@emails) { >+ foreach (@emails) { >+ push @emails_to_use, $borrower_overdues->{$_} if ( $borrower_overdues->{$_} ); >+ } >+ } else { >+ push @emails_to_use, $notice_email if ($notice_email); >+ } >+ } >+ >+ for my $trigger ( sort keys %{ $borrower_overdues->{triggers} } ) { >+ for my $notice ( keys %{ $borrower_overdues->{triggers}->{$trigger} } ) { >+ my $print_sent = 0; # A print notice is not yet sent for this patron >+ for my $mtt ( keys %{ $borrower_overdues->{triggers}->{$trigger}->{$notice} } ) { >+ >+ next if $mtt eq 'itiva'; >+ my $effective_mtt = $mtt; >+ if ( ( $mtt eq 'email' and not scalar @emails_to_use ) >+ or ( $mtt eq 'sms' and not $borrower_overdues->{smsalertnumber} ) ) >+ { >+ # email or sms is requested but not exist, do a print. >+ $effective_mtt = 'print'; >+ } >+ >+ my $j = 0; >+ my $exceededPrintNoticesMaxLines = 0; >+ >+ # Get each overdue item for this trigger >+ my $itemcount = 0; >+ my $titles = ""; >+ my @items = (); >+ for my $item_info ( @{ $borrower_overdues->{triggers}->{$trigger}->{$notice}->{$effective_mtt} } ) { >+ if ( ( scalar(@emails_to_use) == 0 || $nomail ) >+ && $PrintNoticesMaxLines >+ && $j >= $PrintNoticesMaxLines ) >+ { >+ $exceededPrintNoticesMaxLines = 1; >+ last; >+ } >+ next if $patron_homelibrary and !grep { $seen{ $item_info->{branchcode} } } @branches; >+ $j++; >+ >+ $titles .= C4::Letters::get_item_content( >+ { item => $item_info, item_content_fields => \@item_content_fields, dateonly => 1 } ); >+ $itemcount++; >+ push @items, $item_info; >+ } >+ >+ splice @items, $PrintNoticesMaxLines >+ if $effective_mtt eq 'print' >+ && $PrintNoticesMaxLines >+ && scalar @items > $PrintNoticesMaxLines; >+ >+ #catch the case where we are sending a print to someone with an email >+ >+ my $letter_exists = Koha::Notice::Templates->find_effective_template( >+ { >+ module => 'circulation', >+ code => $notice, >+ message_transport_type => $effective_mtt, >+ branchcode => $branchcode, >+ lang => $patron->lang >+ } >+ ); >+ >+ unless ($letter_exists) { >+ $verbose and warn qq|Message '$notice' for '$effective_mtt' content not found|; >+ next; >+ } >+ >+ my $letter = parse_overdues_letter( >+ { >+ letter_code => $notice, >+ borrowernumber => $borrowernumber, >+ branchcode => $branchcode, >+ items => \@items, >+ substitute => { # this appears to be a hack to overcome incomplete features in this code. >+ bib => $library->branchname, # maybe 'bib' is a typo for 'lib<rary>'? >+ 'items.content' => $titles, >+ 'count' => $itemcount, >+ }, >+ >+ # If there is no template defined for the requested letter >+ # Fallback on the original type >+ message_transport_type => $letter_exists ? $effective_mtt : $mtt, >+ } >+ ); >+ unless ( $letter && $letter->{content} ) { >+ $verbose and warn qq|Message '$notice' content not found|; >+ >+ # this transport doesn't have a configured notice, so try another >+ next; >+ } >+ >+ if ($exceededPrintNoticesMaxLines) { >+ $letter->{'content'} .= >+ "List too long for form; please check your account online for a complete list of your overdue items."; >+ } >+ >+ my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); } split /\</, >+ $letter->{'content'}; >+ if (@misses) { >+ $verbose >+ and warn "The following terms were not matched and replaced: \n\t" . join "\n\t", >+ @misses; >+ } >+ >+ if ($nomail) { >+ push @output_chunks, >+ prepare_letter_for_printing( >+ { >+ letter => $letter, >+ borrowernumber => $borrowernumber, >+ firstname => $borrower_overdues->{'firstname'}, >+ lastname => $borrower_overdues->{'surname'}, >+ address1 => $borrower_overdues->{'address'}, >+ address2 => $borrower_overdues->{'address2'}, >+ city => $borrower_overdues->{'city'}, >+ phone => $borrower_overdues->{'phone'}, >+ cardnumber => $borrower_overdues->{'cardnumber'}, >+ branchname => $library->branchname, >+ letternumber => $trigger, >+ postcode => $borrower_overdues->{'zipcode'}, >+ country => $borrower_overdues->{'country'}, >+ email => $notice_email, >+ itemcount => $itemcount, >+ titles => $titles, >+ outputformat => defined $csvfilename ? 'csv' >+ : defined $htmlfilename ? 'html' >+ : defined $text_filename ? 'text' >+ : '', >+ } >+ ); >+ } else { >+ if ( ( $mtt eq 'email' and not scalar @emails_to_use ) >+ or ( $mtt eq 'sms' and not $borrower_overdues->{smsalertnumber} ) ) >+ { >+ push @output_chunks, >+ prepare_letter_for_printing( >+ { >+ letter => $letter, >+ borrowernumber => $borrowernumber, >+ firstname => $borrower_overdues->{'firstname'}, >+ lastname => $borrower_overdues->{'surname'}, >+ address1 => $borrower_overdues->{'address'}, >+ address2 => $borrower_overdues->{'address2'}, >+ city => $borrower_overdues->{'city'}, >+ postcode => $borrower_overdues->{'zipcode'}, >+ country => $borrower_overdues->{'country'}, >+ email => $notice_email, >+ itemcount => $itemcount, >+ titles => $titles, >+ outputformat => defined $csvfilename ? 'csv' >+ : defined $htmlfilename ? 'html' >+ : defined $text_filename ? 'text' >+ : '', >+ } >+ ); >+ } >+ unless ( $effective_mtt eq 'print' and $print_sent == 1 ) { >+ >+ # Just sent a print if not already done. >+ C4::Letters::EnqueueLetter( >+ { >+ letter => $letter, >+ borrowernumber => $borrowernumber, >+ message_transport_type => $effective_mtt, >+ from_address => $admin_email_address, >+ to_address => join( ',', @emails_to_use ), >+ reply_address => $library->inbound_email_address, >+ } >+ ) unless $test_mode; >+ >+ # A print notice should be sent only once per overdue level. >+ # 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. >+ $print_sent = 1 if $effective_mtt eq 'print'; >+ } >+ } >+ } >+ >+ $already_queued{"$borrowernumber$trigger"} = 1; >+ } >+ } >+ >+ if ( $borrower_overdues->{restrict} ) { >+ >+ #action taken is debarring >+ AddUniqueDebarment( >+ { >+ borrowernumber => $borrowernumber, >+ type => 'OVERDUES', >+ comment => "OVERDUES_PROCESS " . output_pref( dt_from_string() ), >+ } >+ ) unless $test_mode; >+ >+ my $borr = sprintf( >+ "%s%s%s (%s)", >+ $borrower_overdues->{'surname'} || '', >+ $borrower_overdues->{'firstname'} && $borrower_overdues->{'surname'} ? ', ' : '', >+ $borrower_overdues->{'firstname'} || '', >+ $borrower_overdues->{borrowernumber} >+ ); >+ $verbose and warn "debarring $borr\n"; >+ } >+} >+ > =head2 prepare_letter_for_printing > > returns a string of text appropriate for printing in the event that an >@@ -974,20 +1063,24 @@ sub prepare_letter_for_printing { > my $return; > chomp $params->{titles}; > if ( exists $params->{'outputformat'} && $params->{'outputformat'} eq 'csv' ) { >- if ($csv->combine( >- $params->{'firstname'}, $params->{'lastname'}, $params->{'address1'}, $params->{'address2'}, $params->{'postcode'}, >- $params->{'city'}, $params->{'country'}, $params->{'email'}, $params->{'phone'}, $params->{'cardnumber'}, >+ if ( >+ $csv->combine( >+ $params->{'firstname'}, $params->{'lastname'}, $params->{'address1'}, $params->{'address2'}, >+ $params->{'postcode'}, >+ $params->{'city'}, $params->{'country'}, $params->{'email'}, $params->{'phone'}, >+ $params->{'cardnumber'}, > $params->{'itemcount'}, $params->{'titles'}, $params->{'branchname'}, $params->{'letternumber'} > ) >- ) { >+ ) >+ { > return $csv->string, "\n"; > } else { > $verbose and warn 'combine failed on argument: ' . $csv->error_input; > } > } elsif ( exists $params->{'outputformat'} && $params->{'outputformat'} eq 'html' ) { >- $return = "<pre>\n"; >- $return .= "$params->{'letter'}->{'content'}\n"; >- $return .= "\n</pre>\n"; >+ $return = "<pre>\n"; >+ $return .= "$params->{'letter'}->{'content'}\n"; >+ $return .= "\n</pre>\n"; > } else { > $return .= "$params->{'letter'}->{'content'}\n"; > >@@ -1034,4 +1127,4 @@ sub _get_html_end { > > } > >-cronlogaction({ action => 'End', info => "COMPLETED" }); >+cronlogaction( { action => 'End', info => "COMPLETED" } ); >-- >2.46.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 10190
:
170357
|
170358
|
170359
|
170360
|
170361
|
170362
|
170363
|
170364
|
170365
|
170366
|
170367
|
170368
|
170369
|
170370
|
170371
|
170372
|
170373
|
170374
|
170375
|
170376
|
170380
|
170449
|
171727
|
171728
|
171729
|
171730
|
171731
|
171732
|
171733
|
171734
|
171735
|
171736
|
171737
|
171738
|
171739
|
171740
|
171741
|
171742
|
171743
|
171744
|
171745
|
171746
|
171747
|
171748
|
171749
|
171750
|
171751
|
171752
|
171753
|
171754
|
171755
|
172588
|
172589
|
172590
|
172591
|
172592
|
172593
|
172594
|
172595
|
172596
|
172597
|
172598
|
172599
|
172600
|
172601
|
172602
|
172603
|
172604
|
172605
|
172606
|
172607
|
172608
|
172609
|
172612
|
172614
|
172617
|
172619
|
172621
|
172622
|
172623
|
172624
|
172625
|
172626
|
172630
|
172631
|
172632
|
172633
|
172634
|
172635
|
172636
|
172637
|
172638
|
172639
|
172640
|
172641
|
172642
|
172643
|
172644
|
172645
|
172646
|
172647
|
172648
|
172649
|
172650
|
172651
|
172652
|
172653
|
172654
|
172655
|
172656
|
172657
|
172658
|
172659
|
172660
|
172661
|
172662
|
172663