From 7d32a15947b1f55e4db462f18ad5a6b7207dba68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Cohen=20Arazi?= Date: Wed, 28 Jan 2026 16:29:06 -0300 Subject: [PATCH] =?UTF-8?q?[POC]=C2=A0Bug=2041728:=20Add=20Koha::Item::Che?= =?UTF-8?q?ckout::Availability=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is for illustration purposes only! Not intended to be tested or pushed. This patch introduces a checkout availability class that wraps C4::Circulation::CanBookBeIssued in a Result object interface, providing a consistent API for availability checks. The class: - Wraps CanBookBeIssued and maps its return values to Result object - Maps 'impossible' to blockers - Maps 'confirmation' to confirmations - Maps 'alerts' and 'messages' to warnings - Provides $item->checkout_availability() convenience method This enables the REST API and other code to use a consistent availability checking interface across different operations (checkout, check-in, renewal, etc.). Test plan: 1. Apply patch 2. Run: $ ktd --shell k$ prove t/db_dependent/Koha/Item/Checkout/Availability.t => SUCCESS: Tests pass! 3. Verify checkout availability checks work: - Available items return no blockers - Debarred patrons get DEBARRED blocker - Context includes patron and item objects 4. Sign off :-D --- Koha/Item.pm | 29 +++++ Koha/Item/Checkout/Availability.pm | 122 ++++++++++++++++++ .../Koha/Item/Checkout/Availability.t | 82 ++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 Koha/Item/Checkout/Availability.pm create mode 100644 t/db_dependent/Koha/Item/Checkout/Availability.t diff --git a/Koha/Item.pm b/Koha/Item.pm index 8d239dc14c..a7cada5163 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -807,6 +807,35 @@ sub checkin_availability { return Koha::Item::Checkin::Availability->check( $self, $params ); } +=head3 checkout_availability + + my $availability = $item->checkout_availability( + { + patron => $patron, + } + ); + +Returns checkout availability information for this item. + +This is a convenience method that calls Koha::Item::Checkout::Availability->check(). + +Returns a Koha::Availability::Result object with methods: + available() # Boolean: true if checkout allowed + needs_confirmation()# Boolean: true if confirmation required + blockers() # Hashref of blocking conditions + confirmations() # Hashref of conditions requiring confirmation + warnings() # Hashref of warning messages + context() # Hashref with patron and item objects + +=cut + +sub checkout_availability { + my ( $self, $params ) = @_; + + require Koha::Item::Checkout::Availability; + return Koha::Item::Checkout::Availability->check( $self, $params ); +} + =head3 request_transfer my $transfer = $item->request_transfer( diff --git a/Koha/Item/Checkout/Availability.pm b/Koha/Item/Checkout/Availability.pm new file mode 100644 index 0000000000..47a99b65c5 --- /dev/null +++ b/Koha/Item/Checkout/Availability.pm @@ -0,0 +1,122 @@ +package Koha::Item::Checkout::Availability; + +# Copyright 2026 Koha Development Team +# +# 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 C4::Circulation qw( CanBookBeIssued ); +use Koha::Availability::Result; + +=head1 NAME + +Koha::Item::Checkout::Availability - Checkout availability validation for items + +=head1 SYNOPSIS + + my $availability = Koha::Item::Checkout::Availability->check( $item, + { + patron => $patron, + } + ); + + # Or via instance method + my $availability = $item->checkout_availability( { patron => $patron } ); + +=head1 DESCRIPTION + +This class provides validation logic for item checkout operations, wrapping +C4::Circulation::CanBookBeIssued in a Result object interface. + +The result categorizes conditions as: +- blockers: Prevent checkout (e.g., item already checked out, patron debarred) +- confirmations: Require user confirmation (e.g., item on hold for another patron) +- warnings: Informational (e.g., patron has many checkouts) + +=head1 API + +=head2 Class methods + +=head3 check + + my $availability = Koha::Item::Checkout::Availability->check( $item, + { + patron => $patron, + } + ); + +Validates checkout availability for an item. + +Parameters: + $item - Koha::Item object (required) + $params - hashref with: + patron => $patron (required) + +Returns a Koha::Availability::Result object with: + blockers => {} # Conditions that prevent checkout + confirmations => {} # Conditions requiring confirmation + warnings => {} # Informational messages + context => { + patron => $patron # Koha::Patron object + item => $item # Koha::Item object + } + +=cut + +sub check { + my ( $class, $item, $params ) = @_; + + my $patron = $params->{patron}; + + my $result = Koha::Availability::Result->new(); + + # Set context + $result->set_context( patron => $patron ); + $result->set_context( item => $item ); + + # Call CanBookBeIssued + my ( $impossible, $confirmation, $alerts, $messages ) = + CanBookBeIssued( $patron, undef, undef, 0, 0, { item => $item } ); + + # Map impossible to blockers + for my $key ( keys %{$impossible} ) { + $result->add_blocker( $key => $impossible->{$key} ); + } + + # Map confirmation to confirmations + for my $key ( keys %{$confirmation} ) { + $result->add_confirmation( $key => $confirmation->{$key} ); + } + + # Map alerts and messages to warnings + for my $key ( keys %{$alerts} ) { + $result->add_warning( $key => $alerts->{$key} ); + } + for my $key ( keys %{$messages} ) { + $result->add_warning( $key => $messages->{$key} ); + } + + return $result; +} + +=head1 AUTHOR + +Koha Development Team + +=cut + +1; diff --git a/t/db_dependent/Koha/Item/Checkout/Availability.t b/t/db_dependent/Koha/Item/Checkout/Availability.t new file mode 100644 index 0000000000..32d82461cd --- /dev/null +++ b/t/db_dependent/Koha/Item/Checkout/Availability.t @@ -0,0 +1,82 @@ +#!/usr/bin/perl + +# Copyright 2026 Koha Development Team +# +# 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::More tests => 3; +use Test::NoWarnings; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::Database; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +subtest 'check() - available item' => sub { + + plan tests => 4; + + $schema->storage->txn_begin; + + my $library = $builder->build_object( { class => 'Koha::Libraries' } ); + my $patron = $builder->build_object( { class => 'Koha::Patrons' } ); + my $item = $builder->build_sample_item(); + + my $result = $item->checkout_availability( + { + patron => $patron, + } + ); + + is( keys %{ $result->blockers }, 0, 'no blockers for available item' ); + is( keys %{ $result->confirmations }, 0, 'no confirmations for available item' ); + is( $result->context->{patron}->id, $patron->id, 'patron in context' ); + is( $result->context->{item}->itemnumber, $item->itemnumber, 'item in context' ); + + $schema->storage->txn_rollback; +}; + +subtest 'check() - patron debarred' => sub { + + plan tests => 3; + + $schema->storage->txn_begin; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { debarred => '9999-12-31' } + } + ); + my $item = $builder->build_sample_item(); + + my $result = $item->checkout_availability( + { + patron => $patron, + } + ); + + is( $result->blockers->{DEBARRED}, 1, 'DEBARRED blocker set' ); + is( $result->blockers->{USERBLOCKEDNOENDDATE}, '9999-12-31', 'USERBLOCKEDNOENDDATE blocker set' ); + ok( !$result->available, 'not available when patron debarred' ); + + $schema->storage->txn_rollback; +}; -- 2.50.1 (Apple Git-155)