Bug 37762: ISO18626 KOHA classes

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Pedro Amorim 2025-09-09 15:20:05 +00:00 committed by Lucas Gass
parent 2f17713637
commit 8e87fae1cc
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
18 changed files with 2520 additions and 2 deletions

291
Koha/ILL/ISO18626.pm Normal file
View File

@ -0,0 +1,291 @@
package Koha::ILL::ISO18626;
# Copyright PTFS Europe 2025
#
# 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 JSON qw( encode_json decode_json );
use File::Basename qw( dirname );
use Koha::ILL::ISO18626::RequestingAgencies;
use Koha::DateUtils qw( dt_from_string );
=head1 NAME
Koha::ILL::ISO18626 - Koha ILL ISO18626 class
=cut
=head2 Class methods
=head3 is_invalid
=cut
sub is_invalid {
my ( $definition, $json ) = @_;
my $spec_file = dirname(__FILE__) . "/../../api/v1/swagger/swagger_bundle.json";
if ( !-f $spec_file ) {
$spec_file = dirname(__FILE__) . "/../../api/v1/swagger/swagger.yaml";
}
my $schema = JSON::Validator::Schema::OpenAPIv2->new($spec_file);
$schema->resolve( $schema->data->{definitions}->{$definition} );
my @errors = $schema->validate($json);
my @formatted_errors;
for my $error (@errors) {
my @details = @{ $error->{details} };
my $mapped_details = join( ', ', map { "$_" } @details );
push @formatted_errors,
{ errorValue => $error->{path} . ': ' . $mapped_details, errorType => 'BadlyFormedMessage' };
}
return \@formatted_errors if @formatted_errors;
return 0;
}
=head2 is_auth_ok
my $is_auth_ok = is_auth_ok($json);
Checks the validity of the requestingAgencyAuthentication against the database.
Returns the Koha::ILL::ISO18626::RequestingAgency object if a matching accountId and securityCode are found, 0 otherwise.
=cut
sub is_auth_ok {
my ( $messageType, $json ) = @_;
my $accountId = $json->{$messageType}->{header}->{requestingAgencyAuthentication}->{accountId};
my $securityCode = $json->{$messageType}->{header}->{requestingAgencyAuthentication}->{securityCode};
my $requesting_agency =
Koha::ILL::ISO18626::RequestingAgencies->find( { account_id => $accountId, securityCode => $securityCode } );
return $requesting_agency if $requesting_agency;
return 0;
}
=head3 confirmation_response
This function returns an openapi ISO18626 message confirmation response based on the ISO18626 messages's type and body.
e.g. If the message_type is request, it returns a requestConfirmation message
It does not verify if:
- The message type is valid.
- The message is valid.
These things should be done beforehand
=cut
sub confirmation_response {
my ( $message_type, $body, $requesting_agency ) = @_;
my $json = JSON::decode_json($body);
my $confirmation_message_type = $message_type . 'Confirmation';
my ( $iso18626_request, $response, $action );
if ( $message_type eq 'request' ) {
#
# Handle incoming request message
#
my $request = {
supplyingAgencyId => $json->{request}->{header}->{supplyingAgencyId},
iso18626_requesting_agency_id => $requesting_agency->iso18626_requesting_agency_id,
requestingAgencyRequestId => $json->{request}->{header}->{requestingAgencyRequestId},
service_type => $json->{request}->{serviceInfo}->{serviceType},
_json_payload => $json
};
$iso18626_request = Koha::ILL::ISO18626::Request->new($request)->store();
$iso18626_request->add_message( { type => $message_type, message => $body } );
#
# Prepare outgoing requestConfirmation response
#
$response = {
$confirmation_message_type => {
confirmationHeader => {
timestamp => _now_utc_str(),
timestampReceived => _now_utc_str(),
messageStatus => 'OK',
}
}
};
} elsif ( $message_type eq 'requestingAgencyMessage' ) {
#
# Handle incoming requestingAgencyMessage message
#
my $supplyingAgencyRequestId =
$json->{requestingAgencyMessage}->{header}->{supplyingAgencyRequestId};
$iso18626_request = Koha::ILL::ISO18626::Requests->find($supplyingAgencyRequestId);
return {
status => 400,
openapi => error_response(
$confirmation_message_type,
[
{
errorValue => "supplyingAgencyRequestId: " . $supplyingAgencyRequestId,
errorType => "UnrecognizedDataValue"
}
]
)
} unless $iso18626_request;
$action = $json->{$message_type}->{action};
my $requestingAgencyMessage = $iso18626_request->add_message( { type => $message_type, message => $body } );
# Verify if the provided action is supported
my $supported_action_types = supported_action_types($iso18626_request);
return {
status => 400,
openapi => error_response(
$confirmation_message_type,
[
{
errorValue => "action: " . $json->{$message_type}->{action},
errorType => "UnsupportedActionType"
}
]
)
}
unless ( grep { $_ eq $json->{$message_type}->{action} } @$supported_action_types );
#
# Do the things that need to be done depending on the action
#
if ( $action eq 'Cancel' || $action eq 'Renew' ) {
$iso18626_request->pending_requesting_agency_action($action)->store();
}
# TODO: Only send out supplyingAgencyMessage if $action == statusRequest (?)
#$iso18626_request->progress_request( 'requestingAgency', { message => $requestingAgencyMessage } ); #TODO: Rethink this but good enough for now
#
# Prepare outgoing requestingAgencyMessageConfirmation response
#
$response = {
$confirmation_message_type => {
confirmationHeader => {
timestamp => _now_utc_str(),
timestampReceived => _now_utc_str(),
messageStatus => 'OK',
},
action => $json->{$message_type}->{action},
}
};
}
#TODO: Handle supplyingAgencyMessage ?
# IF a supplyingAgencyMessage is received, it means this Koha is acting as a requesting agency and we need to handle Koha::ILL::Request instead of Koha::ILL::ISO18626::Request (?)
# Ensure the produced confirmation message is ISO18626 conformant
my $validation_errors = is_invalid( $confirmation_message_type, $response );
if ($validation_errors) {
return {
status => 400,
openapi => error_response(
$confirmation_message_type,
$validation_errors
)
};
}
# All good, save the message and return the response
sleep 1;
$iso18626_request->add_message( { type => $confirmation_message_type, message => JSON::encode_json($response) } );
return { status => 201, openapi => $response };
}
=head3 _now_utc_str
=cut
sub _now_utc_str {
return dt_from_string( undef, undef, 'UTC' )->strftime('%Y-%m-%dT%H:%M:%SZ');
}
=head3 error_response
=cut
sub error_response {
my ( $message_type, $error_data ) = @_;
return {
$message_type => {
confirmationHeader => {
timestamp => _now_utc_str(),
timestampReceived => _now_utc_str(),
messageStatus => 'ERROR',
},
errorData => $error_data,
}
};
}
=head3 message_types
=cut
sub message_types {
return [
'request',
'requestConfirmation',
'requestingAgencyMessage',
'requestingAgencyMessageConfirmation',
'supplyingAgencyMessage',
'supplyingAgencyMessageConfirmation'
];
}
=head3 supported_action_types
Returns an array of supported action types for a given ISO18626 request.
Returns an empty array if the request has already been completed.
=cut
sub supported_action_types {
my ($iso18626_request) = @_;
return []
if $iso18626_request->status =~ /^(?:Cancelled|CopyCompleted|CompletedWithoutReturn|LoanCompleted|Unfilled)$/;
return [
'StatusRequest',
'Received',
'Cancel',
# TODO: Uncomment these once implemented
#'Renew',
#'HoldReturn',
#'ShippedReturn',
#'ShippedForward',
#'Notification',
#'Lost',
];
}
1;

