Bug 21738: make call of CanBookBeReserved more safe
[koha.git] / C4 / Overdues.pm
index 40f49a7..98e7bbf 100644 (file)
@@ -34,8 +34,10 @@ use C4::Accounts;
 use C4::Log; # logaction
 use C4::Debug;
 use Koha::DateUtils;
 use C4::Log; # logaction
 use C4::Debug;
 use Koha::DateUtils;
-use Koha::Account::Line;
 use Koha::Account::Lines;
 use Koha::Account::Lines;
+use Koha::Account::Offsets;
+use Koha::IssuingRules;
+use Koha::Libraries;
 
 use vars qw(@ISA @EXPORT);
 
 
 use vars qw(@ISA @EXPORT);
 
@@ -48,15 +50,10 @@ BEGIN {
       &CalcFine
       &Getoverdues
       &checkoverdues
       &CalcFine
       &Getoverdues
       &checkoverdues
-      &NumberNotifyId
-      &AmountNotify
       &UpdateFine
       &GetFine
       &get_chargeable_units
       &UpdateFine
       &GetFine
       &get_chargeable_units
-      &CheckItemNotify
       &GetOverduesForBranch
       &GetOverduesForBranch
-      &RemoveNotifyLine
-      &AddNotifyLine
       &GetOverdueMessageTransportTypes
       &parse_overdues_letter
     );
       &GetOverdueMessageTransportTypes
       &parse_overdues_letter
     );
@@ -72,14 +69,6 @@ BEGIN {
     push @EXPORT, qw(
       &GetIssuesIteminfo
     );
     push @EXPORT, qw(
       &GetIssuesIteminfo
     );
-
-    # &GetIssuingRules - delete.
-    # use C4::Circulation::GetIssuingRule instead.
-
-    # subs to move to Biblio.pm
-    push @EXPORT, qw(
-      &GetItems
-    );
 }
 
 =head1 NAME
 }
 
 =head1 NAME
