From f7caec076f3eaef545b9cf47722793d8feec6036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Cohen=20Arazi?= Date: Sat, 23 May 2026 01:00:44 -0300 Subject: [PATCH] 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 Signed-off-by: Kyle M Hall Signed-off-by: Pedro Amorim --- Koha/REST/V1/Patrons.pm | 22 +++++ api/v1/swagger/paths/patrons.yaml | 40 ++++++++ api/v1/swagger/swagger.yaml | 5 + .../api/v1/patrons_overdues_count.t | 94 +++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100755 t/db_dependent/api/v1/patrons_overdues_count.t diff --git a/Koha/REST/V1/Patrons.pm b/Koha/REST/V1/Patrons.pm index a6f9fb6657..2531eac7aa 100644 --- a/Koha/REST/V1/Patrons.pm +++ b/Koha/REST/V1/Patrons.pm @@ -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; diff --git a/api/v1/swagger/paths/patrons.yaml b/api/v1/swagger/paths/patrons.yaml index 3621453c70..6c05c9ac4d 100644 --- a/api/v1/swagger/paths/patrons.yaml +++ b/api/v1/swagger/paths/patrons.yaml @@ -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" diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index b2497735f7..7beacae0f9 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -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 diff --git a/t/db_dependent/api/v1/patrons_overdues_count.t b/t/db_dependent/api/v1/patrons_overdues_count.t new file mode 100755 index 0000000000..7dd009808e --- /dev/null +++ b/t/db_dependent/api/v1/patrons_overdues_count.t @@ -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 . + +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; +};