View File

@ -0,0 +1,40 @@
package Koha::ILL::ISO18626::Message;
# Copyright Open Fifth 2026
#
# 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 base qw(Koha::Object);
=head1 NAME
Koha::ILL::ISO18626::Message - Koha ILL ISO18626 message Object class
=cut
=head2 Internal methods
=head3 _type
=cut
sub _type {
return 'Iso18626Message';
}
1;

View File

@ -0,0 +1,51 @@
package Koha::ILL::ISO18626::Messages;
# Copyright Open Fifth 2026
#
# 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 Koha::Database;
use Koha::ILL::ISO18626::Message;
use base qw(Koha::Objects);
=head1 NAME
Koha::ILL::Messages - Koha ISO18626 messages Object class
=cut
=head2 Internal methods
=head3 _type
=cut
sub _type {
return 'Iso18626Message';
}
=head3 object_class
=cut
sub object_class {
return 'Koha::ILL::ISO18626::Message';
}
1;

View File

@ -0,0 +1,407 @@
package Koha::ILL::ISO18626::Request;
# Copyright Open Fifth 2026
#
# 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 Koha::ILL::ISO18626::Messages;
use Koha::REST::V1;
use XML::LibXML;
use JSON qw( encode_json decode_json );
use File::Basename qw( dirname );
use Koha::Biblios;
use Koha::ILL::ISO18626::RequestingAgency;
use Koha::DateUtils qw( dt_from_string );
use base qw(Koha::Object);
=head1 NAME
Koha::ILL::ISO18626::Request - Koha ILL ISO18626 request Object class
=cut
=head2 Internal methods
=head3 new
Intercept the 'new' I<Koha::Object> lifecycle method
If a supplierUniqueRecordId exists in the request payload, the
corresponding bib record is linked to this request on creation
=cut
sub new {
my ( $class, $params ) = @_;
my $json = delete $params->{_json_payload};
if ( $json && $json->{request}->{bibliographicInfo}->{supplierUniqueRecordId} ) {
my $supplierUniqueRecordId = $json->{request}->{bibliographicInfo}->{supplierUniqueRecordId};
my $biblio = Koha::Biblios->find($supplierUniqueRecordId);
$params->{biblio_id} = $biblio->biblionumber if $biblio;
}
my $self = $class->SUPER::new($params);
return $self;
}
=head3 add_message
Add the I<Koha::ILL::ISO18626::Message> to this ISO18626 request
=cut
sub add_message {
my ( $self, $params ) = @_;
my $type = $params->{type};
my $message = $params->{message};
if ( ref $message eq 'HASH' ) {
$message = encode_json($message);
}
return $self->_result->add_to_iso18626_messages(
{
type => $type,
content => $message,
}
);
}
=head3 messages
Return the I<Koha::ILL::ISO18626::Messages> for this ISO18626 request
=cut
sub messages {
my ($self) = @_;
my $messages = $self->_result->iso18626_messages->search(
{},
{ order_by => { -desc => 'timestamp' } }
);
return Koha::ILL::ISO18626::Messages->_new_from_dbic($messages);
}
=head3 send_message
Send a message to the requesting agency
# $message must be a valid XML string.
=cut
sub send_message {
my ( $self, $type, $message ) = @_;
# my $requesting_agency = $self->_result->requesting_agency;
# return unless $requesting_agency->callback_endpoint;
# my $ua = LWP::UserAgent->new( agent => 'Koha ILL' );
# $ua->agent( 'Koha/' . Koha::version() );
# my $response = $ua->post(
# $requesting_agency->callback_endpoint,
# Content_Type => 'application/xml',
# Content => $message,
# );
# if ( $response->is_success ) {
#FIXME: Better way to convert this to json other than invoking Koha::REST::V1 ?
# my $parser = XML::LibXML->new();
# my $doc = $parser->parse_string($message);
# my $root = $doc->documentElement();
# my $json = Koha::REST::V1::parse_xml($root);
# $json = JSON::encode_json($json);
$self->add_message( { type => $type, message => $message } );
return 1;
# }
# else {
# $self->_result->add_message( 'Request', "Failed to send message: " . $response->status_line );
# return;
# }
}
=head3 requesting_agency
Returns the associated Koha::ILL::ISO18626::RequestingAgency object.
=cut
sub requesting_agency {
my ($self) = @_;
my $ra_rs = $self->_result->iso18626_requesting_agency;
return unless $ra_rs;
return Koha::ILL::ISO18626::RequestingAgency->_new_from_dbic($ra_rs);
}
=head3 hold
my $hold = $iso18626_request->hold();
Method that returns the related I<Koha::Hold>
=cut
sub hold {
my ($self) = @_;
my $hold_rs = $self->_result->reserve;
return unless $hold_rs;
return Koha::Hold->_new_from_dbic($hold_rs);
}
=head3 get_checkout
Returns the related Koha::Checkout obj for this ISO18626 request
=cut
sub get_checkout {
my ($self) = @_;
my $rs = $self->_result->issue;
return unless $rs;
return Koha::Checkout->_new_from_dbic($rs);
}
=head3 progress_request
Progress the request by sending a message to the requesting agency with the status of the request.
Params:
- actor: supplyingAgency or requestingAgency
- params: may contain status, messageInfoNote, message
=cut
sub progress_request {
my ( $self, $actor, $params ) = @_;
return unless $actor;
my $resulting_status = $self->status;
my $old_status = $self->status;
my $new_status = $params->{status};
my $reasonForMessage = 'RequestResponse';
my $messageInfoNote = $params->{messageInfoNote} // undef;
my $answerYesNo = $params->{answerYesNo} // undef;
my $expectedDeliveryDate = _format_iso_payload_date_param( $params->{expectedDeliveryDate} );
my $reasonUnfilled = $params->{reasonUnfilled} // undef;
my $reasonRetry = $params->{reasonRetry} // undef;
my $courierName =
( $params->{courierName}
&& $params->{reasonRetry} eq 'ReqDelMethodNotSupp'
&& $params->{deliveryMethod}
&& grep { $_ eq 'Courier' } @{ $params->{deliveryMethod} } ) ? $params->{courierName} : undef;
my $deliveryMethod = $params->{deliveryMethod}
&& $params->{reasonRetry} eq 'ReqDelMethodNotSupp' ? $params->{deliveryMethod} : undef;
my $edition =
$params->{edition} && $params->{reasonRetry} eq 'ReqEditionNotPossible' ? $params->{edition} : undef;
my $itemFormat =
$params->{itemFormat} && $params->{reasonRetry} eq 'ReqFormatNotPossible' ? $params->{itemFormat} : undef;
my $loanCondition = $params->{loanCondition}
&& $params->{reasonRetry} eq 'MustMeetLoanCondition' ? $params->{loanCondition} : undef;
my $offeredCostsCurrencyCode = $params->{offeredCostsCurrencyCode}
&& $params->{reasonRetry} eq 'CostExceedsMaxCost' ? $params->{offeredCostsCurrencyCode} : undef;
my $offeredCostsMonetaryValue = $params->{offeredCostsMonetaryValue}
&& $params->{reasonRetry} eq 'CostExceedsMaxCost' ? $params->{offeredCostsMonetaryValue} : undef;
my $paymentMethod =
$params->{paymentMethod} && $params->{reasonRetry} eq 'ReqPayMethodNotSupported'
? $params->{paymentMethod}
: undef;
my $retryBefore = $params->{retryBefore} && $new_status eq 'RetryPossible' ? $params->{retryBefore} : undef;
my $retryAfter = $params->{retryAfter} && $new_status eq 'RetryPossible' ? $params->{retryAfter} : undef;
my $serviceLevel = $params->{serviceLevel}
&& $params->{reasonRetry} eq 'ReqServLevelNotSupp' ? $params->{serviceLevel} : undef;
my $serviceType =
$params->{serviceType} && $params->{reasonRetry} eq 'ReqServTypeNotPossible' ? $params->{serviceType} : undef;
my $volume = $params->{volume} && $params->{reasonRetry} eq 'MultiVolAvail' ? $params->{volume} : undef;
if ( $actor eq 'requestingAgency' ) {
return unless $params->{message};
my $message = $params->{message};
my $json_message = JSON::decode_json( $message->content );
my $requesting_agency_action = $json_message->{requestingAgencyMessage}->{action};
if ( $requesting_agency_action eq 'StatusRequest' ) {
$reasonForMessage = 'StatusRequestResponse';
} elsif ( $requesting_agency_action eq 'Received' ) {
return;
}
} elsif ( $actor eq 'supplyingAgency' ) {
return unless $new_status;
$resulting_status = $new_status if $actor eq 'supplyingAgency';
if ( $resulting_status ne $old_status ) {
$reasonForMessage = 'StatusChange';
}
if ( $new_status eq 'Cancelled' ) {
$reasonForMessage = 'CancelResponse';
$resulting_status = $old_status unless $answerYesNo eq 'Y';
$self->pending_requesting_agency_action(undef)->store;
}
#TODO: Handle other statuses
# elsif ( $new_status eq 'Renew' ) { #Handle 'renew', it's not a status
# $reasonForMessage = 'RenewResponse';
# $answerYesNo = $params->{answerYesNo};
# $resulting_status = ?????;
# }
} else {
return;
}
my $check_out = $self->get_checkout;
my $check_out_item = $self->get_checkout ? $self->get_checkout->item : undef;
my $json = {
supplyingAgencyMessage => {
header => {
supplyingAgencyId => {
agencyIdType => 'ISIL',
agencyIdValue => 'sup_agency_value',
},
requestingAgencyRequestId => 'XYZ',
supplyingAgencyRequestId => $self->iso18626_request_id,
timestamp => _format_iso_payload_date_param('now'),
requestingAgencyId => {
agencyIdType => 'ISIL',
agencyIdValue => 'req_agency_value',
},
},
messageInfo => {
reasonForMessage => $reasonForMessage,
$answerYesNo ? ( answerYesNo => $answerYesNo ) : (),
$messageInfoNote ? ( note => $messageInfoNote ) : (),
$reasonUnfilled && $resulting_status eq 'Unfilled' ? ( reasonUnfilled => $reasonUnfilled ) : (),
$reasonRetry && $resulting_status eq 'RetryPossible' ? ( reasonRetry => $reasonRetry ) : (),
},
statusInfo => {
status => $resulting_status,
$expectedDeliveryDate ? ( expectedDeliveryDate => $expectedDeliveryDate ) : (),
$check_out ? ( dueDate => $check_out->date_due ) : (),
lastChange => $self->updated_on,
},
$resulting_status eq 'RetryPossible'
? (
retryInfo => {
$loanCondition ? ( loanCondition => $loanCondition ) : (),
$edition ? ( edition => [ split /,/, $edition ] ) : (),
$itemFormat ? ( itemFormat => $itemFormat ) : (),
$volume ? ( volume => [ split /,/, $volume ] ) : (),
$serviceType ? ( serviceType => $serviceType ) : (),
$serviceLevel ? ( serviceLevel => $serviceLevel ) : (),
$deliveryMethod ? ( deliveryMethod => $deliveryMethod ) : (),
$courierName ? ( courierName => $courierName ) : (),
$offeredCostsMonetaryValue && $offeredCostsCurrencyCode
? (
offeredCosts => [
{ currencyCode => $offeredCostsCurrencyCode, monetaryValue => $offeredCostsMonetaryValue }
]
)
: (),
$paymentMethod ? ( paymentMethod => $paymentMethod ) : (),
$retryBefore ? ( retryBefore => $retryBefore ) : (),
$retryAfter ? ( retryAfter => $retryAfter ) : (),
}
)
: (),
( $resulting_status eq 'Loaned' && $check_out_item )
? (
deliveryInfo => {
dateSent => $check_out->issuedate,
itemId => $check_out_item->barcode,
#itemFormat => ['PaperCopy'], # TODO: Implement: Must come from payload (only needed if specified by RA?)
serviceType => 'Loan',
#deliveryMethod => 'Email', # TODO: Implement: Must come from payload (only needed if specified by RA?)
#paymentMethod => 'BankTransfer' # TODO: Implement: Must come from payload (only needed if specified by RA?)
}
)
: (),
# TODO: Implement shippingInfo
# shippingInfo => {
# courierName => 'DHL',
# trackingId => [ '123', 'abc' ],
# insurance => 'N',
# insuranceThirdParty => 'N',
# thirdPartyName => 'Some name, if insuranceThirdParty',
# insuranceCosts => [ { currencyCode => 'EUR', monetaryValue => '50.00' } ]
# }
},
};
my $spec_file = dirname(__FILE__) . "/../../../api/v1/swagger/swagger_bundle.json";
if ( !-f $spec_file ) {
$spec_file = dirname(__FILE__) . "/../../../api/v1/swagger/swagger.yaml";
}
my $schema = JSON::Validator::Schema::OpenAPIv2->new($spec_file);
$schema->resolve( $schema->data->{definitions}->{supplyingAgencyMessage} );
my @errors = $schema->validate($json);
if (@errors) {
#TODO: Throw exception
use Data::Dumper;
$Data::Dumper::Maxdepth = 4;
warn Dumper( '##### 2 #######################################################line: ' . __LINE__ );
warn Dumper( \@errors );
warn Dumper('##### end2 #######################################################');
}
$self->status($resulting_status)->store;
$self->send_message( 'supplyingAgencyMessage', $json );
return 1;
}
=head2 _format_iso_payload_date_param
Normalizes a date string to C<YYYY-MM-DD HH:MM:SS>
=cut
sub _format_iso_payload_date_param {
my ($date_str) = @_;
return unless $date_str;
my $dt = eval { $date_str eq 'now' ? dt_from_string() : dt_from_string($date_str) };
return $dt ? $dt->strftime('%Y-%m-%d %H:%M:%S') : $date_str;
}
=head3 _type
=cut
sub _type {
return 'Iso18626Request';
}
1;

