Bug 42206: Add GET /libraries/{id}/holidays endpoint
- Add list_holidays controller to Koha::REST::V1::Libraries - Returns closed days for a library within a configurable date range - Uses Koha::Calendar to determine holidays - Defaults to today + 3 months if no range specified - Add OpenAPI spec for the new endpoint - Register route in swagger.yaml - Add unit tests in t/db_dependent/api/v1/libraries.t To test: 1. Apply patch 2. prove t/db_dependent/api/v1/libraries.t 3. Verify all tests pass, including the new list_holidays subtest which covers: - Holidays returned within a specified date range - Empty list when no holidays exist in range - 400 response when 'to' date precedes 'from' date - 404 response for non-existent library - Default range (no params) returns successfully Sponsored-by: Büchereizentrale Schleswig-Holstein <https://www.bz-sh.de/> Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io> Signed-off-by: David Nind <david@davidnind.com> Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk> Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
parent
cdf015b497
commit
2a3376b243
|
|
@ -18,7 +18,10 @@ package Koha::REST::V1::Libraries;
|
|||
use Modern::Perl;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
use C4::Context;
|
||||
use Koha::Libraries;
|
||||
use Koha::Calendar;
|
||||
use Koha::DateUtils qw( dt_from_string );
|
||||
|
||||
use Scalar::Util qw( blessed );
|
||||
|
||||
|
|
@ -209,4 +212,54 @@ sub list_cash_registers {
|
|||
};
|
||||
}
|
||||
|
||||
=head3 list_holidays
|
||||
|
||||
Controller function that returns closed days for a library within a date range.
|
||||
Used by booking calendar to disable selection of closed days.
|
||||
|
||||
=cut
|
||||
|
||||
sub list_holidays {
|
||||
my $c = shift->openapi->valid_input or return;
|
||||
|
||||
my $library_id = $c->param('library_id');
|
||||
my $from = $c->param('from');
|
||||
my $to = $c->param('to');
|
||||
|
||||
my $library = Koha::Libraries->find($library_id);
|
||||
|
||||
return $c->render_resource_not_found("Library")
|
||||
unless $library;
|
||||
|
||||
return try {
|
||||
my $from_dt = $from ? dt_from_string( $from, 'iso' ) : dt_from_string();
|
||||
my $to_dt = $to ? dt_from_string( $to, 'iso' ) : $from_dt->clone->add( months => 3 );
|
||||
|
||||
if ( $to_dt->compare($from_dt) < 0 ) {
|
||||
return $c->render(
|
||||
status => 400,
|
||||
openapi => { error => "'to' date must be after 'from' date" }
|
||||
);
|
||||
}
|
||||
|
||||
my $calendar = Koha::Calendar->new( branchcode => $library_id );
|
||||
my @holidays;
|
||||
|
||||
my $current = $from_dt->clone;
|
||||
while ( $current <= $to_dt ) {
|
||||
if ( $calendar->is_holiday($current) ) {
|
||||
push @holidays, $current->ymd;
|
||||
}
|
||||
$current->add( days => 1 );
|
||||
}
|
||||
|
||||
return $c->render(
|
||||
status => 200,
|
||||
openapi => \@holidays
|
||||
);
|
||||
} catch {
|
||||
$c->unhandled_exception($_);
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -425,6 +425,62 @@
|
|||
x-koha-authorization:
|
||||
permissions:
|
||||
catalogue: 1
|
||||
"/libraries/{library_id}/holidays":
|
||||
get:
|
||||
x-mojo-to: Libraries#list_holidays
|
||||
operationId: listLibraryHolidays
|
||||
tags:
|
||||
- libraries
|
||||
summary: List holidays for a library
|
||||
description: |
|
||||
Returns a list of closed days (holidays) for a library within a date range.
|
||||
Used by booking calendar to disable selection of closed days.
|
||||
parameters:
|
||||
- $ref: "../swagger.yaml#/parameters/library_id_pp"
|
||||
- name: from
|
||||
in: query
|
||||
description: Start date for the range (ISO 8601 format, e.g., 2024-01-01). Defaults to today.
|
||||
required: false
|
||||
type: string
|
||||
format: date
|
||||
- name: to
|
||||
in: query
|
||||
description: End date for the range (ISO 8601 format, e.g., 2024-03-31). Defaults to 3 months from 'from'.
|
||||
required: false
|
||||
type: string
|
||||
format: date
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: A list of holiday dates in YYYY-MM-DD format
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: date
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"404":
|
||||
description: Library not found
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"500":
|
||||
description: |
|
||||
Internal server error. Possible `error_code` attribute values:
|
||||
|
||||
* `internal_server_error`
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"503":
|
||||
description: Under maintenance
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
x-koha-authorization:
|
||||
permissions:
|
||||
catalogue: "1"
|
||||
/public/libraries:
|
||||
get:
|
||||
x-mojo-to: Libraries#list
|
||||
|
|
|
|||
|
|
@ -499,6 +499,8 @@ paths:
|
|||
$ref: "./paths/libraries.yaml#/~1libraries~1{library_id}~1cash_registers"
|
||||
"/libraries/{library_id}/desks":
|
||||
$ref: "./paths/libraries.yaml#/~1libraries~1{library_id}~1desks"
|
||||
"/libraries/{library_id}/holidays":
|
||||
$ref: "./paths/libraries.yaml#/~1libraries~1{library_id}~1holidays"
|
||||
"/oauth/login/{provider_code}/{interface}":
|
||||
$ref: ./paths/oauth.yaml#/~1oauth~1login~1{provider_code}~1{interface}
|
||||
/oauth/token:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
use Modern::Perl;
|
||||
|
||||
use Test::NoWarnings;
|
||||
use Test::More tests => 8;
|
||||
use Test::More tests => 9;
|
||||
use Test::Mojo;
|
||||
use Test::Warn;
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ use t::lib::Mocks;
|
|||
|
||||
use List::Util qw(min);
|
||||
|
||||
use Koha::Caches;
|
||||
use Koha::Libraries;
|
||||
use Koha::Database;
|
||||
|
||||
|
|
@ -438,3 +439,100 @@ subtest 'list_cash_registers() tests' => sub {
|
|||
|
||||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
||||
subtest 'list_holidays() tests' => sub {
|
||||
|
||||
plan tests => 16;
|
||||
|
||||
$schema->storage->txn_begin;
|
||||
|
||||
my $library = $builder->build_object( { class => 'Koha::Libraries' } );
|
||||
my $patron = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Patrons',
|
||||
value => { flags => 4 }
|
||||
}
|
||||
);
|
||||
my $password = 'thePassword123';
|
||||
$patron->set_password( { password => $password, skip_validation => 1 } );
|
||||
my $userid = $patron->userid;
|
||||
|
||||
my $branchcode = $library->branchcode;
|
||||
my $cache = Koha::Caches->get_instance();
|
||||
|
||||
# Clear existing holidays for this branch
|
||||
$schema->resultset('SpecialHoliday')->search( { branchcode => $branchcode } )->delete;
|
||||
$schema->resultset('RepeatableHoliday')->search( { branchcode => $branchcode } )->delete;
|
||||
$cache->clear_from_cache( $branchcode . '_holidays' );
|
||||
|
||||
# Add a specific holiday
|
||||
$builder->build(
|
||||
{
|
||||
source => 'SpecialHoliday',
|
||||
value => {
|
||||
branchcode => $branchcode,
|
||||
day => 15,
|
||||
month => 6,
|
||||
year => 2026,
|
||||
title => 'Test holiday',
|
||||
description => '',
|
||||
isexception => 0,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
# Add a second holiday
|
||||
$builder->build(
|
||||
{
|
||||
source => 'SpecialHoliday',
|
||||
value => {
|
||||
branchcode => $branchcode,
|
||||
day => 20,
|
||||
month => 6,
|
||||
year => 2026,
|
||||
title => 'Another holiday',
|
||||
description => '',
|
||||
isexception => 0,
|
||||
},
|
||||
}
|
||||
);
|
||||
$cache->clear_from_cache( $branchcode . '_holidays' );
|
||||
|
||||
# Returns holidays within range
|
||||
$t->get_ok("//$userid:$password@/api/v1/libraries/$branchcode/holidays?from=2026-06-01&to=2026-06-30")
|
||||
->status_is(200);
|
||||
|
||||
my $holidays = $t->tx->res->json;
|
||||
ok( ( grep { $_ eq '2026-06-15' } @$holidays ), 'First holiday found in response' );
|
||||
ok( ( grep { $_ eq '2026-06-20' } @$holidays ), 'Second holiday found in response' );
|
||||
|
||||
# Returns empty list when no holidays in range
|
||||
$t->get_ok("//$userid:$password@/api/v1/libraries/$branchcode/holidays?from=2026-07-01&to=2026-07-05")
|
||||
->status_is(200);
|
||||
|
||||
my $no_holidays = $t->tx->res->json;
|
||||
my @filtered = grep { $_ eq '2026-06-15' || $_ eq '2026-06-20' } @$no_holidays;
|
||||
is( scalar @filtered, 0, 'No test holidays outside their range' );
|
||||
|
||||
# 400 when to < from
|
||||
$t->get_ok("//$userid:$password@/api/v1/libraries/$branchcode/holidays?from=2026-06-30&to=2026-06-01")
|
||||
->status_is(400)
|
||||
->json_is( '/error' => "'to' date must be after 'from' date" );
|
||||
|
||||
# 404 for non-existent library
|
||||
my $non_existent_code = $library->branchcode;
|
||||
$library->delete;
|
||||
$cache->clear_from_cache( $non_existent_code . '_holidays' );
|
||||
|
||||
$t->get_ok("//$userid:$password@/api/v1/libraries/$non_existent_code/holidays?from=2026-06-01&to=2026-06-30")
|
||||
->status_is(404)
|
||||
->json_is( '/error' => 'Library not found' );
|
||||
|
||||
# Defaults work (no from/to params)
|
||||
my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
|
||||
$t->get_ok( "//$userid:$password@/api/v1/libraries/" . $library2->branchcode . "/holidays" )
|
||||
->status_is(200)
|
||||
->json_is( '' => [], 'Empty holidays for fresh library with defaults' );
|
||||
|
||||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue