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

(-)a/misc/cronjobs/overdue_notices.pl (-408 / +288 lines)
Lines 316-386 pod2usage( -verbose => 2 ) if $man; Link Here
316
316
317
cronlogaction();
317
cronlogaction();
318
318
319
if ( defined $csvfilename && $csvfilename =~ /^-/ ) {
319
sub VERBOSE{
320
    warn qq(using "$csvfilename" as filename, that seems odd);
320
    if ($verbose){
321
        printf STDERR "%s\n", shift;
322
    }
321
}
323
}
322
324
323
my @overduebranches    = C4::Overdues::GetBranchcodesWithOverdueRules();	# Branches with overdue rules
325
if ( defined $csvfilename && $csvfilename =~ /^-/ ) {
324
my @branches;									# Branches passed as parameter with overdue rules
326
    warn qq(using "$csvfilename" as filename, that seems odd);
325
my $branchcount = scalar(@overduebranches);
326
327
my $overduebranch_word = scalar @overduebranches > 1 ? 'branches' : 'branch';
328
my $branchcodes_word = scalar @branchcodes > 1 ? 'branches' : 'branch';
329
330
my $PrintNoticesMaxLines = C4::Context->preference('PrintNoticesMaxLines');
331
332
if ($branchcount) {
333
    $verbose and warn "Found $branchcount $overduebranch_word with first message enabled: " . join( ', ', map { "'$_'" } @overduebranches ), "\n";
334
} else {
335
    die 'No branches with active overduerules';
336
}
327
}
337
328
338
if (@branchcodes) {
339
    $verbose and warn "$branchcodes_word @branchcodes passed on parameter\n";
340
    
341
    # Getting libraries which have overdue rules
342
    my %seen = map { $_ => 1 } @branchcodes;
343
    @branches = grep { $seen{$_} } @overduebranches;
344
    
345
    
346
    if (@branches) {
347
348
    	my $branch_word = scalar @branches > 1 ? 'branches' : 'branch';
349
	$verbose and warn "$branch_word @branches have overdue rules\n";
350
351
    } else {
352
    
353
        $verbose and warn "No active overduerules for $branchcodes_word  '@branchcodes'\n";
354
        ( scalar grep { '' eq $_ } @branches )
355
          or die "No active overduerules for DEFAULT either!";
356
        $verbose and warn "Falling back on default rules for @branchcodes\n";
357
        @branches = ('');
358
    }
359
}
360
my $date_to_run;
329
my $date_to_run;
361
my $date;
330
my $date;
362
if ( $date_input ){
331
if ( $date_input ){
332
    $date = $dbh->quote($date);
363
    eval {
333
    eval {
364
        $date_to_run = dt_from_string( $date_input, 'iso' );
334
        $date_to_run = dt_from_string( $date_input );
365
    };
335
    };
366
    die "$date_input is not a valid date, aborting! Use a date in format YYYY-MM-DD."
336
    die "$date_input is not a valid date, aborting!"
367
        if $@ or not $date_to_run;
337
        if $@ or not $date_to_run;
368
338
369
    # It's certainly useless to escape $date_input
370
    # dt_from_string should not return something if $date_input is not correctly set.
371
    $date = $dbh->quote( $date_input );
372
}
339
}
373
else {
340
else {
374
    $date="NOW()";
341
    $date="NOW()";
375
    $date_to_run = dt_from_string();
342
    $date_to_run = dt_from_string();
376
}
343
}
377
344
345
##
346
## Some file/html preparation ?
347
##
348
349
my $printNoticesMaxLines = C4::Context->preference('PrintNoticesMaxLines');
350
378
# these are the fields that will be substituted into <<item.content>>
351
# these are the fields that will be substituted into <<item.content>>
379
my @item_content_fields = split( /,/, $itemscontent );
352
my @item_content_fields = split( /,/, $itemscontent );
380
353
381
binmode( STDOUT, ':encoding(UTF-8)' );
354
binmode( STDOUT, ':encoding(UTF-8)' );
382
355
383
384
our $csv;       # the Text::CSV_XS object
356
our $csv;       # the Text::CSV_XS object
385
our $csv_fh;    # the filehandle to the CSV file.
357
our $csv_fh;    # the filehandle to the CSV file.
386
if ( defined $csvfilename ) {
358
if ( defined $csvfilename ) {
Lines 392-417 if ( defined $csvfilename ) { Link Here
392
    } else {
364
    } else {
393
        open $csv_fh, ">", $csvfilename or die "unable to open $csvfilename: $!";
365
        open $csv_fh, ">", $csvfilename or die "unable to open $csvfilename: $!";
394
    }
366
    }
395
    if ( $csv->combine(qw(name surname address1 address2 zipcode city country email phone cardnumber itemcount itemsinfo branchname letternumber)) ) {
367
    if ( $csv->combine(qw(name surname address1 address2 zipcode city country email itemcount itemsinfo)) ) {
396
        print $csv_fh $csv->string, "\n";
368
        print $csv_fh $csv->string, "\n";
397
    } else {
369
    } else {
398
        $verbose and warn 'combine failed on argument: ' . $csv->error_input;
370
        VERBOSE( 'combine failed on argument: ' . $csv->error_input);
399
    }
371
    }
400
}
372
}
401
373
402
@branches = @overduebranches unless @branches;
403
our $fh;
374
our $fh;
404
if ( defined $htmlfilename ) {
375
if ( defined $htmlfilename ) {
405
  if ( $htmlfilename eq '' ) {
376
  if ( $htmlfilename eq '' ) {
406
    $fh = *STDOUT;
377
    $fh = *STDOUT;
407
  } else {
378
  } else {
408
    my $today = DateTime->now(time_zone => C4::Context->tz );
379
    my $today = DateTime->now(time_zone => C4::Context->tz );
409
    open $fh, ">:encoding(UTF-8)",File::Spec->catdir ($htmlfilename,"notices-".$today->ymd().".html");
380
    open $fh, ">",File::Spec->catdir ($htmlfilename,"notices-".$today->ymd().".html");
410
  }
381
  }
411
  
382
  
412
  print $fh "<html>\n";
383
  print $fh "<html>\n";
413
  print $fh "<head>\n";
384
  print $fh "<head>\n";
414
  print $fh "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
415
  print $fh "<style type='text/css'>\n";
385
  print $fh "<style type='text/css'>\n";
416
  print $fh "pre {page-break-after: always;}\n";
386
  print $fh "pre {page-break-after: always;}\n";
417
  print $fh "pre {white-space: pre-wrap;}\n";
387
  print $fh "pre {white-space: pre-wrap;}\n";
Lines 431-818 elsif ( defined $text_filename ) { Link Here
431
  }
401
  }
432
}
402
}
433
403
434
foreach my $branchcode (@branches) {
404
##
435
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
405
## sql queries
436
        my $calendar = Koha::Calendar->new( branchcode => $branchcode );
406
##
437
        if ( $calendar->is_holiday($date_to_run) ) {
407
my $sqlIssues = "SELECT biblio.*, items.*, issues.*, biblioitems.itemtype, TO_DAYS($date)-TO_DAYS(date_due) AS days_overdue
438
            next;
408
    FROM issues,items,biblio, biblioitems
439
        }
409
    WHERE items.itemnumber=issues.itemnumber
440
    }