View File

@ -0,0 +1,51 @@
package Koha::ILL::ISO18626::RequestingAgencies;
# Copyright Open Fifth 2026
#
# 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 Koha::Database;
use Koha::ILL::ISO18626::RequestingAgency;
use base qw(Koha::Objects);
=head1 NAME
Koha::ILL::RequestingAgencies - Koha ISO18626 Requesting Agencies Object class
=cut
=head2 Internal methods
=head3 _type
=cut
sub _type {
return 'Iso18626RequestingAgency';
}
=head3 object_class
=cut
sub object_class {
return 'Koha::ILL::ISO18626::RequestingAgency';
}
1;

View File

@ -0,0 +1,66 @@
package Koha::ILL::ISO18626::RequestingAgency;
# Copyright Open Fifth 2026
#
# 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 base qw(Koha::Object);
=head1 NAME
Koha::ILL::ISO18626::RequestingAgency - Koha ILL ISO18626 Requesting Agency Object class
=cut
=head2 Internal methods
=head3 to_api_mapping
This method returns the mapping for representing a Koha::ILL::ISO18626::RequestingAgency
object on the API.
=cut
sub to_api_mapping {
return {
borrowernumber => 'patron_id',
};
}
=head3 ill_partner
Return the ill_partner patron for this requesting agency
=cut
sub ill_partner {
my ($self) = @_;
my $patrons_rs = $self->_result->patron;
return unless $patrons_rs;
return Koha::Patron->_new_from_dbic($patrons_rs);
}
=head3 _type
=cut
sub _type {
return 'Iso18626RequestingAgency';
}
1;

