Bug 42679: Add GET /patrons/{patron_id}/overdues_count endpoint

Returns the count of overdue checkouts for a patron as a plain integer.
Computed live from the database (date_due < NOW()) to ensure accuracy,
as overdue status is time-based with no discrete event to trigger updates.

This endpoint is intended for async loading in the patron search table,
allowing the main search results to render immediately while overdues
are fetched separately per visible page.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/api/v1/patrons_overdues_count.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
Tomás Cohen Arazi 2026-05-23 01:00:44 -03:00 committed by Pedro Amorim
parent 5d75b6fdad
commit f7caec076f
No known key found for this signature in database
GPG Key ID: 06B75BA7F3894D55
4 changed files with 161 additions and 0 deletions

View File

@ -503,4 +503,26 @@ sub guarantors_can_see_checkouts {
};
}
=head3 overdues_count
Controller function that returns the count of overdue checkouts for a patron.
=cut
sub overdues_count {
my $c = shift->openapi->valid_input or return;
return try {
my $patron = Koha::Patrons->find( $c->param('patron_id') );
return $c->render_resource_not_found("Patron") unless $patron;
return $c->render(
status => 200,
openapi => $patron->overdues->count,
);
} catch {
$c->unhandled_exception($_);
};
}
1;

View File

@ -740,3 +740,43 @@
x-koha-authorization:
permissions:
ill: "1"
"/patrons/{patron_id}/overdues_count":
get:
x-mojo-to: Patrons#overdues_count
operationId: getPatronOverduesCount
tags:
- patrons
summary: Get count of overdue checkouts for a patron
parameters:
- $ref: "../swagger.yaml#/parameters/patron_id_pp"
produces:
- application/json
responses:
"200":
description: Overdue count
schema:
type: integer
"400":
description: Bad request
schema:
$ref: "../swagger.yaml#/definitions/error"
"401":
description: Authentication required
schema:
$ref: "../swagger.yaml#/definitions/error"
"403":
description: Access forbidden
schema:
$ref: "../swagger.yaml#/definitions/error"
"404":
description: Patron not found
schema:
$ref: "../swagger.yaml#/definitions/error"
"500":
description: Internal server error
schema:
$ref: "../swagger.yaml#/definitions/error"
x-koha-authorization:
permissions:
- borrowers: "list_borrowers"

View File

@ -549,6 +549,8 @@ paths:
$ref: ./paths/patrons.yaml#/~1patrons
"/patrons/{patron_id}":
$ref: "./paths/patrons.yaml#/~1patrons~1{patron_id}"
"/patrons/{patron_id}/overdues_count":
$ref: "./paths/patrons.yaml#/~1patrons~1{patron_id}~1overdues_count"
"/patrons/{patron_id}/account":
$ref: "./paths/patrons_account.yaml#/~1patrons~1{patron_id}~1account"
"/patrons/{patron_id}/account/credits":
@ -1515,6 +1517,9 @@ tags:
- description: "Manage search field mappings"
name: search_mappings
x-displayName: Search mappings
- description: "Search patrons via Elasticsearch"
name: search
x-displayName: Search
- description: "Manage SIP2 accounts"
name: sip2_accounts
x-displayName: SIP2 Accounts

View File

@ -0,0 +1,94 @@
#!/usr/bin/env perl
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use Test::NoWarnings;
use Test::More tests => 3;
use Test::Mojo;
use t::lib::TestBuilder;
use t::lib::Mocks;
use Koha::Checkouts;
use Koha::Database;
use Koha::DateUtils qw( dt_from_string );
my $schema = Koha::Database->new->schema;
my $builder = t::lib::TestBuilder->new;
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
my $t = Test::Mojo->new('Koha::REST::V1');
subtest 'overdues_count unauthorized' => sub {
plan tests => 2;
$schema->storage->txn_begin;
my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
$t->get_ok( "/api/v1/patrons/" . $patron->borrowernumber . "/overdues_count" )->status_is(401);
$schema->storage->txn_rollback;
};
subtest 'overdues_count' => sub {
plan tests => 8;
$schema->storage->txn_begin;
my $librarian = $builder->build_object(
{
class => 'Koha::Patrons',
value => { flags => 2**4 } # borrowers flag
}
);
my $password = 'thePassword123';
$librarian->set_password( { password => $password, skip_validation => 1 } );
my $userid = $librarian->userid;
my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
# No checkouts - 0 overdues
$t->get_ok( "//$userid:$password\@/api/v1/patrons/" . $patron->borrowernumber . "/overdues_count" )
->status_is(200)
->json_is(0);
# Add an overdue checkout
my $item = $builder->build_sample_item();
$builder->build(
{
source => 'Issue',
value => {
borrowernumber => $patron->borrowernumber,
itemnumber => $item->itemnumber,
date_due => '2020-01-01 00:00:00',
branchcode => $patron->branchcode,
}
}
);
$t->get_ok( "//$userid:$password\@/api/v1/patrons/" . $patron->borrowernumber . "/overdues_count" )
->status_is(200)
->json_is(1);
# Non-existent patron
$t->get_ok("//$userid:$password\@/api/v1/patrons/999999999/overdues_count")->status_is(404);
$schema->storage->txn_rollback;
};