441
442
    my $branch_details      = C4::Branch::GetBranchDetail($branchcode);
443
    my $admin_email_address = $branch_details->{'branchemail'}
444
      || C4::Context->preference('KohaAdminEmailAddress');
445
    my @output_chunks;    # may be sent to mail or stdout or csv file.
446
447
    $verbose and warn sprintf "branchcode : '%s' using %s\n", $branchcode, $admin_email_address;
448
449
    my $sth2 = $dbh->prepare( <<"END_SQL" );
450
SELECT biblio.*, items.*, issues.*, biblioitems.itemtype, TO_DAYS($date)-TO_DAYS(date_due) AS days_overdue, branchname
451
  FROM issues,items,biblio, biblioitems, branches b
452
  WHERE items.itemnumber=issues.itemnumber
453
    AND biblio.biblionumber   = items.biblionumber
410
    AND biblio.biblionumber   = items.biblionumber
454
    AND b.branchcode = items.homebranch
455
    AND biblio.biblionumber   = biblioitems.biblionumber
411
    AND biblio.biblionumber   = biblioitems.biblionumber
456
    AND issues.borrowernumber = ?
412
    AND TO_DAYS($date)-TO_DAYS(date_due) BETWEEN 1 AND $MAX ";
457
END_SQL
413
458
414
my $sqlBorrowers = "SELECT issues.borrowernumber, firstname, surname, address, address2, city, zipcode,
459
    my $query = "SELECT * FROM overduerules WHERE delay1 IS NOT NULL AND branchcode = ? ";
415
                           country, email, emailpro, B_email, smsalertnumber, borrowers.categorycode
460
    $query .= " AND categorycode IN (".join( ',' , ('?') x @myborcat ).") " if (@myborcat);
416
    FROM issues,borrowers,categories
461
    $query .= " AND categorycode NOT IN (".join( ',' , ('?') x @myborcatout ).") " if (@myborcatout);
417
    WHERE issues.borrowernumber=borrowers.borrowernumber
462
    
418
    AND borrowers.categorycode=categories.categorycode
463
    my $rqoverduerules =  $dbh->prepare($query);
419
    AND TO_DAYS($date)-TO_DAYS(date_due) BETWEEN 1 AND $MAX
464
    $rqoverduerules->execute($branchcode, @myborcat, @myborcatout);
420
    AND categories.overduenoticerequired=1 ";
465
    
421
466
    # We get default rules is there is no rule for this branch