View File

@ -0,0 +1,67 @@
package Koha::ILL::ISO18626::Requests;
# Copyright Open Fifth 2026
#
# 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 Koha::Database;
use Koha::ILL::ISO18626::Request;
use base qw(Koha::Objects);
=head1 NAME
Koha::ILL::Requests - Koha ISO18626 requests Object class
=cut
=head2 Internal methods
=head3 get_request_by_issue_id
my $iso18626_request = Koha::ILL::ISO18626::Requests->get_request_by_issue_id( $c->{issue_id} );
Returns the request provided an issue_id.
=cut
sub get_request_by_issue_id {
my ( $self, $issue_id ) = @_;
return $self->find( { 'issue_id' => $issue_id } );
}
=head2 Internal methods
=head3 _type
=cut
sub _type {
return 'Iso18626Request';
}
=head3 object_class
=cut
sub object_class {
return 'Koha::ILL::ISO18626::Request';
}
1;

View File

@ -0,0 +1,209 @@
package Koha::REST::V1::ILL::ISO18626::RequestingAgencies;
# 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 Mojo::Base 'Mojolicious::Controller';
use Koha::ILL::ISO18626::RequestingAgencies;
use Try::Tiny qw( catch try );
=head1 NAME
Koha::REST::V1::ILL::ISO18626::RequestingAgencies
=head2 Operations
=head3 list
Controller function that handles listing Koha::ILL::ISO18626::RequestingAgency objects
=cut
sub list {
my $c = shift->openapi->valid_input or return;
return try {
my $reqs = $c->objects->search( Koha::ILL::ISO18626::RequestingAgencies->new );
return $c->render(
status => 200,
openapi => $reqs,
);
} catch {
$c->unhandled_exception($_);
};
}
=head3 get
Controller function that handles retrieving a single Koha::ILL::ISO18626::RequestingAgency object
=cut
sub get {
my $c = shift->openapi->valid_input or return;
return try {
my $iso18626_requesting_agency = $c->objects->find(
Koha::ILL::ISO18626::RequestingAgencies->search,
$c->param('iso18626_requesting_agency_id')
);
return $c->render_resource_not_found("ISO18626 requesting agency")
unless $iso18626_requesting_agency;
return $c->render(
status => 200,
openapi => $iso18626_requesting_agency
);
} catch {
$c->unhandled_exception($_);
};
}
=head3 add
Controller function that handles adding a new Koha::ILL::ISO18626::RequestingAgency object
=cut
sub add {
my $c = shift->openapi->valid_input or return;
return try {
Koha::Database->new->schema->txn_do(
sub {
my $body = $c->req->json;
my $requesting_agency = Koha::ILL::ISO18626::RequestingAgency->new_from_api($body)->store;
$c->res->headers->location(
$c->req->url->to_string . '/' . $requesting_agency->iso18626_requesting_agency_id );
return $c->render(
status => 201,
openapi => $c->objects->to_api($requesting_agency),
);
}
);
} catch {
my $to_api_mapping = Koha::ILL::ISO18626::RequestingAgency->new->to_api_mapping;
if ( blessed $_ ) {
if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
return $c->render(
status => 409,
openapi => { error => $_->error, conflict => $_->duplicate_id }
);
} elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
return $c->render(
status => 400,
openapi => { error => "Given " . $to_api_mapping->{ $_->broken_fk } . " does not exist" }
);
} elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
return $c->render(
status => 400,
openapi => { error => "Given " . $to_api_mapping->{ $_->parameter } . " does not exist" }
);
} elsif ( $_->isa('Koha::Exceptions::PayloadTooLarge') ) {
return $c->render(
status => 413,
openapi => { error => $_->error }
);
}
}
$c->unhandled_exception($_);
};
}
=head3 update
Controller function that handles updating a Koha::ILL::ISO18626::RequestingAgency object
=cut
sub update {
my $c = shift->openapi->valid_input or return;
my $requesting_agency = Koha::ILL::ISO18626::RequestingAgencies->find( $c->param('iso18626_requesting_agency_id') );
return $c->render_resource_not_found("ISO18626 requesting agency")
unless $requesting_agency;
return try {
Koha::Database->new->schema->txn_do(
sub {
my $body = $c->req->json;
$requesting_agency->set_from_api($body)->store;
$c->res->headers->location(
$c->req->url->to_string . '/' . $requesting_agency->iso18626_requesting_agency_id );
return $c->render(
status => 200,
openapi => $c->objects->to_api($requesting_agency),
);
}
);
} catch {
my $to_api_mapping = Koha::ILL::ISO18626::RequestingAgency->new->to_api_mapping;
if ( blessed $_ ) {
if ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
return $c->render(
status => 400,
openapi => { error => "Given " . $to_api_mapping->{ $_->broken_fk } . " does not exist" }
);
} elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
return $c->render(
status => 400,
openapi => { error => "Given " . $to_api_mapping->{ $_->parameter } . " does not exist" }
);
} elsif ( $_->isa('Koha::Exceptions::PayloadTooLarge') ) {
return $c->render(
status => 413,
openapi => { error => $_->error }
);
}
}
$c->unhandled_exception($_);
};
}
=head3 delete
=cut
sub delete {
my $c = shift->openapi->valid_input or return;
my $requesting_agency = Koha::ILL::ISO18626::RequestingAgencies->find( $c->param('iso18626_requesting_agency_id') );
return $c->render_resource_not_found("ISO18626 requesting agency")
unless $requesting_agency;
return try {
$requesting_agency->delete;
return $c->render_resource_deleted;
} catch {
$c->unhandled_exception($_);
};
}
1;

