297 lines
9.0 KiB
Perl
297 lines
9.0 KiB
Perl
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
|
|
$iso18626_request->add_message(
|
|
{
|
|
type => $confirmation_message_type,
|
|
message => JSON::encode_json($response),
|
|
timestamp => \"NOW() + INTERVAL 1 SECOND",
|
|
}
|
|
);
|
|
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',
|
|
'ShippedReturn',
|
|
|
|
# TODO: Uncomment these once implemented
|
|
#'Renew',
|
|
#'HoldReturn',
|
|
#'ShippedForward',
|
|
#'Notification',
|
|
#'Lost',
|
|
];
|
|
}
|
|
|
|
1;
|