--- a/Koha/Patron/Discharge.pm 2024-07-23 15:40:04.029970274 +0300 +++ b/Koha/Patron/Discharge.pm 2025-12-10 14:36:25.809164340 +0200 @@ -39,8 +39,22 @@ my $patron = Koha::Patrons->find( $params->{borrowernumber} ); return unless $patron; - my $has_pending_checkouts = $patron->checkouts->count; - return $has_pending_checkouts ? 0 : 1; + my $can_be_discharged = 1; + my $problems = {}; + + my $checkouts = $patron->checkouts->count; + if ($checkouts) { + $can_be_discharged = 0; + $problems->{checkouts} = $checkouts; + } + + my $debt = $patron->account->outstanding_debits->total_outstanding; + if ( $debt > 0 ) { + $can_be_discharged = 0; + $problems->{debt} = $debt; + } + + return ( $can_be_discharged, $problems ); } sub is_discharged { @@ -62,7 +76,8 @@ my ($params) = @_; my $borrowernumber = $params->{borrowernumber}; return unless $borrowernumber; - return unless can_be_discharged({ borrowernumber => $borrowernumber }); + my ($can) = can_be_discharged( { borrowernumber => $borrowernumber } ); + return unless $can; my $rs = Koha::Database->new->schema->resultset('Discharge'); return $rs->create({ @@ -74,7 +89,9 @@ sub discharge { my ($params) = @_; my $borrowernumber = $params->{borrowernumber}; - return unless $borrowernumber and can_be_discharged( { borrowernumber => $borrowernumber } ); + + my ($can) = can_be_discharged( { borrowernumber => $borrowernumber } ); + return unless $borrowernumber and $can; # Cancel reserves my $patron = Koha::Patrons->find( $borrowernumber ); --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-discharge.tt 2025-05-20 17:40:38.293395967 +0300 +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-discharge.tt 2025-12-10 14:46:03.093599152 +0200 @@ -1,5 +1,6 @@ [% USE raw %] [% USE Koha %] +[% USE Price %] [% USE AdditionalContents %] [% SET OpacNav = AdditionalContents.get( location => "OpacNav", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %] [% SET OpacNavBottom = AdditionalContents.get( location => "OpacNavBottom", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %] @@ -43,20 +44,32 @@ Get your discharge [% ELSIF pending %]

Your discharge will be available on this page within a few days.

- [% ELSIF has_issues %] -

You cannot be discharged, you have checked out items. Please return items before asking for a discharge.

[% ELSIF not messages %]

What is a discharge?

This document certifies that you have returned all borrowed items. It is sometimes asked during a file transfer from a school to another. The discharge is sent by us to your school. You will also find it available on your reader account.

Warning: This request is only valid if you are in good standing with the library. Once the application is made, you can not borrow library materials.

- [% IF has_checkouts %] -
You cannot be discharged, you have checked out items. Please return items before asking for a discharge.
- [% ELSE %] + [% IF can_be_discharged %]
[% INCLUDE 'csrf-token.inc' %]
+ [% ELSE %] + [% IF failure %] +

There was an error during the discharge process

+ [% END %] +
You cannot be discharged because: + +
[% END %] [% END %] --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-discharge.tt 2025-05-20 17:40:38.293395967 +0300 +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-discharge.tt 2025-12-10 14:46:03.093599152 +0200 @@ -1,5 +1,6 @@ [% USE raw %] [% USE Koha %] +[% USE Price %] [% USE AdditionalContents %] [% SET OpacNav = AdditionalContents.get( location => "OpacNav", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %] [% SET OpacNavBottom = AdditionalContents.get( location => "OpacNavBottom", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %] @@ -43,20 +44,32 @@ Get your discharge [% ELSIF pending %]

Your discharge will be available on this page within a few days.

- [% ELSIF has_issues %] -

You cannot be discharged, you have checked out items. Please return items before asking for a discharge.

[% ELSIF not messages %]

What is a discharge?

This document certifies that you have returned all borrowed items. It is sometimes asked during a file transfer from a school to another. The discharge is sent by us to your school. You will also find it available on your reader account.

Warning: This request is only valid if you are in good standing with the library. Once the application is made, you can not borrow library materials.

- [% IF has_checkouts %] -
You cannot be discharged, you have checked out items. Please return items before asking for a discharge.
- [% ELSE %] + [% IF can_be_discharged %]
[% INCLUDE 'csrf-token.inc' %]
+ [% ELSE %] + [% IF failure %] +

There was an error during the discharge process

+ [% END %] +
You cannot be discharged because: + +
[% END %] [% END %] --- a/members/discharge.pl 2025-05-20 17:40:38.555392536 +0300 +++ b/members/discharge.pl 2025-12-10 14:26:03.817925780 +0200 @@ -61,9 +61,8 @@ my $patron = Koha::Patrons->find( $borrowernumber ); output_and_exit_if_error( $input, $cookie, $template, { module => 'members', logged_in_user => $logged_in_user, current_patron => $patron } ); -my $can_be_discharged = Koha::Patron::Discharge::can_be_discharged({ - borrowernumber => $borrowernumber -}); +my ( $can_be_discharged, $discharge_problems ) = + Koha::Patron::Discharge::can_be_discharged( { borrowernumber => $borrowernumber } ); # Generating discharge if needed if ( $op eq 'cud-discharge' && $input->param('discharge') and $can_be_discharged ) { @@ -107,6 +106,7 @@ $template->param( patron => $patron, can_be_discharged => $can_be_discharged, + discharge_problems => $discharge_problems, validated_discharges => \@validated_discharges, ); --- a/opac/opac-discharge.pl 2025-05-20 17:40:45.151306301 +0300 +++ b//opac/opac-discharge.pl 2025-12-10 14:59:49.806228119 +0200 @@ -43,10 +43,14 @@ type => "opac", }); -my $can_be_discharged = Koha::Patron::Discharge::can_be_discharged({ borrowernumber => $loggedinuser }); -if ($can_be_discharged == 0) { - $template->param( has_checkouts => 1 ); -} +my ( $can_be_discharged, $discharge_problems ) = + Koha::Patron::Discharge::can_be_discharged( { borrowernumber => $loggedinuser } ); +my $patron = Koha::Patrons->find($loggedinuser); + +$template->param( + can_be_discharged => $can_be_discharged, + discharge_problems => $discharge_problems, +); my $pending = Koha::Patron::Discharge::count({ borrowernumber => $loggedinuser, @@ -67,7 +71,7 @@ $template->param( success => 1 ); } else { - $template->param( has_issues => 1 ); + $template->param( failure => 1 ); } } elsif ( $op eq 'get' ) { --- a/t/db_dependent/Patron/Borrower_Discharge.t 2025-05-20 17:40:46.600287390 +0300 +++ b/t/db_dependent/Patron/Borrower_Discharge.t 2025-12-10 14:33:21.243162635 +0200 @@ -16,7 +16,7 @@ use Modern::Perl; -use Test::More tests => 23; +use Test::More tests => 32; use Test::MockModule; use Test::Warn; @@ -83,17 +83,56 @@ ); AddIssue( $patron, $barcode ); -is( Koha::Patron::Discharge::can_be_discharged({ borrowernumber => $patron->borrowernumber }), 0, 'A patron with issues cannot be discharged' ); +my ( $can, $problems ) = Koha::Patron::Discharge::can_be_discharged( { borrowernumber => $patron->borrowernumber } ); +is( $can, 0, 'A patron with issues cannot be discharged' ); +is( $problems->{checkouts}, 1, "Patron has checkouts" ); +is( $problems->{debt}, undef, "Patron has no debt" ); is( Koha::Patron::Discharge::request({ borrowernumber => $patron->borrowernumber }), undef, 'No request done if patron has issues' ); is( Koha::Patron::Discharge::discharge({ borrowernumber => $patron->borrowernumber }), undef, 'No discharge done if patron has issues' ); is_deeply( [ Koha::Patron::Discharge::get_pendings ], [], 'There is no pending discharge request' ); is_deeply( [ Koha::Patron::Discharge::get_validated ], [], 'There is no validated discharge' ); +# Discharge not possible with issues and fines +$patron->account->add_debit( + { + amount => 0.1, + interface => C4::Context->interface, + type => 'OVERDUE' + } +); + +( $can, $problems ) = Koha::Patron::Discharge::can_be_discharged( { borrowernumber => $patron->borrowernumber } ); +is( + $can, 0, + 'A patron with fines and checkouts cannot be discharged' +); +is( $problems->{checkouts}, 1, "Patron has checkouts" ); +is( $problems->{debt}, 0.1, "Patron has debt" ); + AddReturn( $barcode ); -# Discharge possible without issue -is( Koha::Patron::Discharge::can_be_discharged({ borrowernumber => $patron->borrowernumber }), 1, 'A patron without issues can be discharged' ); +( $can, $problems ) = Koha::Patron::Discharge::can_be_discharged( { borrowernumber => $patron->borrowernumber } ); +is( + $can, 0, + 'A patron with fines cannot be discharged' +); +is( $problems->{checkouts}, undef, "Patron has checkouts" ); +is( $problems->{debt}, 0.1, "Patron has debt" ); + +$patron->account->pay( + { + amount => 0.1, + } +); + +# Discharge possible without issue or fine +( $can, $problems ) = Koha::Patron::Discharge::can_be_discharged( { borrowernumber => $patron->borrowernumber } ); + is( + $can, 1, + 'A patron without issues or fines can be discharged' + ); +is( scalar keys %$problems, 0, "No dishcharge problems found" ); is(Koha::Patron::Discharge::generate_as_pdf,undef,"Confirm failure when lacking borrower number");