View File

@ -0,0 +1,103 @@
package Koha::REST::V1::ILL::ISO18626::Requests;
# 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 Mojo::Base 'Mojolicious::Controller';
use Koha::ILL::ISO18626::Requests;
use Try::Tiny qw( catch try );
=head1 NAME
Koha::REST::V1::ILL::ISO18626::Requests
=head2 Operations
=head3 list
Controller function that handles listing Koha::ILL::ISO18626::Request objects
=cut
sub list {
my $c = shift->openapi->valid_input or return;
return try {
my $reqs = $c->objects->search( Koha::ILL::ISO18626::Requests->new );
return $c->render(
status => 200,
openapi => $reqs,
);
} catch {
$c->unhandled_exception($_);
};
}
=head3 get
Controller function that handles retrieving a single Koha::ILL::ISO18626::Request object
=cut
sub get {
my $c = shift->openapi->valid_input or return;
return try {
my $iso18626_request =
$c->objects->find( Koha::ILL::ISO18626::Requests->search, $c->param('iso18626_request_id') );
return $c->render_resource_not_found("Iso18626 request")
unless $iso18626_request;
return $c->render(
status => 200,
openapi => $iso18626_request
);
} catch {
$c->unhandled_exception($_);
};
}
=head3 edit
Controller function that handles editing a single Koha::ILL::ISO18626::Request object
=cut
sub edit {
my $c = shift or return;
my $request_id = $c->param('iso18626_request_id');
my $request = Koha::ILL::ISO18626::Requests->find($request_id);
return try {
my $body = $c->req->json;
my $status = $body->{status};
$request->progress_request( 'supplyingAgency', $body ); #TODO: Rethink this but good enough for now
return $c->render(
status => 200,
openapi => $request->to_api
);
} catch {
return $c->unhandled_exception($_);
};
}
1;

View File