422
my $sqlRules = "SELECT *
467
    if($rqoverduerules->rows == 0){
423
    FROM overduerules
468
        $query = "SELECT * FROM overduerules WHERE delay1 IS NOT NULL AND branchcode = '' ";
424
    WHERE delay != 0
469
        $query .= " AND categorycode IN (".join( ',' , ('?') x @myborcat ).") " if (@myborcat);
425
    AND letter <> ''
470
        $query .= " AND categorycode NOT IN (".join( ',' , ('?') x @myborcatout ).") " if (@myborcatout);
426
    ORDER BY branchcode desc, categorycode desc, itemtype desc, onhold desc, delay desc";
471
        
427
472
        $rqoverduerules = $dbh->prepare($query);
428
my $sqlMTT = "SELECT message_transport_type from overduerules_transport_types where overduerules_id = ?";
473
        $rqoverduerules->execute(@myborcat, @myborcatout);
429
474
    }
430
my $sqlReserves = "SELECT distinct(biblionumber) FROM reserves";
475
431
476
    # my $outfile = 'overdues_' . ( $mybranch || $branchcode || 'default' );
432
##
477
    while ( my $overdue_rules = $rqoverduerules->fetchrow_hashref ) {
433
## customized depending on parameters
478
      PERIOD: foreach my $i ( 1 .. 3 ) {
434
##
479
435
if(@branchcodes){
480
            $verbose and warn "branch '$branchcode', categorycode = $overdue_rules->{categorycode} pass $i\n";
436
    $sqlIssues .= " AND issues.branchcode IN ('" . join ("','", @branchcodes) . "')";
437
}
438
if(@myborcat){
439
    $sqlBorrowers .= " AND borrower.categorycode IN ('" . join ("','", @myborcat) . "')";
440
}
441
if(@myborcatout){
442
    $sqlBorrowers .= " AND borrower.categorycode NOT IN ('" . join ("','", @myborcatout) . "')";
443
}
481
444
482
            my $mindays = $overdue_rules->{"delay$i"};    # the notice will be sent after mindays days (grace period)
445
##
483
            my $maxdays = (
446
## collect everything in meaningful hashes
484
                  $overdue_rules->{ "delay" . ( $i + 1 ) }
447
##
485
                ? $overdue_rules->{ "delay" . ( $i + 1 ) } - 1
448
my $hrBorrowers = $dbh->selectall_hashref($sqlBorrowers, 'borrowernumber');
486
                : ($MAX)
449
VERBOSE("$sqlBorrowers\nReturned " . (scalar keys $hrBorrowers) . " borrowers with issues.");
487
            );                                            # issues being more than maxdays late are managed somewhere else. (borrower probably suspended)
450
my $hrOverdueIssues = $dbh->selectall_hashref($sqlIssues, 'itemnumber' );
451
VERBOSE("$sqlIssues\nReturned " . (scalar keys $hrOverdueIssues) . " overdue issues.");
452
my $arRules = $dbh->selectall_arrayref($sqlRules, { Slice => {} } );
453
VERBOSE("\nRule order:\nId\tBranch\tCat\tItem\tHold\tDelay\tDebarred\tLetter");
454
foreach my $rule (@$arRules){
455
    $rule->{mtt} = $dbh->selectcol_arrayref($sqlMTT, undef, $rule->{overduerules_id});
456
    VERBOSE("$rule->{id}\t$rule->{branchcode}\t$rule->{categorycode}\t$rule->{itemtype}\t$rule->{onhold}\t$rule->{delay}\t$rule->{debarred}\t$rule->{letter}");
457
}
458
my $hrReserves = $dbh->selectall_hashref($sqlReserves, 'biblionumber');
459
VERBOSE("$sqlReserves\nReturned " . (scalar keys $hrReserves) . " unique reserves.");
460
my %hBranches;
461
my %hChunks; # messages to send once per branch to the administrator
462
463
##
464
## For each issue, determine the most meaningful rule that applies
465
## and associate it to the borrower
466
##
467
468
my $whichItemType = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
469
foreach my $in (keys $hrOverdueIssues){
470
    my $issue = $hrOverdueIssues->{$in};
471
    VERBOSE("Processing issue $in.  branchcode: $issue->{branchcode}, borrower: $issue->{borrowernumber}.");
472
    foreach my $rule(@$arRules){
473
        if( ($rule->{branchcode} eq '' || $rule->{branchcode} eq $issue->{branchcode}) &&
474
            ($rule->{categorycode} eq '' || $rule->{categorycode} eq $hrBorrowers->{$issue->{borrowernumber}}->{categorycode}) &&
475
            ($rule->{itemtype} eq '' || $rule->{itemtype} eq $issue->{$whichItemType}) &&
476
            (!$rule->{onhold} || defined $hrReserves->{$issue->{biblionumber}})
477
          )
478
        {
479
            # limit the creation of calendar objects
480
            if(!defined $hBranches{$issue->{branchcode}}){
481
                $hBranches{$issue->{branchcode}} = C4::Branch::GetBranchDetail($issue->{branchcode});
482
                $hBranches{$issue->{branchcode}}{CALENDAR} = Koha::Calendar->new( branchcode => $issue->{branchcode} );
483
            }
488
484
489
            next unless defined $mindays;
485
            # validate number of days overdue vs delay
486
            if ( C4::Context->preference('OverdueNoticeCalendar') ){
487
                my $days_between = $hBranches{$issue->{branchcode}}{CALENDAR}->days_between( dt_from_string($issue->{date_due}), $date_to_run );
488
                $issue->{days_overdue} = $days_between->in_units('days');
489
            }
490
            next if ( $triggered && $rule->{delay} != $issue->{days_overdue}); # on "triggered". we only look for exact matches
491
            next if (!$triggered && $rule->{delay} >  $issue->{days_overdue});
490
492
491
            if ( !$overdue_rules->{"letter$i"} ) {
493
            # validate the letter
492
                $verbose and warn "No letter$i code for branch '$branchcode'";
494
            unless(C4::Letters::getletter( 'circulation', $rule->{letter}, $issue->{branchcode} )){
493
                next PERIOD;
495
                VERBOSE("Message '$rule->{letter}' content not found");
496
                last; # if no letter
494
            }
497
            }
495
498
496
            # $letter->{'content'} is the text of the mail that is sent.
499
            # we found the best rule to apply, let's store the branchcode/lettter combo into the borrower
497
            # this text contains fields that are replaced by their value. Those fields must be written between brackets
500
            my $arItems = $hrBorrowers->{$issue->{borrowernumber}}{NOTICES}{$issue->{branchcode}}{$rule->{letter}}{ITEMS} ||= [];
498
            # The following fields are available :
501
            push $arItems, $issue->{itemnumber};
499
	    # 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).
502
            # And note that we union the transport types, so IF the user set two similar alerts (except for delay and mtt), then
500
            # <date> <itemcount> <firstname> <lastname> <address1> <address2> <address3> <city> <postcode> <country>
503
            # only one message will be sent to each of the transport specified by both rules together
501
504
            my $hrMtts = $hrBorrowers->{$issue->{borrowernumber}}{NOTICES}{$issue->{branchcode}}{$rule->{letter}}{MTTS} ||= {};
502
            my $borrower_sql = <<'END_SQL';
505
            foreach my $mtt(@{$rule->{mtt}}){
503
SELECT issues.borrowernumber, firstname, surname, address, address2, city, zipcode, country, email, emailpro, B_email, smsalertnumber, phone, cardnumber,
506
                $hrMtts->{$mtt} = 1;
504
TO_DAYS(?)-TO_DAYS(date_due) as difference, date_due
505
FROM   issues,borrowers,categories
506
WHERE  issues.borrowernumber=borrowers.borrowernumber
507
AND    borrowers.categorycode=categories.categorycode
508
END_SQL
509
            my @borrower_parameters;
510
            push @borrower_parameters, $date_to_run->datetime();
511
            if ($branchcode) {
512
                $borrower_sql .= ' AND issues.branchcode=? ';
513
                push @borrower_parameters, $branchcode;
514
            }
507
            }
515
            if ( $overdue_rules->{categorycode} ) {
508
            VERBOSE("User $hrBorrowers->{$issue->{borrowernumber}}{surname}, Branch $issue->{branchcode}, Letter $rule->{letter} : Item $issue->{itemnumber}");
516
                $borrower_sql .= ' AND borrowers.categorycode=? ';
509
517
                push @borrower_parameters, $overdue_rules->{categorycode};
510
            # block the user if needed
511
            if ( $rule->{"debarred"} ) {
512
                AddUniqueDebarment({
513
                    borrowernumber => $issue->{borrowernumber},
514
                    type           => 'OVERDUES',
515
                    comment => "Restriction added by overdues process " . output_pref( dt_from_string() ),
516
                });
517
                VERBOSE("debarring user $issue->{borrowernumber} (" . $hrBorrowers->{$issue->{borrowernumber}}{surname} . ")");
518
            }
518
            }
519
            $borrower_sql .= '  AND categories.overduenoticerequired=1 ORDER BY issues.borrowernumber';
519
            last; # we found a rule, let's move to the next issue
520
520
        }
521
            # $sth gets borrower info iff at least one overdue item has triggered the overdue action.
521
    }
522
	        my $sth = $dbh->prepare($borrower_sql);
522
}
523
            $sth->execute(@borrower_parameters);
524
525
            $verbose and warn $borrower_sql . "\n $branchcode | " . $overdue_rules->{'categorycode'} . "\n ($mindays, $maxdays, ".  $date_to_run->datetime() .")\nreturns " . $sth->rows . " rows";
526
            my $borrowernumber;
527
            while ( my $data = $sth->fetchrow_hashref ) {
528
529
                next unless ( DateTime->compare( $date_to_run, dt_from_string($data->{date_due})) ) == 1;
530
531
                # check the borrower has at least one item that matches
532
                my $days_between;
533
                if ( C4::Context->preference('OverdueNoticeCalendar') )
534
                {
535
                    my $calendar =
536
                      Koha::Calendar->new( branchcode => $branchcode );
537
                    $days_between =
538
                      $calendar->days_between( dt_from_string($data->{date_due}),
539
                        $date_to_run );
540
                }
541
                else {
542
                    $days_between =
543
                      $date_to_run->delta_days( dt_from_string($data->{date_due}) );
544
                }
545
                $days_between = $days_between->in_units('days');
546
                if ($triggered) {
547
                    if ( $mindays != $days_between ) {
548
                        next;
549
                    }
550
                }
551
                else {
552
                    unless (   $days_between >= $mindays
553
                        && $days_between <= $maxdays )
554
                    {
555
                        next;
556
                    }
557
                }
558
                if (defined $borrowernumber && $borrowernumber eq $data->{'borrowernumber'}){
559
# we have already dealt with this borrower
560
                    $verbose and warn "already dealt with this borrower $borrowernumber";
561
                    next;
562
                }
563
                $borrowernumber = $data->{'borrowernumber'};
564
                my $borr =
565
                    $data->{'firstname'} . ', '
566
                  . $data->{'surname'} . ' ('
567
                  . $borrowernumber . ')';
568
                $verbose
569
                  and warn "borrower $borr has items triggering level $i.";
570
571
                @emails_to_use = ();
572
                my $notice_email =
573
                    C4::Members::GetNoticeEmailAddress($borrowernumber);
574
                unless ($nomail) {
575
                    if (@emails) {
576
                        foreach (@emails) {
577
                            push @emails_to_use, $data->{$_} if ( $data->{$_} );
578
                        }
579
                    }
580
                    else {
581
                        push @emails_to_use, $notice_email if ($notice_email);
582
                    }
583
                }
584
523
585
                my $letter = C4::Letters::getletter( 'circulation', $overdue_rules->{"letter$i"}, $branchcode );
524
##
525
## Main loop
526
##
527
VERBOSE("Main loop.");
528
foreach my $borrowernumber(keys $hrBorrowers){
529
    if(!defined $hrBorrowers->{$borrowernumber}{NOTICES}){
530
        VERBOSE("User $hrBorrowers->{$borrowernumber}{surname} has no notice.");
531
        next;
532
    }
533
    # for each branch
534
    foreach my $branchcode (keys $hrBorrowers->{$borrowernumber}{NOTICES}){
535
        my $admin_email_address = $hBranches{$branchcode}{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
536
        if($hBranches{$branchcode}{CALENDAR}->is_holiday($date_to_run)){
537
            VERBOSE("$date_to_run is a holiday at branch $branchcode");
538
            next;
539
        }
586
540
587
                unless ($letter) {
541
        # for each letter
588
                    $verbose and warn qq|Message '$overdue_rules->{"letter$i"}' content not found|;
542
        foreach my $lettercode (keys $hrBorrowers->{$borrowernumber}{NOTICES}{$branchcode}){
543
            @emails_to_use = ();
544
            my $notice_email = C4::Members::GetNoticeEmailAddress($borrowernumber);
545
            unless ($nomail) {
546
                if (@emails) {
547
                    @emails_to_use = @emails;
548
  #              foreach (@emails) {
549
#                    push @emails_to_use, $data->{$_} if ( $data->{$_} );
550
 #               }
551
                }else{
552
                    push @emails_to_use, $notice_email if ($notice_email);
553
                }
554
            }
589
555
590
                    # might as well skip while PERIOD, no other borrowers are going to work.
556
            # finally, the items
591
                    # FIXME : Does this mean a letter must be defined in order to trigger a debar ?
557
            my $arItems = $hrBorrowers->{$borrowernumber}{NOTICES}{$branchcode}{$lettercode}{ITEMS};
592
                    next PERIOD;
558
            if(!defined $arItems){
559
                VERBOSE("No item for borrower $borrowernumber");
560
                next;
561
            }
562
            my $itemcount = 0;
563
            my $titles = "";
564
            my @items = ();
565
566
            my $j = 0;
567
            my $exceededPrintNoticesMaxLines = 0;
568
            foreach my $itemnumber(@{$arItems}){
569
                if ( ( scalar(@emails_to_use) == 0 || $nomail ) && $printNoticesMaxLines && $j >= $printNoticesMaxLines ) {
570
                    $exceededPrintNoticesMaxLines = 1;
571
                    last;
593
                }
572
                }
594
    
573
                $j++;
595
                if ( $overdue_rules->{"debarred$i"} ) {
574
                my $item = $hrOverdueIssues->{$itemnumber};
596
    
575
                my @item_info = map { $_ =~ /^date|date$/ ? format_date( $item->{$_} ) : $item->{$_} || '' } @item_content_fields;
597
                    #action taken is debarring
576
                $titles .= join("\t", @item_info) . "\n";
598
                    AddUniqueDebarment(
577
                $itemcount++;
599
                        {
578
                push @items, $item;
600
                            borrowernumber => $borrowernumber,
579
            }
601
                            type           => 'OVERDUES',
580
602
                            comment => "Restriction added by overdues process "
581
            # the transport types (email, sms...)
603
                              . output_pref( dt_from_string() ),
582
            my @message_transport_types = keys $hrBorrowers->{$borrowernumber}{NOTICES}{$branchcode}{$lettercode}{MTTS};
604
                        }
583
            my $print_sent = 0; # A print notice is not yet sent for this patron
605
                    );
584
            for my $mtt ( @message_transport_types ) {
606
                    $verbose and warn "debarring $borr\n";
585
                my $letter = parse_letter( {
586
                    letter_code     => $lettercode,
587
                    borrowernumber  => $borrowernumber,
588
                    branchcode      => $branchcode,
589
                    items           => \@items,
590
                    substitute      => {    # this appears to be a hack to overcome incomplete features in this code.
591
                                        bib             => $hBranches{$branchcode}{'branchname'}, # maybe 'bib' is a typo for 'lib<rary>'?
592
                                        'items.content' => $titles,
593
                                        'count'         => $itemcount,
594
                                       },
595
                    message_transport_type => $mtt,
596
                } );
597
                unless ($letter) {
598
                    VERBOSE("Message '$lettercode' content not found (transport $mtt, branch $branchcode)");
599
                    next;
607
                }
600
                }
608
                my @params = ($borrowernumber);
609
                $verbose and warn "STH2 PARAMS: borrowernumber = $borrowernumber";
610
611
                $sth2->execute(@params);
612
                my $itemcount = 0;
613
                my $titles = "";
614
                my @items = ();
615
                
616
                my $j = 0;
617
                my $exceededPrintNoticesMaxLines = 0;
618
                while ( my $item_info = $sth2->fetchrow_hashref() ) {
619
                    next unless ( DateTime->compare( $date_to_run,  dt_from_string($item_info->{date_due})) ) == 1;
620
621
                    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
622
                        my $calendar =
623
                          Koha::Calendar->new( branchcode => $branchcode );
624
                        $days_between =
625
                          $calendar->days_between(
626
                            dt_from_string( $item_info->{date_due} ), $date_to_run );
627
                    }
628
                    else {
629
                        $days_between =
630
                          $date_to_run->delta_days(
631
                            dt_from_string( $item_info->{date_due} ) );
632
                    }
633
                    $days_between = $days_between->in_units('days');
634
                    if ($listall){
635
                        unless ($days_between >= 1 and $days_between <= $MAX){
636
                            next;
637
                        }
638
                    }
639
                    else {
640
                        if ($triggered) {
641
                            if ( $mindays != $days_between ) {
642
                                next;
643
                            }
644
                        }
645
                        else {
646
                            unless ( $days_between >= $mindays
647
                                && $days_between <= $maxdays )
648
                            {
649
                                next;
650
                            }
651
                        }
652
                    }
653
601
654
                    if ( ( scalar(@emails_to_use) == 0 || $nomail ) && $PrintNoticesMaxLines && $j >= $PrintNoticesMaxLines ) {
602
                if ( $exceededPrintNoticesMaxLines ) {
655
                      $exceededPrintNoticesMaxLines = 1;
603
                    $letter->{'content'} .= "List too long for form; please check your account online for a complete list of your overdue items.";
656
                      last;
657
                    }
658
                    $j++;
659
                    my @item_info = map { $_ =~ /^date|date$/ ?
660
                                           eval { output_pref( { dt => dt_from_string( $item_info->{$_} ), dateonly => 1 } ); }
661
                                           :
662
                                           $item_info->{$_} || '' } @item_content_fields;
663
                    $titles .= join("\t", @item_info) . "\n";
664
                    $itemcount++;
665
                    push @items, $item_info;
666
                }
604
                }
667
                $sth2->finish;
668
669
                my @message_transport_types = @{ GetOverdueMessageTransportTypes( $branchcode, $overdue_rules->{categorycode}, $i) };
670
                @message_transport_types = @{ GetOverdueMessageTransportTypes( q{}, $overdue_rules->{categorycode}, $i) }
671
                    unless @message_transport_types;
672
673
674
                my $print_sent = 0; # A print notice is not yet sent for this patron
675
                for my $mtt ( @message_transport_types ) {
676
677
                    my $letter = parse_overdues_letter(
678
                        {   letter_code     => $overdue_rules->{"letter$i"},
679
                            borrowernumber  => $borrowernumber,
680
                            branchcode      => $branchcode,
681
                            items           => \@items,
682
                            substitute      => {    # this appears to be a hack to overcome incomplete features in this code.
683
                                                bib             => $branch_details->{'branchname'}, # maybe 'bib' is a typo for 'lib<rary>'?
684
                                                'items.content' => $titles,
685
                                                'count'         => $itemcount,
686
                                               },
687
                            message_transport_type => $mtt,
688
                        }
689
                    );
690
                    unless ($letter) {
691
                        $verbose and warn qq|Message '$overdue_rules->{"letter$i"}' content not found|;
692
                        # this transport doesn't have a configured notice, so try another
693
                        next;
694
                    }
695
605
696
                    if ( $exceededPrintNoticesMaxLines ) {
606
                my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); } split /\</, $letter->{'content'};
697
                      $letter->{'content'} .= "List too long for form; please check your account online for a complete list of your overdue items.";
607
                if (@misses) {
698
                    }
608
                    VERBOSE( "The following terms were not matched and replaced: \n\t" . join "\n\t", @misses );
609
                }
699
610
700
                    my @misses = grep { /./ } map { /^([^>]*)[>]+/; ( $1 || '' ); } split /\</, $letter->{'content'};
611
                if ($nomail) {
701
                    if (@misses) {
612
                    $hChunks{$admin_email_address} ||= [];
702
                        $verbose and warn "The following terms were not matched and replaced: \n\t" . join "\n\t", @misses;
613
                    push $hChunks{$admin_email_address}, prepare_letter_for_printing( {
614
                        letter         => $letter,
615
                        borrowernumber => $borrowernumber,
616
                        firstname      => $hrBorrowers->{$borrowernumber}->{'firstname'},
617
                        lastname       => $hrBorrowers->{$borrowernumber}->{'surname'},
618
                        address1       => $hrBorrowers->{$borrowernumber}->{'address'},
619
                        address2       => $hrBorrowers->{$borrowernumber}->{'address2'},
620
                        city           => $hrBorrowers->{$borrowernumber}->{'city'},
621
                        postcode       => $hrBorrowers->{$borrowernumber}->{'zipcode'},
622
                        country        => $hrBorrowers->{$borrowernumber}->{'country'},
623
                        email          => $notice_email,
624
                        itemcount      => $itemcount,
625
                        titles         => $titles,
626
                        outputformat   => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : defined $text_filename ? 'text' : '',
627
                    } );
628
                } else {
629
                    if ( ($mtt eq 'email' and not scalar @emails_to_use) or ($mtt eq 'sms' and not $hrBorrowers->{$borrowernumber}->{smsalertnumber}) ) {
630
                        # email or sms is requested but not exist, do a print.
631
                        $mtt = 'print';
703
                    }
632
                    }
704
633
                    unless ( $mtt eq 'print' and $print_sent == 1 ) {
705
                    if ($nomail) {
634
                        # Just sent a print if not already done.
706
                        push @output_chunks,
635
                        C4::Letters::EnqueueLetter(
707
                          prepare_letter_for_printing(
636
                            {   letter                 => $letter,
708
                          {   letter         => $letter,
637
                                borrowernumber         => $borrowernumber,
709
                              borrowernumber => $borrowernumber,
638
                                message_transport_type => $mtt,
710
                              firstname      => $data->{'firstname'},
639
                                from_address           => $admin_email_address,
711
                              lastname       => $data->{'surname'},
640
                                to_address             => join(',', @emails_to_use),
712
                              address1       => $data->{'address'},
713
                              address2       => $data->{'address2'},
714
                              city           => $data->{'city'},
715
                              phone          => $data->{'phone'},
716
                              cardnumber     => $data->{'cardnumber'},
717
                              branchname     => $branch_details->{'branchname'},
718
                              letternumber   => $i,
719
                              postcode       => $data->{'zipcode'},
720
                              country        => $data->{'country'},
721
                              email          => $notice_email,
722
                              itemcount      => $itemcount,
723
                              titles         => $titles,
724
                              outputformat   => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : defined $text_filename ? 'text' : '',
725
                            }
641
                            }
726
                          );
642
                        );
727
                    } else {
643
                        # A print notice should be sent only once per overdue level.
728
                        if ( ($mtt eq 'email' and not scalar @emails_to_use) or ($mtt eq 'sms' and not $data->{smsalertnumber}) ) {
644
                        # 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.
729
                            # email or sms is requested but not exist, do a print.
645
                        $print_sent = 1 if $mtt eq 'print';
730
                            $mtt = 'print';
731
                            push @output_chunks,
732
                              prepare_letter_for_printing(
733
                              {   letter         => $letter,
734
                                  borrowernumber => $borrowernumber,
735
                                  firstname      => $data->{'firstname'},
736
                                  lastname       => $data->{'surname'},
737
                                  address1       => $data->{'address'},
738
                                  address2       => $data->{'address2'},
739
                                  city           => $data->{'city'},
740
                                  postcode       => $data->{'zipcode'},
741
                                  country        => $data->{'country'},
742
                                  email          => $notice_email,
743
                                  itemcount      => $itemcount,
744
                                  titles         => $titles,
745
                                  outputformat   => defined $csvfilename ? 'csv' : defined $htmlfilename ? 'html' : defined $text_filename ? 'text' : '',
746
                                }
747
                              );
748
                        }
749
                        unless ( $mtt eq 'print' and $print_sent == 1 ) {
750
                            # Just sent a print if not already done.
751
                            C4::Letters::EnqueueLetter(
752
                                {   letter                 => $letter,
753
                                    borrowernumber         => $borrowernumber,
754
                                    message_transport_type => $mtt,
755
                                    from_address           => $admin_email_address,
756
                                    to_address             => join(',', @emails_to_use),
757
                                }
758
                            );
759
                            # A print notice should be sent only once per overdue level.
760
                            # 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.
761
                            $print_sent = 1 if $mtt eq 'print';
762
                        }
763
                    }
646
                    }
764
                }
647
                }
765
            }
648
            }
766
            $sth->finish;
767
        }
649
        }
768
    }