@@ -159,7 +148,6 @@ Returns a count and a list of overdueitems for a given borrowernumber
 
 sub checkoverdues {
     my $borrowernumber = shift or return;
 
 sub checkoverdues {
     my $borrowernumber = shift or return;
-    # don't select biblioitems.marc or biblioitems.marcxml... too slow on large systems
     my $sth = C4::Context->dbh->prepare(
         "SELECT biblio.*, items.*, issues.*,
                 biblioitems.volume,
     my $sth = C4::Context->dbh->prepare(
         "SELECT biblio.*, items.*, issues.*,
                 biblioitems.volume,
@@ -196,7 +184,6 @@ sub checkoverdues {
             WHERE issues.borrowernumber  = ?
             AND   issues.date_due < NOW()"
     );
             WHERE issues.borrowernumber  = ?
             AND   issues.date_due < NOW()"
     );
-    # FIXME: SELECT * across 4 tables?  do we really need the marc AND marcxml blobs??
     $sth->execute($borrowernumber);
     my $results = $sth->fetchall_arrayref({});
     return ( scalar(@$results), $results);  # returning the count and the results is silly
     $sth->execute($borrowernumber);
     my $results = $sth->fetchall_arrayref({});
     return ( scalar(@$results), $results);  # returning the count and the results is silly
@@ -204,7 +191,7 @@ sub checkoverdues {
 
 =head2 CalcFine
 
 
 =head2 CalcFine
 
-    ($amount, $chargename,  $units_minus_grace, $chargeable_units) = &CalcFine($item,
+    ($amount, $units_minus_grace, $chargeable_units) = &CalcFine($item,
                                   $categorycode, $branch,
                                   $start_dt, $end_dt );
 
                                   $categorycode, $branch,
                                   $start_dt, $end_dt );
 
@@ -229,13 +216,10 @@ defining the date range over which to determine the fine.
 
 Fines scripts should just supply the date range over which to calculate the fine.
 
 
 Fines scripts should just supply the date range over which to calculate the fine.
 
-C<&CalcFine> returns four values:
+C<&CalcFine> returns three values:
 
 C<$amount> is the fine owed by the patron (see above).
 
 
 C<$amount> is the fine owed by the patron (see above).
 
-C<$chargename> is the chargename field from the applicable record in
-the categoryitem table, whatever that is.
-
 C<$units_minus_grace> is the number of chargeable units minus the grace period
 
 C<$chargeable_units> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
 C<$units_minus_grace> is the number of chargeable units minus the grace period
 
 C<$chargeable_units> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
@@ -251,27 +235,38 @@ sub CalcFine {
     my $start_date = $due_dt->clone();
     # get issuingrules (fines part will be used)
     my $itemtype = $item->{itemtype} || $item->{itype};
     my $start_date = $due_dt->clone();
     # get issuingrules (fines part will be used)
     my $itemtype = $item->{itemtype} || $item->{itype};
-    my $data = C4::Circulation::GetIssuingRule($bortype, $itemtype, $branchcode);
-    my $fine_unit = $data->{lengthunit};
-    $fine_unit ||= 'days';
+    my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ categorycode => $bortype, itemtype => $itemtype, branchcode => $branchcode });
+
+    $itemtype = Koha::ItemTypes->find($itemtype);
+
+    return unless $issuing_rule; # If not rule exist, there is no fine
+
+    my $fine_unit = $issuing_rule->lengthunit || 'days';
 
     my $chargeable_units = get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
 
     my $chargeable_units = get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
-    my $units_minus_grace = $chargeable_units - $data->{firstremind};
+    my $units_minus_grace = $chargeable_units - $issuing_rule->firstremind;
     my $amount = 0;
     my $amount = 0;
-    if ( $data->{'chargeperiod'} && ( $units_minus_grace > 0 ) ) {
+    if ( $issuing_rule->chargeperiod && ( $units_minus_grace > 0 ) ) {
         my $units = C4::Context->preference('FinesIncludeGracePeriod') ? $chargeable_units : $units_minus_grace;
         my $units = C4::Context->preference('FinesIncludeGracePeriod') ? $chargeable_units : $units_minus_grace;
-        my $charge_periods = $units / $data->{'chargeperiod'};
+        my $charge_periods = $units / $issuing_rule->chargeperiod;
         # If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
         # if chargeperiod_charge_at = 0, we charge at the end of each charge period
         # If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
         # if chargeperiod_charge_at = 0, we charge at the end of each charge period
-        $charge_periods = $data->{'chargeperiod_charge_at'} == 1 ? ceil($charge_periods) : floor($charge_periods);
-        $amount = $charge_periods * $data->{'fine'};
+        $charge_periods = $issuing_rule->chargeperiod_charge_at == 1 ? ceil($charge_periods) : floor($charge_periods);
+        $amount = $charge_periods * $issuing_rule->fine;
     } # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
 
     } # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
 
-    $amount = $data->{overduefinescap} if $data->{overduefinescap} && $amount > $data->{overduefinescap};
-    $amount = $item->{replacementprice} if ( $data->{cap_fine_to_replacement_price} && $item->{replacementprice} && $amount > $item->{replacementprice} );
-    $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
-    return ($amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
-    # FIXME: chargename is NEVER populated anywhere.
+    $amount = $issuing_rule->overduefinescap if $issuing_rule->overduefinescap && $amount > $issuing_rule->overduefinescap;
+
+    # This must be moved to Koha::Item (see also similar code in C4::Accounts::chargelostitem
+    $item->{replacementprice} ||= $itemtype->defaultreplacecost
+      if $itemtype
+      && $item->{replacementprice} == 0
+      && C4::Context->preference("useDefaultReplacementCost");
+
+    $amount = $item->{replacementprice} if ( $issuing_rule->cap_fine_to_replacement_price && $item->{replacementprice} && $amount > $item->{replacementprice} );
+
+    $debug and warn sprintf("CalcFine returning (%s, %s, %s)", $amount, $units_minus_grace, $chargeable_units);
+    return ($amount, $units_minus_grace, $chargeable_units);
 }
 
 
 }
 
 
@@ -471,7 +466,15 @@ sub GetIssuesIteminfo {
 
 =head2 UpdateFine
 
 
 =head2 UpdateFine
 
-    &UpdateFine({ issue_id => $issue_id, itemnumber => $itemnumber, borrwernumber => $borrowernumber, amount => $amount, type => $type, $due => $date_due });
+    &UpdateFine(
+        {
+            issue_id       => $issue_id,
+            itemnumber     => $itemnumber,
+            borrowernumber => $borrowernumber,
+            amount         => $amount,
+            due            => $date_due
+        }
+    );
 
 (Note: the following is mostly conjecture and guesswork.)
 
 
 (Note: the following is mostly conjecture and guesswork.)
 
@@ -484,8 +487,6 @@ has the book on loan.
 
 C<$amount> is the current amount owed by the patron.
 
 
 C<$amount> is the current amount owed by the patron.
 
-C<$type> will be used in the description of the fine.
-
 C<$due> is the due date formatted to the currently specified date format
 
 C<&UpdateFine> looks up the amount currently owed on the given item
 C<$due> is the due date formatted to the currently specified date format
 
 C<&UpdateFine> looks up the amount currently owed on the given item
@@ -509,10 +510,9 @@ sub UpdateFine {
     my $itemnum        = $params->{itemnumber};
     my $borrowernumber = $params->{borrowernumber};
     my $amount         = $params->{amount};
     my $itemnum        = $params->{itemnumber};
     my $borrowernumber = $params->{borrowernumber};
     my $amount         = $params->{amount};
-    my $type           = $params->{type};
     my $due            = $params->{due};
 
     my $due            = $params->{due};
 
-    $debug and warn "UpdateFine({ itemnumber => $itemnum, borrowernumber => $borrowernumber, type => $type, due => $due, issue_id => $issue_id})";
+    $debug and warn "UpdateFine({ itemnumber => $itemnum, borrowernumber => $borrowernumber, due => $due, issue_id => $issue_id})";
 
     unless ( $issue_id ) {
         carp("No issue_id passed in!");
 
     unless ( $issue_id ) {
         carp("No issue_id passed in!");
@@ -537,8 +537,9 @@ sub UpdateFine {
     #   "REF" is Cash Refund
     my $sth = $dbh->prepare(
         "SELECT * FROM accountlines
     #   "REF" is Cash Refund
     my $sth = $dbh->prepare(
         "SELECT * FROM accountlines
-        WHERE borrowernumber=?
-        AND   accounttype IN ('FU','O','F','M')"
+        WHERE borrowernumber=? AND
+        (( accounttype IN ('O','F','M') AND amountoutstanding<>0 ) OR
+           accounttype = 'FU' )"
     );
     $sth->execute( $borrowernumber );
     my $data;
     );
     $sth->execute( $borrowernumber );
     my $data;
@@ -549,9 +550,10 @@ sub UpdateFine {
     # - accumulate fines for other items
     # so we can update $itemnum fine taking in account fine caps
     while (my $rec = $sth->fetchrow_hashref) {
     # - accumulate fines for other items
     # so we can update $itemnum fine taking in account fine caps
     while (my $rec = $sth->fetchrow_hashref) {
-        if ( $rec->{issue_id} == $issue_id ) {
+        if ( $rec->{issue_id} == $issue_id && $rec->{accounttype} eq 'FU' ) {
             if ($data) {
                 warn "Not a unique accountlines record for issue_id $issue_id";
             if ($data) {
                 warn "Not a unique accountlines record for issue_id $issue_id";
+                #FIXME Should we still count this one in total_amount ??
             }
             else {
                 $data = $rec;
             }
             else {
                 $data = $rec;
@@ -590,6 +592,14 @@ sub UpdateFine {
                     accounttype   => 'FU',
                 }
             )->store();
                     accounttype   => 'FU',
                 }
             )->store();
+
+            Koha::Account::Offset->new(
+                {
+                    debit_id => $accountline->id,
+                    type     => 'Fine Update',
+                    amount   => $diff,
+                }
+            )->store();
         }
     } else {
         if ( $amount ) { # Don't add new fines with an amount of 0
         }
     } else {
         if ( $amount ) { # Don't add new fines with an amount of 0
@@ -601,7 +611,7 @@ sub UpdateFine {
 
             my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
 
 
             my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
 
-            my $desc = ( $type ? "$type " : '' ) . "$title $due";    # FIXEDME, avoid whitespace prefix on empty $type
+            my $desc = "$title $due";
 
             my $accountline = Koha::Account::Line->new(
                 {
 
             my $accountline = Koha::Account::Line->new(
                 {
@@ -614,6 +624,15 @@ sub UpdateFine {
                     amountoutstanding => $amount,
                     lastincrement     => $amount,
                     accountno         => $nextaccntno,
                     amountoutstanding => $amount,
                     lastincrement     => $amount,
                     accountno         => $nextaccntno,
+                    issue_id          => $issue_id,
+                }
+            )->store();
+
+            Koha::Account::Offset->new(
+                {
+                    debit_id => $accountline->id,
+                    type     => 'Fine',
+                    amount   => $amount,
                 }
             )->store();
         }
                 }
             )->store();
         }
@@ -621,7 +640,7 @@ sub UpdateFine {
     # logging action
     &logaction(
         "FINES",
     # logging action
     &logaction(
         "FINES",
-        $type,
+        undef,
         $borrowernumber,
         "due=".$due."  amount=".$amount." itemnumber=".$itemnum
         ) if C4::Context->preference("FinesLog");
         $borrowernumber,
         "due=".$due."  amount=".$amount." itemnumber=".$itemnum
         ) if C4::Context->preference("FinesLog");
@@ -636,7 +655,7 @@ Looks up a patron by borrower number.
 C<$borrower> is a reference-to-hash whose keys are all of the fields
 from the borrowers and categories tables of the Koha database. Thus,
 C<$borrower> contains all information about both the borrower and
 C<$borrower> is a reference-to-hash whose keys are all of the fields
 from the borrowers and categories tables of the Koha database. Thus,
 C<$borrower> contains all information about both the borrower and
-category he or she belongs to.
+category they belong to.
 
 =cut
 
 
 =cut
 
@@ -686,87 +705,6 @@ sub GetFine {
     return 0;
 }
 
     return 0;
 }
 
-=head2 NumberNotifyId
-
-    (@notify) = &NumberNotifyId($borrowernumber);
-
-Returns amount for all file per borrowers
-C<@notify> array contains all file per borrowers
-
-C<$notify_id> contains the file number for the borrower number nad item number
-
-=cut
-
-sub NumberNotifyId{
-    my ($borrowernumber)=@_;
-    my $dbh = C4::Context->dbh;
-    my $query=qq|    SELECT distinct(notify_id)
-            FROM accountlines
-            WHERE borrowernumber=?|;
-    my @notify;
-    my $sth = $dbh->prepare($query);
-    $sth->execute($borrowernumber);
-    while ( my ($numberofnotify) = $sth->fetchrow ) {
-        push( @notify, $numberofnotify );
-    }
-    return (@notify);
-}
-
-=head2 AmountNotify
-
-    ($totalnotify) = &AmountNotify($notifyid);
-
-Returns amount for all file per borrowers
-C<$notifyid> is the file number
-
-C<$totalnotify> contains amount of a file
-
-C<$notify_id> contains the file number for the borrower number and item number
-
-=cut
-
-sub AmountNotify{
-    my ($notifyid,$borrowernumber)=@_;
-    my $dbh = C4::Context->dbh;
-    my $query=qq|    SELECT sum(amountoutstanding)
-            FROM accountlines
-            WHERE notify_id=? AND borrowernumber = ?|;
-    my $sth=$dbh->prepare($query);
-       $sth->execute($notifyid,$borrowernumber);
-       my $totalnotify=$sth->fetchrow;
-    $sth->finish;
-    return ($totalnotify);
-}
-
-=head2 GetItems
-
-    ($items) = &GetItems($itemnumber);
-
-Returns the list of all delays from overduerules.
-
-C<$items> is a reference-to-hash whose keys are all of the fields
-from the items tables of the Koha database. Thus,
-
-C<$itemnumber> contains the borrower categorycode
-
-=cut
-
-# FIXME: This is a bad function to have here.
-# Shouldn't it be in C4::Items?
-# Shouldn't it be called GetItem since you only get 1 row?
-# Shouldn't it be called GetItem since you give it only 1 itemnumber?
-
-sub GetItems {
-    my $itemnumber = shift or return;
-    my $query = qq|SELECT *
-             FROM items
-              WHERE itemnumber=?|;
-    my $sth = C4::Context->dbh->prepare($query);
-    $sth->execute($itemnumber);
-    my ($items) = $sth->fetchrow_hashref;
-    return ($items);
-}
-
 =head2 GetBranchcodesWithOverdueRules
 
     my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
 =head2 GetBranchcodesWithOverdueRules
 
     my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
@@ -785,33 +723,11 @@ sub GetBranchcodesWithOverdueRules {
     |);
     if ( $branchcodes->[0] eq '' ) {
         # If a default rule exists, all branches should be returned
     |);
     if ( $branchcodes->[0] eq '' ) {
         # If a default rule exists, all branches should be returned
-        my $availbranches = C4::Branch::GetBranches();
-        return keys %$availbranches;
+        return map { $_->branchcode } Koha::Libraries->search({}, { order_by => 'branchname' });
     }
     return @$branchcodes;
 }
 
     }
     return @$branchcodes;
 }
 
-=head2 CheckItemNotify
-
-Sql request to check if the document has alreday been notified
-this function is not exported, only used with GetOverduesForBranch
-
-=cut
-
-sub CheckItemNotify {
-    my ($notify_id,$notify_level,$itemnumber) = @_;
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare("
-    SELECT COUNT(*)
-     FROM notifys
-    WHERE notify_id    = ?
-     AND  notify_level = ? 
-     AND  itemnumber   = ? ");
-    $sth->execute($notify_id,$notify_level,$itemnumber);
-    my $notified = $sth->fetchrow;
-    return ($notified);
-}
-
 =head2 GetOverduesForBranch
 
 Sql request for display all information for branchoverdues.pl
 =head2 GetOverduesForBranch
 
 Sql request for display all information for branchoverdues.pl
@@ -847,8 +763,6 @@ sub GetOverduesForBranch {
                 items.location,
                 items.itemnumber,
             itemtypes.description,
                 items.location,
                 items.itemnumber,
             itemtypes.description,
-         accountlines.notify_id,
-         accountlines.notify_level,
          accountlines.amountoutstanding
     FROM  accountlines
     LEFT JOIN issues      ON    issues.itemnumber     = accountlines.itemnumber
          accountlines.amountoutstanding
     FROM  accountlines
     LEFT JOIN issues      ON    issues.itemnumber     = accountlines.itemnumber
@@ -864,78 +778,13 @@ sub GetOverduesForBranch {
       AND (issues.branchcode =  ?   )
       AND (issues.date_due  < NOW())
     ";
       AND (issues.branchcode =  ?   )
       AND (issues.date_due  < NOW())
     ";
-    my @getoverdues;
-    my $i = 0;
-    my $sth;
     if ($location) {
     if ($location) {
-        $sth = $dbh->prepare("$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname");
-        $sth->execute($branch, $location);
+        my $q = "$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname";
+        return @{ $dbh->selectall_arrayref($q, { Slice => {} }, $branch, $location ) };
     } else {
     } else {
-        $sth = $dbh->prepare("$select ORDER BY borrowers.surname, borrowers.firstname");
-        $sth->execute($branch);
-    }
-    while ( my $data = $sth->fetchrow_hashref ) {
-    #check if the document has already been notified
-        my $countnotify = CheckItemNotify($data->{'notify_id'}, $data->{'notify_level'}, $data->{'itemnumber'});
-        if ($countnotify eq '0') {
-            $getoverdues[$i] = $data;
-            $i++;
-        }
+        my $q = "$select ORDER BY borrowers.surname, borrowers.firstname";
+        return @{ $dbh->selectall_arrayref($q, { Slice => {} }, $branch ) };
     }
     }
-    return (@getoverdues);
-}
-
-
-=head2 AddNotifyLine
-
-    &AddNotifyLine($borrowernumber, $itemnumber, $overduelevel, $method, $notifyId)
-
-Create a line into notify, if the method is phone, the notification_send_date is implemented to
-
-=cut
-
-sub AddNotifyLine {
-    my ( $borrowernumber, $itemnumber, $overduelevel, $method, $notifyId ) = @_;
-    my $dbh = C4::Context->dbh;
-    if ( $method eq "phone" ) {
-        my $sth = $dbh->prepare(
-            "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_send_date,notify_level,method,notify_id)
-        VALUES (?,?,now(),now(),?,?,?)"
-        );
-        $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
-            $notifyId );
-    }
-    else {
-        my $sth = $dbh->prepare(
-            "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_level,method,notify_id)
-        VALUES (?,?,now(),?,?,?)"
-        );
-        $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
-            $notifyId );
-    }
-    return 1;
-}
-
-=head2 RemoveNotifyLine
-
-    &RemoveNotifyLine( $borrowernumber, $itemnumber, $notify_date );
-
-Cancel a notification
-
-=cut
-
-sub RemoveNotifyLine {
-    my ( $borrowernumber, $itemnumber, $notify_date ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare(
-        "DELETE FROM notifys 
-            WHERE
-            borrowernumber=?
-            AND itemnumber=?
-            AND notify_date=?"
-    );
-    $sth->execute( $borrowernumber, $itemnumber, $notify_date );
-    return 1;
 }
 
 =head2 GetOverdueMessageTransportTypes
 }
 
 =head2 GetOverdueMessageTransportTypes
@@ -993,8 +842,9 @@ sub parse_overdues_letter {
         return unless ( exists $params->{$required} && $params->{$required} );
     }
 
         return unless ( exists $params->{$required} && $params->{$required} );
     }
 
+    my $patron = Koha::Patrons->find( $params->{borrowernumber} );
+
     my $substitute = $params->{'substitute'} || {};
     my $substitute = $params->{'substitute'} || {};
-    $substitute->{today} ||= output_pref( { dt => dt_from_string, dateonly => 1} );
 
     my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
     if ( my $p = $params->{'branchcode'} ) {
 
     my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
     if ( my $p = $params->{'branchcode'} ) {
@@ -1008,14 +858,8 @@ sub parse_overdues_letter {
 
     my @item_tables;
     if ( my $i = $params->{'items'} ) {
 
     my @item_tables;
     if ( my $i = $params->{'items'} ) {
-        my $item_format = '';
         foreach my $item (@$i) {
             my $fine = GetFine($item->{'itemnumber'}, $params->{'borrowernumber'});
         foreach my $item (@$i) {
             my $fine = GetFine($item->{'itemnumber'}, $params->{'borrowernumber'});
-            if ( !$item_format and defined $params->{'letter'}->{'content'} ) {
-                $params->{'letter'}->{'content'} =~ m/(<item>.*<\/item>)/;
-                $item_format = $1;
-            }
-
             $item->{'fine'} = currency_format($currency_format, "$fine", FMT_SYMBOL);
             # if active currency isn't correct ISO code fallback to sprintf
             $item->{'fine'} = sprintf('%.2f', $fine) unless $item->{'fine'};
             $item->{'fine'} = currency_format($currency_format, "$fine", FMT_SYMBOL);
             # if active currency isn't correct ISO code fallback to sprintf
             $item->{'fine'} = sprintf('%.2f', $fine) unless $item->{'fine'};
@@ -1033,7 +877,11 @@ sub parse_overdues_letter {
         module => 'circulation',
         letter_code => $params->{'letter_code'},
         branchcode => $params->{'branchcode'},
         module => 'circulation',
         letter_code => $params->{'letter_code'},
         branchcode => $params->{'branchcode'},
+        lang => $patron->lang,
         tables => \%tables,
         tables => \%tables,
+        loops => {
+            overdues => [ map { $_->{items}->{itemnumber} } @item_tables ],
+        },
         substitute => $substitute,
         repeat => { item => \@item_tables },
         message_transport_type => $params->{message_transport_type},
         substitute => $substitute,
         repeat => { item => \@item_tables },
         message_transport_type => $params->{message_transport_type},