@ -1526,6 +1526,21 @@ __PACKAGE__->has_many(
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 iso18626_requesting_agency
Type: might_have
Related object: L<Koha::Schema::Result::Iso18626RequestingAgency>
=cut
__PACKAGE__->might_have(
"iso18626_requesting_agency",
"Koha::Schema::Result::Iso18626RequestingAgency",
{ "foreign.borrowernumber" => "self.borrowernumber" },
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 issues
Type: has_many
@ -2227,8 +2242,8 @@ Composing rels: L</user_permissions> -> permission
__PACKAGE__->many_to_many("permissions", "user_permissions", "permission");
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-11-03 14:19:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:NHrhTcf4p6JW5fNgfYiqeQ
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2026-04-14 10:19:11
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Tptr5nUuS5haVck624FS3Q
__PACKAGE__->belongs_to(
"library",

View File

@ -0,0 +1,150 @@
use utf8;
package Koha::Schema::Result::Iso18626Message;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Koha::Schema::Result::Iso18626Message
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 TABLE: C<iso18626_messages>
=cut
__PACKAGE__->table("iso18626_messages");
=head1 ACCESSORS
=head2 iso18626_message_id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
Internal message number
=head2 iso18626_request_id
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
Associated ISO18626 request
=head2 content
data_type: 'mediumtext'
is_nullable: 0
Message content (XML)
=head2 type
data_type: 'enum'
extra: {list => ["request","requestConfirmation","supplyingAgencyMessage","supplyingAgencyMessageConfirmation","requestingAgencyMessage","requestingAgencyMessageConfirmation"]}
is_nullable: 0
ISO18626 message type
=head2 timestamp
data_type: 'timestamp'
datetime_undef_if_invalid: 1
default_value: current_timestamp
is_nullable: 0
=cut
__PACKAGE__->add_columns(
"iso18626_message_id",
{ data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
"iso18626_request_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
"content",
{ data_type => "mediumtext", is_nullable => 0 },
"type",
{
data_type => "enum",
extra => {
list => [
"request",
"requestConfirmation",
"supplyingAgencyMessage",
"supplyingAgencyMessageConfirmation",
"requestingAgencyMessage",
"requestingAgencyMessageConfirmation",
],
},
is_nullable => 0,
},
"timestamp",
{
data_type => "timestamp",
datetime_undef_if_invalid => 1,
default_value => \"current_timestamp",
is_nullable => 0,
},
);
=head1 PRIMARY KEY
=over 4
=item * L</iso18626_message_id>
=back
=cut
__PACKAGE__->set_primary_key("iso18626_message_id");
=head1 RELATIONS
=head2 iso18626_request
Type: belongs_to
Related object: L<Koha::Schema::Result::Iso18626Request>
=cut
__PACKAGE__->belongs_to(
"iso18626_request",
"Koha::Schema::Result::Iso18626Request",
{ iso18626_request_id => "iso18626_request_id" },
{ is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-09-26 14:56:58
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3IbJ4SgKGHU+cXJL4LFqwg
=head2 koha_object_class
Missing POD for koha_object_class.
=cut
sub koha_object_class {
'Koha::ILL::ISO18626::Message';
}
=head2 koha_objects_class
Missing POD for koha_objects_class.
=cut
sub koha_objects_class {
'Koha::ILL::ISO18626::Messages';
}
1;

View File

@ -0,0 +1,276 @@
use utf8;
package Koha::Schema::Result::Iso18626Request;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Koha::Schema::Result::Iso18626Request
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 TABLE: C<iso18626_requests>
=cut
__PACKAGE__->table("iso18626_requests");
=head1 ACCESSORS
=head2 iso18626_request_id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
Internal request number
=head2 supplyingAgencyId
accessor: 'supplying_agency_id'
data_type: 'varchar'
is_nullable: 1
size: 80
Supplying agency ID
=head2 iso18626_requesting_agency_id
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
Associated ISO18626 requesting agency
=head2 created_on
data_type: 'timestamp'
datetime_undef_if_invalid: 1
default_value: current_timestamp
is_nullable: 0
Date and time the request was created
=head2 updated_on
data_type: 'timestamp'
datetime_undef_if_invalid: 1
default_value: current_timestamp
is_nullable: 0
Date and time the request was last updated
=head2 requestingAgencyRequestId
accessor: 'requesting_agency_request_id'
data_type: 'varchar'
is_nullable: 1
size: 80
Requesting agency request ID or number
=head2 status
data_type: 'enum'
default_value: 'RequestReceived'
extra: {list => ["RequestReceived","ExpectToSupply","WillSupply","Loaned","Overdue","Recalled","RetryPossible","Unfilled","HoldReturn","ReleaseHoldReturn","CopyCompleted","LoanCompleted","CompletedWithoutReturn","Cancelled"]}
is_nullable: 1
Current ISO18626 status of request
=head2 service_type
data_type: 'enum'
extra: {list => ["Copy","Loan","CopyOrLoan"]}
is_nullable: 0
ISO18626 service type
=head2 pending_requesting_agency_action
data_type: 'enum'
extra: {list => ["Cancel","Renew"]}
is_nullable: 1
ISO18626 Requesting Agency action that requires a manual response (yes or no)
=cut
__PACKAGE__->add_columns(
"iso18626_request_id",
{ data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
"supplyingAgencyId",
{
accessor => "supplying_agency_id",
data_type => "varchar",
is_nullable => 1,
size => 80,
},
"iso18626_requesting_agency_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
"created_on",
{
data_type => "timestamp",
datetime_undef_if_invalid => 1,
default_value => \"current_timestamp",
is_nullable => 0,
},
"updated_on",
{
data_type => "timestamp",
datetime_undef_if_invalid => 1,
default_value => \"current_timestamp",
is_nullable => 0,
},
"requestingAgencyRequestId",
{
accessor => "requesting_agency_request_id",
data_type => "varchar",
is_nullable => 1,
size => 80,
},
"status",
{
data_type => "enum",
default_value => "RequestReceived",
extra => {
list => [
"RequestReceived",
"ExpectToSupply",
"WillSupply",
"Loaned",
"Overdue",
"Recalled",
"RetryPossible",
"Unfilled",
"HoldReturn",
"ReleaseHoldReturn",
"CopyCompleted",
"LoanCompleted",
"CompletedWithoutReturn",
"Cancelled",
],
},
is_nullable => 1,
},
"service_type",
{
data_type => "enum",
extra => { list => ["Copy", "Loan", "CopyOrLoan"] },
is_nullable => 0,
},
"pending_requesting_agency_action",
{
data_type => "enum",
extra => { list => ["Cancel", "Renew"] },
is_nullable => 1,
},
);
=head1 PRIMARY KEY
=over 4
=item * L</iso18626_request_id>
=back
=cut
__PACKAGE__->set_primary_key("iso18626_request_id");
=head1 RELATIONS
=head2 iso18626_messages
Type: has_many
Related object: L<Koha::Schema::Result::Iso18626Message>
=cut
__PACKAGE__->has_many(
"iso18626_messages",
"Koha::Schema::Result::Iso18626Message",
{ "foreign.iso18626_request_id" => "self.iso18626_request_id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 iso18626_requesting_agency
Type: belongs_to
Related object: L<Koha::Schema::Result::Iso18626RequestingAgency>
=cut
__PACKAGE__->belongs_to(
"iso18626_requesting_agency",
"Koha::Schema::Result::Iso18626RequestingAgency",
{
iso18626_requesting_agency_id => "iso18626_requesting_agency_id",
},
{ is_deferrable => 1, on_delete => "RESTRICT", on_update => "CASCADE" },
);
=head2 old_reserve
Type: might_have
Related object: L<Koha::Schema::Result::OldReserve>
=cut
__PACKAGE__->might_have(
"old_reserve",
"Koha::Schema::Result::OldReserve",
{ "foreign.iso18626_request_id" => "self.iso18626_request_id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 reserve
Type: might_have
Related object: L<Koha::Schema::Result::Reserve>
=cut
__PACKAGE__->might_have(
"reserve",
"Koha::Schema::Result::Reserve",
{ "foreign.iso18626_request_id" => "self.iso18626_request_id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2026-03-04 14:57:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:T0PCb3b3SH9O7/f3TYDnTw
=head2 koha_object_class
Missing POD for koha_object_class.
=cut
sub koha_object_class {
'Koha::ILL::ISO18626::Request';
}
=head2 koha_objects_class
Missing POD for koha_objects_class.
=cut
sub koha_objects_class {
'Koha::ILL::ISO18626::Requests';
}
1;

View File

@ -0,0 +1,206 @@
use utf8;
package Koha::Schema::Result::Iso18626RequestingAgency;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Koha::Schema::Result::Iso18626RequestingAgency
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 TABLE: C<iso18626_requesting_agencies>
=cut
__PACKAGE__->table("iso18626_requesting_agencies");
=head1 ACCESSORS
=head2 iso18626_requesting_agency_id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
Internal requesting agency number
=head2 name
data_type: 'varchar'
is_nullable: 1
size: 80
Requesting agency name
=head2 borrowernumber
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
foreign key, linking this to the borrowers table (ILL partner patron)
=head2 type
data_type: 'enum'
extra: {list => ["DNUCNI","ICOLC","ISIL"]}
is_nullable: 0
ISO18626 agency type
=head2 account_id
data_type: 'varchar'
is_nullable: 0
size: 80
Authentication: Requesting agency account ID
=head2 securityCode
accessor: 'security_code'
data_type: 'varchar'
is_nullable: 0
size: 80
Authentication: Requesting agency security code
=head2 callback_endpoint
data_type: 'mediumtext'
is_nullable: 0
Callback endpoint to send messages back to
=cut
__PACKAGE__->add_columns(
"iso18626_requesting_agency_id",
{ data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
"name",
{ data_type => "varchar", is_nullable => 1, size => 80 },
"borrowernumber",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
"type",
{
data_type => "enum",
extra => { list => ["DNUCNI", "ICOLC", "ISIL"] },
is_nullable => 0,
},
"account_id",
{ data_type => "varchar", is_nullable => 0, size => 80 },
"securityCode",
{
accessor => "security_code",
data_type => "varchar",
is_nullable => 0,
size => 80,
},
"callback_endpoint",
{ data_type => "mediumtext", is_nullable => 0 },
);
=head1 PRIMARY KEY
=over 4
=item * L</iso18626_requesting_agency_id>
=back
=cut
__PACKAGE__->set_primary_key("iso18626_requesting_agency_id");
=head1 UNIQUE CONSTRAINTS
=head2 C<uniq_borrowernumber>
=over 4
=item * L</borrowernumber>
=back
=cut
__PACKAGE__->add_unique_constraint("uniq_borrowernumber", ["borrowernumber"]);
=head1 RELATIONS
=head2 borrowernumber
Type: belongs_to
Related object: L<Koha::Schema::Result::Borrower>
=cut
__PACKAGE__->belongs_to(
"borrowernumber",
"Koha::Schema::Result::Borrower",
{ borrowernumber => "borrowernumber" },
{ is_deferrable => 1, on_delete => "RESTRICT", on_update => "CASCADE" },
);
=head2 iso18626_requests
Type: has_many
Related object: L<Koha::Schema::Result::Iso18626Request>
=cut
__PACKAGE__->has_many(
"iso18626_requests",
"Koha::Schema::Result::Iso18626Request",
{
"foreign.iso18626_requesting_agency_id" => "self.iso18626_requesting_agency_id",
},
{ cascade_copy => 0, cascade_delete => 0 },
);
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2026-01-21 12:22:43
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:vqPIA624r/PznpUwlK2FBg
__PACKAGE__->belongs_to(
"patron",
"Koha::Schema::Result::Borrower",
{ borrowernumber => "borrowernumber" },
{
is_deferrable => 1,
join_type => "LEFT",
on_delete => "CASCADE",
on_update => "CASCADE",
},
);
=head2 koha_object_class
Missing POD for koha_object_class.
=cut
sub koha_object_class {
'Koha::ILL::ISO18626::RequestingAgency';
}
=head2 koha_objects_class
Missing POD for koha_objects_class.
=cut
sub koha_objects_class {
'Koha::ILL::ISO18626::RequestingAgencies';
}
1;

View File

@ -0,0 +1,58 @@
---
type: object
properties:
iso18626_request_id:
type: integer
description: Internal ILL request identifier
supplyingAgencyId:
type:
- string
- "null"
description: Internal supplying agency identifier
status:
type: string
description: ISO18626 request status
service_type:
description: ISO18626 request service type
$ref: "../definitions/ISO18626/types/serviceType.yaml"
pending_requesting_agency_action:
type:
- string
- "null"
enum:
- Cancel
- Renew
- null
description: Requesting Agency action that requires an action from the supplying agency
created_on:
type:
- string
- "null"
format: date-time
description: Date and time the request was created
updated_on:
type:
- string
- "null"
format: date-time
description: Date and time the request was last updated
requestingAgencyRequestId:
type: string
description: Requesting agency request ID or number
iso18626_requesting_agency_id:
type: integer
description: Associated ISO18626 requesting agency internal identifier
requesting_agency:
type:
- object
- "null"
description: Embeded object
messages:
type:
- array
- "null"
description: The linked messages (x-koha-embed)
required:
- service_type
- status
additionalProperties: false

View File

@ -0,0 +1,44 @@
---
type: object
properties:
iso18626_requesting_agency_id:
type: integer
description: Internal requesting agency number
readOnly: true
name:
type: string
description: Requesting agency name
patron_id:
type:
- integer
- "null"
description: Internal patron id
type:
type: string
enum:
- DNUCNI # Danish National Union Catalogue Non-ISIL Identifier
- ICOLC # https://icolc.net/consortia
- ISIL # ISO 15511
description: ISO18626 requesting agency type
account_id:
type: string
description: 'Authentication: Requesting agency account ID'
securityCode:
type: string
description: 'Authentication: Requesting agency security code'
callback_endpoint:
type: string
description: Callback endpoint to send messages back to
ill_partner:
description: ILL partner patron object
type:
- object
- "null"
required:
- iso18626_requesting_agency_id
- name
- type
- account_id
- securityCode
- callback_endpoint
additionalProperties: false

View File

@ -0,0 +1,281 @@
---
/ill/iso18626_requesting_agencies:
get:
x-mojo-to: ILL::ISO18626::RequestingAgencies#list
operationId: listIso18626IllrequestingAgencies
tags:
- iso18626_requesting_agencies
summary: List ISO18626 ILL requesting agencies
parameters:
- name: x-koha-embed
in: header
required: false
description: Embed list sent as a request header
type: array
items:
type: string
enum:
- ill_partner
- $ref: "../swagger.yaml#/parameters/page"
- $ref: "../swagger.yaml#/parameters/per_page"
- $ref: "../swagger.yaml#/parameters/match"
- $ref: "../swagger.yaml#/parameters/order_by"
- $ref: "../swagger.yaml#/parameters/q_param"
- $ref: "../swagger.yaml#/parameters/q_body"
- $ref: "../swagger.yaml#/parameters/request_id_header"
produces:
- application/json
responses:
"200":
description: A list of ISO18626 ILL requesting agencies
schema:
type: array
items:
$ref: "../swagger.yaml#/definitions/iso18626_requesting_agency"
"400":
description: |
Bad request. Possible `error_code` attribute values:
* `invalid_query`
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. 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:
ill: "1"
post:
x-mojo-to: ILL::ISO18626::RequestingAgencies#add
operationId: addIso18626IllrequestingAgencies
tags:
- iso18626_requesting_agencies
summary: Add ISO18626 requesting agency
consumes:
- application/json
produces:
- application/json
parameters:
- description: A JSON object containing information about the new requesting agency
in: body
name: body
required: true
schema:
$ref: "../swagger.yaml#/definitions/iso18626_requesting_agency"
responses:
201:
description: A successfully created ISO18626 requesting agency
schema:
items:
$ref: "../swagger.yaml#/definitions/iso18626_requesting_agency"
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: Ressource not found
schema:
$ref: "../swagger.yaml#/definitions/error"
409:
description: Conflict in creating resource
schema:
$ref: "../swagger.yaml#/definitions/error"
413:
description: Payload too large
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:
ill: 1
/ill/iso18626_requesting_agencies/{iso18626_requesting_agency_id}:
get:
x-mojo-to: ILL::ISO18626::RequestingAgencies#get
operationId: getIso18626IllrequestingAgency
tags:
- iso18626_requesting_agencies
summary: Retrieve a single ISO18626 ILL requesting agency
parameters:
- name: x-koha-embed
in: header
required: false
description: Embed list sent as a request header
type: array
items:
type: string
enum:
- ill_partner
- $ref: "../swagger.yaml#/parameters/iso18626_requesting_agency_id_pp"
produces:
- application/json
responses:
"200":
description: A single ISO18626 ILL requesting agency
schema:
$ref: "../swagger.yaml#/definitions/iso18626_requesting_agency"
"400":
description: |
Bad request. Possible `error_code` attribute values:
* `invalid_query`
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. 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:
ill: "1"
put:
x-mojo-to: ILL::ISO18626::RequestingAgencies#update
operationId: updateIso18626IllrequestingAgency
tags:
- iso18626_requesting_agencies
summary: Update ISO18626 ILL requesting agency
consumes:
- application/json
produces:
- application/json
parameters:
- $ref: "../swagger.yaml#/parameters/iso18626_requesting_agency_id_pp"
- name: body
in: body
description: A JSON object containing new information about existing ISO18626 ILL requesting agency
required: true
schema:
$ref: "../swagger.yaml#/definitions/iso18626_requesting_agency"
responses:
200:
description: A successfully updated ISO18626 ILL requesting agency
schema:
items:
$ref: "../swagger.yaml#/definitions/iso18626_requesting_agency"
400:
description: Bad request
schema:
$ref: "../swagger.yaml#/definitions/error"
403:
description: Access forbidden
schema:
$ref: "../swagger.yaml#/definitions/error"
404:
description: Ressource not found
schema:
$ref: "../swagger.yaml#/definitions/error"
409:
description: Conflict in updating resource
schema:
$ref: "../swagger.yaml#/definitions/error"
413:
description: Payload too large
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:
erm: 1
delete:
x-mojo-to: ILL::ISO18626::RequestingAgencies#delete
operationId: deleteIso18626IllrequestingAgency
tags:
- iso18626_requesting_agencies
summary: Delete ISO18626 ILL requesting agency
produces:
- application/json
parameters:
- $ref: "../swagger.yaml#/parameters/iso18626_requesting_agency_id_pp"
responses:
204:
description: ISO18626 ILL requesting agency deleted
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: Ressource not found
schema:
$ref: "../swagger.yaml#/definitions/error"
409:
description: Conflict in deleting resource
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:
erm: 1

View File

@ -0,0 +1,185 @@
---
/ill/iso18626_requests:
get:
x-mojo-to: ILL::ISO18626::Requests#list
operationId: listIso18626Illrequests
tags:
- iso18626_requests
summary: List ISO18626 ILL requests
parameters:
- $ref: "../swagger.yaml#/parameters/page"
- $ref: "../swagger.yaml#/parameters/per_page"
- $ref: "../swagger.yaml#/parameters/match"
- $ref: "../swagger.yaml#/parameters/order_by"
- $ref: "../swagger.yaml#/parameters/q_param"
- $ref: "../swagger.yaml#/parameters/q_body"
- $ref: "../swagger.yaml#/parameters/request_id_header"
- name: x-koha-embed
in: header
required: false
description: Embed list sent as a request header
type: array
items:
type: string
enum:
- messages
- requesting_agency
collectionFormat: csv
produces:
- application/json
responses:
"200":
description: A list of ISO18626 ILL requests
schema:
type: array
items:
$ref: "../swagger.yaml#/definitions/iso18626_request"
"400":
description: |
Bad request. Possible `error_code` attribute values:
* `invalid_query`
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. 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:
ill: "1"
/ill/iso18626_requests/{iso18626_request_id}:
get:
x-mojo-to: ILL::ISO18626::Requests#get
operationId: getIso18626Illrequest
tags:
- iso18626_requests
summary: Retrieve a single ISO18626 ILL request
parameters:
- $ref: "../swagger.yaml#/parameters/iso18626_request_id_pp"
- name: x-koha-embed
in: header
required: false
description: Embed list sent as a request header
type: array
items:
type: string
enum:
- messages
- requesting_agency
collectionFormat: csv
produces:
- application/json
responses:
"200":
description: A single ISO18626 ILL request
schema:
$ref: "../swagger.yaml#/definitions/iso18626_request"
"400":
description: |
Bad request. Possible `error_code` attribute values:
* `invalid_query`
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. 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:
ill: "1"
patch:
x-mojo-to: ILL::ISO18626::Requests#edit
operationId: editIso18626Illrequest
tags:
- iso18626_requests
summary: Edit a single ISO18626 ILL request
produces:
- application/json
parameters:
- description: A JSON object containing the new status
in: body
name: status
required: true
schema:
type: object
properties:
status:
description: The status of the ISO18626 request
$ref: "../definitions/ISO18626/types/status.yaml"
answerYesNo:
description: The answerYesNo of the ISO18626 request
$ref: "../definitions/ISO18626/types/yesNo.yaml"
messageInfoNote:
description: The messageInfo note of the ISO18626 request
type:
- string
- "null"
additionalProperties: false
- $ref: "../swagger.yaml#/parameters/iso18626_request_id_pp"
responses:
200:
description: The ISO18626 ILL request that was updated
schema:
$ref: "../swagger.yaml#/definitions/iso18626_request"
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: Package 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:
ill: 1

View File

@ -120,6 +120,10 @@ definitions:
$ref: ./definitions/ill_batchstatus.yaml
ill_batchstatuses:
$ref: ./definitions/ill_batchstatuses.yaml
iso18626_request:
$ref: ./definitions/iso18626_request.yaml
iso18626_requesting_agency:
$ref: ./definitions/iso18626_requesting_agency.yaml
ISO18626request:
$ref: ./definitions/ISO18626/request.yaml
ISO18626requestingAgencyMessage:
@ -471,6 +475,14 @@ paths:
$ref: ./paths/ill_backends.yaml#/~1ill~1backends
"/ill/backends/{ill_backend_id}":
$ref: "./paths/ill_backends.yaml#/~1ill~1backends~1{ill_backend_id}"
/ill/iso18626_requests:
$ref: ./paths/ill_iso18626_requests.yaml#/~1ill~1iso18626_requests
"/ill/iso18626_requests/{iso18626_request_id}":
$ref: ./paths/ill_iso18626_requests.yaml#/~1ill~1iso18626_requests~1{iso18626_request_id}
/ill/iso18626_requesting_agencies:
$ref: ./paths/ill_iso18626_requesting_agencies.yaml#/~1ill~1iso18626_requesting_agencies
"/ill/iso18626_requesting_agencies/{iso18626_requesting_agency_id}":
$ref: ./paths/ill_iso18626_requesting_agencies.yaml#/~1ill~1iso18626_requesting_agencies~1{iso18626_requesting_agency_id}
/ill/requests:
$ref: ./paths/ill_requests.yaml#/~1ill~1requests
/ill/batches:
@ -887,6 +899,12 @@ parameters:
name: iso18626_request_id
required: true
type: integer
iso18626_requesting_agency_id_pp:
description: ISO18626 ILL requesting agency internal identifier
in: path
name: iso18626_requesting_agency_id
required: true
type: integer
import_batch_profile_id_pp:
description: Internal profile identifier
in: path