From d76de98a1bc627105b390b3f5ac8548ba3d415e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Cohen=20Arazi?= Date: Wed, 28 Jan 2026 15:33:54 -0300 Subject: [PATCH] Bug 41728: Add Koha::Availability::Result base class This patch introduces a standardized result class for availability checks, providing a clean API with proper methods instead of raw hashrefs. The Koha::Availability::Result class provides: - add_blocker() - Add conditions that prevent the action - add_confirmation() - Add conditions requiring user confirmation - add_warning() - Add informational messages - set_context() - Store related objects (item, checkout, patron, etc.) - available() - Check if action can proceed (no blockers) - needs_confirmation() - Check if confirmations are required - Accessor methods for all categories - to_hashref() for backward compatibility This design: - Provides a consistent interface across all availability checks - Makes the API more discoverable and self-documenting - Enables future enhancements without breaking changes - Follows OO principles with proper encapsulation - Can be used as base for checkout, hold, renewal availability Test plan: 1. Apply patch 2. Run: $ ktd --shell k$ prove t/Koha/Availability/Result.t => SUCCESS: Tests pass! 3. Verify all methods work correctly 4. Sign off :-D --- Koha/Availability/Result.pm | 239 +++++++++++++++++++++++++++++++++++ t/Koha/Availability/Result.t | 141 +++++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 Koha/Availability/Result.pm create mode 100755 t/Koha/Availability/Result.t diff --git a/Koha/Availability/Result.pm b/Koha/Availability/Result.pm new file mode 100644 index 0000000000..8c48049a3c --- /dev/null +++ b/Koha/Availability/Result.pm @@ -0,0 +1,239 @@ +package Koha::Availability::Result; + +# 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; + +=head1 NAME + +Koha::Availability::Result - Base class for availability check results + +=head1 SYNOPSIS + + my $result = Koha::Availability::Result->new(); + + $result->add_blocker( BadBarcode => $barcode ); + $result->add_confirmation( NotIssued => $barcode ); + $result->add_warning( withdrawn => 1 ); + + if ( $result->available ) { + # Proceed with action + } + +=head1 DESCRIPTION + +This class provides a standardized structure for availability check results +across different operations (check-in, checkout, hold, renewal, etc.). + +Results are categorized into: +- blockers: Conditions that prevent the action +- confirmations: Conditions requiring user confirmation +- warnings: Informational messages that don't prevent the action + +=head1 API + +=head2 Class methods + +=head3 new + + my $result = Koha::Availability::Result->new(); + +Constructor. Creates a new result object with empty blockers, confirmations, +and warnings. + +=cut + +sub new { + my ($class) = @_; + + my $self = { + blockers => {}, + confirmations => {}, + warnings => {}, + context => {}, + }; + + return bless $self, $class; +} + +=head2 Instance methods + +=head3 add_blocker + + $result->add_blocker( $key => $value ); + +Adds a blocker. Blockers prevent the action from proceeding. + +=cut + +sub add_blocker { + my ( $self, $key, $value ) = @_; + $self->{blockers}->{$key} = $value; + return $self; +} + +=head3 add_confirmation + + $result->add_confirmation( $key => $value ); + +Adds a confirmation requirement. Confirmations require user acknowledgment +before proceeding. + +=cut + +sub add_confirmation { + my ( $self, $key, $value ) = @_; + $self->{confirmations}->{$key} = $value; + return $self; +} + +=head3 add_warning + + $result->add_warning( $key => $value ); + +Adds a warning. Warnings are informational and don't prevent the action. + +=cut + +sub add_warning { + my ( $self, $key, $value ) = @_; + $self->{warnings}->{$key} = $value; + return $self; +} + +=head3 set_context + + $result->set_context( $key => $value ); + +Sets a context value. Context provides additional information about the +availability check (e.g., related objects like item, checkout, patron). + +=cut + +sub set_context { + my ( $self, $key, $value ) = @_; + $self->{context}->{$key} = $value; + return $self; +} + +=head3 available + + if ( $result->available ) { ... } + +Returns true if there are no blockers, false otherwise. + +=cut + +sub available { + my ($self) = @_; + return keys %{ $self->{blockers} } == 0; +} + +=head3 needs_confirmation + + if ( $result->needs_confirmation ) { ... } + +Returns true if there are confirmations required, false otherwise. + +=cut + +sub needs_confirmation { + my ($self) = @_; + return keys %{ $self->{confirmations} } > 0; +} + +=head3 blockers + + my $blockers = $result->blockers; + +Returns the blockers hashref. + +=cut + +sub blockers { + my ($self) = @_; + return $self->{blockers}; +} + +=head3 confirmations + + my $confirmations = $result->confirmations; + +Returns the confirmations hashref. + +=cut + +sub confirmations { + my ($self) = @_; + return $self->{confirmations}; +} + +=head3 warnings + + my $warnings = $result->warnings; + +Returns the warnings hashref. + +=cut + +sub warnings { + my ($self) = @_; + return $self->{warnings}; +} + +=head3 context + + my $context = $result->context; + my $item = $result->context->{item}; + +Returns the context hashref. + +=cut + +sub context { + my ($self) = @_; + return $self->{context}; +} + +=head3 to_hashref + + my $hashref = $result->to_hashref; + +Returns a plain hashref representation of the result, suitable for +backward compatibility or serialization. + +=cut + +sub to_hashref { + my ($self) = @_; + + return { + blockers => $self->{blockers}, + confirms => $self->{confirmations}, + warnings => $self->{warnings}, + %{ $self->{context} }, + }; +} + +=head1 AUTHOR + +Koha Development Team + +=cut + +1; diff --git a/t/Koha/Availability/Result.t b/t/Koha/Availability/Result.t new file mode 100755 index 0000000000..2d3059f356 --- /dev/null +++ b/t/Koha/Availability/Result.t @@ -0,0 +1,141 @@ +#!/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 => 9; +use Test::NoWarnings; + +use Koha::Availability::Result; + +subtest 'new() creates empty result' => sub { + + plan tests => 5; + + my $result = Koha::Availability::Result->new(); + + isa_ok( $result, 'Koha::Availability::Result', 'new() returns Result object' ); + is( ref( $result->blockers ), 'HASH', 'blockers is a hashref' ); + is( ref( $result->confirmations ), 'HASH', 'confirmations is a hashref' ); + is( ref( $result->warnings ), 'HASH', 'warnings is a hashref' ); + is( ref( $result->context ), 'HASH', 'context is a hashref' ); +}; + +subtest 'add_blocker()' => sub { + + plan tests => 3; + + my $result = Koha::Availability::Result->new(); + + $result->add_blocker( test_blocker => 'value' ); + + is( $result->blockers->{test_blocker}, 'value', 'blocker added' ); + is( keys %{ $result->blockers }, 1, 'one blocker present' ); + isa_ok( $result->add_blocker( another => 1 ), 'Koha::Availability::Result', 'returns self for chaining' ); +}; + +subtest 'add_confirmation()' => sub { + + plan tests => 3; + + my $result = Koha::Availability::Result->new(); + + $result->add_confirmation( test_confirm => 'value' ); + + is( $result->confirmations->{test_confirm}, 'value', 'confirmation added' ); + is( keys %{ $result->confirmations }, 1, 'one confirmation present' ); + isa_ok( $result->add_confirmation( another => 1 ), 'Koha::Availability::Result', 'returns self for chaining' ); +}; + +subtest 'add_warning()' => sub { + + plan tests => 3; + + my $result = Koha::Availability::Result->new(); + + $result->add_warning( test_warning => 'value' ); + + is( $result->warnings->{test_warning}, 'value', 'warning added' ); + is( keys %{ $result->warnings }, 1, 'one warning present' ); + isa_ok( $result->add_warning( another => 1 ), 'Koha::Availability::Result', 'returns self for chaining' ); +}; + +subtest 'set_context()' => sub { + + plan tests => 3; + + my $result = Koha::Availability::Result->new(); + + $result->set_context( item => 'test_item' ); + + is( $result->context->{item}, 'test_item', 'context value set' ); + is( keys %{ $result->context }, 1, 'one context value present' ); + isa_ok( + $result->set_context( patron => 'test_patron' ), 'Koha::Availability::Result', + 'returns self for chaining' + ); +}; + +subtest 'available()' => sub { + + plan tests => 2; + + my $result = Koha::Availability::Result->new(); + + ok( $result->available, 'available when no blockers' ); + + $result->add_blocker( test => 1 ); + + ok( !$result->available, 'not available when blockers present' ); +}; + +subtest 'needs_confirmation()' => sub { + + plan tests => 2; + + my $result = Koha::Availability::Result->new(); + + ok( !$result->needs_confirmation, 'no confirmation needed when empty' ); + + $result->add_confirmation( test => 1 ); + + ok( $result->needs_confirmation, 'confirmation needed when confirmations present' ); +}; + +subtest 'to_hashref()' => sub { + + plan tests => 6; + + my $result = Koha::Availability::Result->new(); + + $result->add_blocker( blocker1 => 'b1' ); + $result->add_confirmation( confirm1 => 'c1' ); + $result->add_warning( warning1 => 'w1' ); + $result->set_context( item => 'test_item' ); + $result->set_context( patron => 'test_patron' ); + + my $hashref = $result->to_hashref(); + + is( ref($hashref), 'HASH', 'returns hashref' ); + is( $hashref->{blockers}->{blocker1}, 'b1', 'blockers included' ); + is( $hashref->{confirms}->{confirm1}, 'c1', 'confirmations included as confirms' ); + is( $hashref->{warnings}->{warning1}, 'w1', 'warnings included' ); + is( $hashref->{item}, 'test_item', 'context item included at top level' ); + is( $hashref->{patron}, 'test_patron', 'context patron included at top level' ); +}; -- 2.50.1 (Apple Git-155)