650
    }
651
}
769
652
770
    if (@output_chunks) {
653
foreach my $admin_email_address (keys %hChunks) {
771
        if ( defined $csvfilename ) {
654
    my @output_chunks = @{$hChunks{$admin_email_address}};
772
            print $csv_fh @output_chunks;        
655
    if ( defined $csvfilename ) {
773
        }
656
        print $csv_fh @output_chunks;
774
        elsif ( defined $htmlfilename ) {
657
    }
775
            print $fh @output_chunks;        
658
    elsif ( defined $htmlfilename ) {
776
        }
659
        print $fh @output_chunks;
777
        elsif ( defined $text_filename ) {
660
    }
778
            print $fh @output_chunks;        
661
    elsif ( defined $text_filename ) {
779
        }
662
        print $fh @output_chunks;
780
        elsif ($nomail){
663
    }
781
                local $, = "\f";    # pagebreak
664
    elsif ($nomail){
782
                print @output_chunks;
665
        local $, = "\f";    # pagebreak
783
        }
666
        print @output_chunks;
784
        # Generate the content of the csv with headers
667
    }
785
        my $content;
668
    # Generate the content of the csv with headers
786
        if ( defined $csvfilename ) {
669
    my $content;
787
            my $delimiter = C4::Context->preference('delimiter') || ';';
670
    if ( defined $csvfilename ) {
788
            $content = join($delimiter, qw(title name surname address1 address2 zipcode city country email itemcount itemsinfo due_date issue_date)) . "\n";
671
        my $delimiter = C4::Context->preference('delimiter') || ';';
789
        }
672
        $content = join($delimiter, qw(title name surname address1 address2 zipcode city country email itemcount itemsinfo due_date issue_date)) . "\n";
790
        else {
791
            $content = "";
792
        }
793
        $content .= join( "\n", @output_chunks );
794
795
        my $attachment = {
796
            filename => defined $csvfilename ? 'attachment.csv' : 'attachment.txt',
797
            type => 'text/plain',
798
            content => $content, 
799
        };
800
801
        my $letter = {
802
            title   => 'Overdue Notices',
803
            content => 'These messages were not sent directly to the patrons.',
804
        };
805
        C4::Letters::EnqueueLetter(
806
            {   letter                 => $letter,
807
                borrowernumber         => undef,
808
                message_transport_type => 'email',
809
                attachments            => [$attachment],
810
                to_address             => $admin_email_address,
811
            }
812
        );
813
    }
673
    }
674
    else {
675
        $content = "";
676
    }
677
    $content .= join( "\n", @output_chunks );
678
679
    my $attachment = {
680
        filename => defined $csvfilename ? 'attachment.csv' : 'attachment.txt',
681
        type => 'text/plain',
682
        content => $content,
683
    };
814
684
685
    my $letter = {
686
        title   => 'Overdue Notices',
687
        content => 'These messages were not sent directly to the patrons.',
688
    };
689
    C4::Letters::EnqueueLetter( {
690
        letter                 => $letter,
691
        borrowernumber         => undef,
692
        message_transport_type => 'email',
693
        attachments            => [$attachment],
694
        to_address             => $admin_email_address,
695
    } );
815
}
696
}
697
816
if ($csvfilename) {
698
if ($csvfilename) {
817
    # note that we're not testing on $csv_fh to prevent closing
699
    # note that we're not testing on $csv_fh to prevent closing
818
    # STDOUT.
700
    # STDOUT.
Lines 820-830 if ($csvfilename) { Link Here
820
}
702
}
821
703
822
if ( defined $htmlfilename ) {
704
if ( defined $htmlfilename ) {
823
  print $fh "</body>\n";
705
    print $fh "</body>\n";
824
  print $fh "</html>\n";
706
    print $fh "</html>\n";
825
  close $fh;
707
    close $fh;
826
} elsif ( defined $text_filename ) {
708
} elsif ( defined $text_filename ) {
827
  close $fh;
709
    close $fh;
828
}
710
}
829
711
830
=head1 INTERNAL METHODS
712
=head1 INTERNAL METHODS
Lines 861-868 sub prepare_letter_for_printing { Link Here
861
    if ( exists $params->{'outputformat'} && $params->{'outputformat'} eq 'csv' ) {
743
    if ( exists $params->{'outputformat'} && $params->{'outputformat'} eq 'csv' ) {
862
        if ($csv->combine(
744
        if ($csv->combine(
863
                $params->{'firstname'}, $params->{'lastname'}, $params->{'address1'},  $params->{'address2'}, $params->{'postcode'},
745
                $params->{'firstname'}, $params->{'lastname'}, $params->{'address1'},  $params->{'address2'}, $params->{'postcode'},
864
                $params->{'city'}, $params->{'country'}, $params->{'email'}, $params->{'phone'}, $params->{'cardnumber'},
746
                $params->{'city'}, $params->{'country'}, $params->{'email'}, $params->{'itemcount'}, $params->{'titles'}
865
                $params->{'itemcount'}, $params->{'titles'}, $params->{'branchname'}, $params->{'letternumber'}
866
            )
747
            )
867
          ) {
748
          ) {
868
            return $csv->string, "\n";
749
            return $csv->string, "\n";
869
- 

Return to bug 12772