526 lines
17 KiB
Perl
526 lines
17 KiB
Perl
package Koha::REST::V1::Patrons;
|
|
|
|
# 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 <http://www.gnu.org/licenses>.
|
|
|
|
use Modern::Perl;
|
|
|
|
use Mojo::Base 'Mojolicious::Controller';
|
|
|
|
use Koha::Database;
|
|
use Koha::Exceptions;
|
|
use Koha::Patrons;
|
|
use C4::Letters qw( GetPreparedLetter EnqueueLetter SendQueuedMessages );
|
|
|
|
use List::MoreUtils qw(any);
|
|
use Scalar::Util qw( blessed );
|
|
use Try::Tiny qw( catch try );
|
|
|
|
=head1 NAME
|
|
|
|
Koha::REST::V1::Patrons
|
|
|
|
=head1 API
|
|
|
|
=head2 Methods
|
|
|
|
=head3 list
|
|
|
|
Controller function that handles listing Koha::Patron objects
|
|
|
|
=cut
|
|
|
|
sub list {
|
|
my $c = shift->openapi->valid_input or return;
|
|
|
|
return try {
|
|
|
|
my $query = {};
|
|
my $restricted = $c->param('restricted');
|
|
$c->req->params->remove('restricted');
|
|
$query->{debarred} = { '!=' => undef }
|
|
if $restricted;
|
|
|
|
my $patrons_rs = Koha::Patrons->search($query);
|
|
my $patrons = $c->objects->search( $patrons_rs );
|
|
|
|
return $c->render(
|
|
status => 200,
|
|
openapi => $patrons
|
|
);
|
|
}
|
|
catch {
|
|
$c->unhandled_exception($_);
|
|
};
|
|
}
|
|
|
|
=head3 get
|
|
|
|
Controller function that handles retrieving a single Koha::Patron object
|
|
|
|
=cut
|
|
|
|
sub get {
|
|
my $c = shift->openapi->valid_input or return;
|
|
|
|
return try {
|
|
my $patron_id = $c->param('patron_id');
|
|
my $patron = $c->objects->find( Koha::Patrons->search_limited, $patron_id );
|
|
|
|
return $c->render_resource_not_found("Patron")
|
|
unless $patron;
|
|
|
|
return $c->render(
|
|
status => 200,
|
|
openapi => $patron
|
|
);
|
|
}
|
|
catch {
|
|
$c->unhandled_exception($_);
|
|
};
|
|
}
|
|
|
|
=head3 add
|
|
|
|
Controller function that handles adding a new Koha::Patron 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 $extended_attributes = delete $body->{extended_attributes} // [];
|
|
|
|
my $patron = Koha::Patron->new_from_api($body)->store;
|
|
|
|
my $overrides = $c->stash('koha.overrides');
|
|
if ( $overrides->{welcome_yes}
|
|
|| ( C4::Context->preference("AutoEmailNewUser") && !$overrides->{welcome_no} ) )
|
|
{
|
|
|
|
# if we manage to find a valid email address, send notice
|
|
if ( $patron->notice_email_address ) {
|
|
my $letter = GetPreparedLetter(
|
|
module => 'members',
|
|
letter_code => 'WELCOME',
|
|
branchcode => $patron->branchcode,
|
|
,
|
|
lang => $patron->lang || 'default',
|
|
tables => {
|
|
'branches' => $patron->branchcode,
|
|
'borrowers' => $patron->borrowernumber,
|
|
},
|
|
want_librarian => 1,
|
|
);
|
|
|
|
if ($letter) {
|
|
my $message_id = EnqueueLetter(
|
|
{
|
|
letter => $letter,
|
|
borrowernumber => $patron->id,
|
|
to_address => $patron->notice_email_address,
|
|
message_transport_type => 'email'
|
|
}
|
|
);
|
|
SendQueuedMessages( { message_id => $message_id } );
|
|
}
|
|
}
|
|
}
|
|
|
|
$patron->extended_attributes(
|
|
[
|
|
map { { code => $_->{type}, attribute => $_->{value} } }
|
|
@$extended_attributes
|
|
]
|
|
);
|
|
if ( C4::Context->preference('EnhancedMessagingPreferences') ) {
|
|
C4::Members::Messaging::SetMessagingPreferencesFromDefaults(
|
|
{
|
|
borrowernumber => $patron->borrowernumber,
|
|
categorycode => $patron->categorycode,
|
|
}
|
|
);
|
|
}
|
|
|
|
$c->res->headers->location($c->req->url->to_string . '/' . $patron->borrowernumber);
|
|
return $c->render(
|
|
status => 201,
|
|
openapi => $c->objects->to_api($patron),
|
|
);
|
|
}
|
|
);
|
|
}
|
|
catch {
|
|
|
|
my $to_api_mapping = Koha::Patron->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::Patron::InvalidUserid') ) {
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "Problem with ". $_->userid }
|
|
);
|
|
}
|
|
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::Patron::MissingMandatoryExtendedAttribute')
|
|
)
|
|
{
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "$_", error_code => 'missing_mandatory_attribute' }
|
|
);
|
|
}
|
|
elsif (
|
|
$_->isa('Koha::Exceptions::Patron::Attribute::InvalidType')
|
|
)
|
|
{
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "$_", error_code => 'invalid_attribute_type' }
|
|
);
|
|
}
|
|
elsif (
|
|
$_->isa('Koha::Exceptions::Patron::Attribute::NonRepeatable')
|
|
)
|
|
{
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "$_", error_code => 'non_repeatable_attribute' }
|
|
);
|
|
}
|
|
elsif (
|
|
$_->isa('Koha::Exceptions::Patron::Attribute::UniqueIDConstraint')
|
|
)
|
|
{
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "$_", error_code => 'attribute_not_unique' }
|
|
);
|
|
}
|
|
}
|
|
|
|
$c->unhandled_exception($_);
|
|
};
|
|
}
|
|
|
|
|
|
=head3 update
|
|
|
|
Controller function that handles updating a Koha::Patron object
|
|
|
|
=cut
|
|
|
|
sub update {
|
|
my $c = shift->openapi->valid_input or return;
|
|
|
|
my $patron = Koha::Patrons->find( $c->param('patron_id') );
|
|
|
|
return $c->render_resource_not_found("Patron")
|
|
unless $patron;
|
|
|
|
return try {
|
|
my $body = $c->req->json;
|
|
my $user = $c->stash('koha.user');
|
|
|
|
if (
|
|
$patron->is_superlibrarian
|
|
and !$user->is_superlibrarian
|
|
and ( exists $body->{email}
|
|
or exists $body->{secondary_email}
|
|
or exists $body->{altaddress_email} )
|
|
)
|
|
{
|
|
foreach my $email_field (qw(email secondary_email altaddress_email)) {
|
|
my $exists_email = exists $body->{$email_field};
|
|
next unless $exists_email;
|
|
|
|
# exists, verify if we are asked to change it
|
|
my $put_email = $body->{$email_field};
|
|
|
|
# As of writing this patch, 'email' is the only unmapped field
|
|
# (i.e. it preserves its name, hence this fallback)
|
|
my $db_email_field = $patron->to_api_mapping->{$email_field} // 'email';
|
|
my $db_email = $patron->$db_email_field;
|
|
|
|
return $c->render(
|
|
status => 403,
|
|
openapi => { error => "Not enough privileges to change a superlibrarian's email" }
|
|
)
|
|
unless ( !defined $put_email and !defined $db_email )
|
|
or (defined $put_email
|
|
and defined $db_email
|
|
and $put_email eq $db_email );
|
|
}
|
|
}
|
|
|
|
$patron->_result->result_source->schema->txn_do(
|
|
sub {
|
|
# Remove `extended_attributes` before storing
|
|
my $extended_attributes = delete $body->{extended_attributes};
|
|
|
|
if ($extended_attributes) {
|
|
my $rs = $patron->extended_attributes(
|
|
[ map { { code => $_->{type}, attribute => $_->{value} } } @{$extended_attributes} ] );
|
|
}
|
|
|
|
$patron->set_from_api($body)->store;
|
|
$patron->discard_changes;
|
|
|
|
return $c->render(
|
|
status => 200,
|
|
openapi => $c->objects->to_api($patron),
|
|
);
|
|
}
|
|
);
|
|
} catch {
|
|
unless ( blessed $_ && $_->can('rethrow') ) {
|
|
return $c->render(
|
|
status => 500,
|
|
openapi => { error => "Something went wrong, check Koha logs for details." }
|
|
);
|
|
}
|
|
if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
|
|
return $c->render(
|
|
status => 409,
|
|
openapi => { error => $_->error, conflict => $_->duplicate_id }
|
|
);
|
|
} elsif ( $_->isa('Koha::Exceptions::Patron::InvalidUserid') ) {
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "Problem with " . $_->userid }
|
|
);
|
|
} elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "Given " . $patron->to_api_mapping->{ $_->broken_fk } . " does not exist" }
|
|
);
|
|
} elsif ( $_->isa('Koha::Exceptions::MissingParameter') ) {
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => {
|
|
error => "Missing mandatory parameter(s)",
|
|
parameters => $_->parameter
|
|
}
|
|
);
|
|
} elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => {
|
|
error => "Invalid parameter(s)",
|
|
parameters => $_->parameter
|
|
}
|
|
);
|
|
} elsif ( $_->isa('Koha::Exceptions::NoChanges') ) {
|
|
return $c->render(
|
|
status => 204,
|
|
openapi => { error => "No changes have been made" }
|
|
);
|
|
} elsif ( $_->isa('Koha::Exceptions::Patron::Attribute::InvalidType') ) {
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "$_", error_code => 'invalid_attribute_type' }
|
|
);
|
|
} elsif ( $_->isa('Koha::Exceptions::Patron::Attribute::UniqueIDConstraint') ) {
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "$_", error_code => 'attribute_not_unique' }
|
|
);
|
|
} elsif ( $_->isa('Koha::Exceptions::Patron::Attribute::NonRepeatable') ) {
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "$_", error_code => 'non_repeatable_attribute' }
|
|
);
|
|
} elsif ( $_->isa('Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute') ) {
|
|
return $c->render(
|
|
status => 400,
|
|
openapi => { error => "$_", error_code => 'missing_mandatory_attribute' }
|
|
);
|
|
}
|
|
|
|
$c->unhandled_exception($_);
|
|
};
|
|
}
|
|
|
|
=head3 delete
|
|
|
|
Controller function that handles deleting a Koha::Patron object
|
|
|
|
=cut
|
|
|
|
sub delete {
|
|
my $c = shift->openapi->valid_input or return;
|
|
|
|
my $patron = Koha::Patrons->find( $c->param('patron_id') );
|
|
|
|
return $c->render_resource_not_found("Patron")
|
|
unless $patron;
|
|
|
|
return try {
|
|
|
|
my $safe_to_delete = $patron->safe_to_delete;
|
|
|
|
if ( !$safe_to_delete ) {
|
|
# Pick the first error, if any
|
|
my ( $error ) = grep { $_->type eq 'error' } @{ $safe_to_delete->messages };
|
|
unless ( $error ) {
|
|
Koha::Exception->throw('Koha::Patron->safe_to_delete returned false but carried no error message');
|
|
}
|
|
|
|
my $error_descriptions = {
|
|
has_checkouts => 'Pending checkouts prevent deletion',
|
|
has_debt => 'Pending debts prevent deletion',
|
|
has_guarantees => 'Patron is a guarantor and it prevents deletion',
|
|
is_anonymous_patron => 'Anonymous patron cannot be deleted',
|
|
is_protected => 'Protected patrons cannot be deleted',
|
|
};
|
|
|
|
if ( any { $error->message eq $_ } keys %{$error_descriptions} ) {
|
|
return $c->render(
|
|
status => 409,
|
|
openapi => {
|
|
error => $error_descriptions->{ $error->message },
|
|
error_code => $error->message,
|
|
}
|
|
);
|
|
} else {
|
|
Koha::Exception->throw( 'Koha::Patron->safe_to_delete carried an unexpected message: ' . $error->message );
|
|
}
|
|
}
|
|
|
|
return $patron->_result->result_source->schema->txn_do(
|
|
sub {
|
|
$patron->move_to_deleted;
|
|
$patron->delete;
|
|
|
|
return $c->render_resource_deleted;
|
|
}
|
|
);
|
|
} catch {
|
|
|
|
$c->unhandled_exception($_);
|
|
};
|
|
}
|
|
|
|
=head3 guarantors_can_see_charges
|
|
|
|
Method for setting whether guarantors can see the patron's charges.
|
|
|
|
=cut
|
|
|
|
sub guarantors_can_see_charges {
|
|
my $c = shift->openapi->valid_input or return;
|
|
|
|
return try {
|
|
$c->auth->public( $c->param('patron_id') );
|
|
|
|
if ( C4::Context->preference('AllowPatronToSetFinesVisibilityForGuarantor') ) {
|
|
my $patron = $c->stash( 'koha.user' );
|
|
my $privacy_setting = ($c->req->json->{allowed}) ? 1 : 0;
|
|
|
|
$patron->privacy_guarantor_fines( $privacy_setting )->store;
|
|
|
|
return $c->render(
|
|
status => 200,
|
|
openapi => {}
|
|
);
|
|
}
|
|
else {
|
|
return $c->render(
|
|
status => 403,
|
|
openapi => {
|
|
error =>
|
|
'The current configuration doesn\'t allow the requested action.'
|
|
}
|
|
);
|
|
}
|
|
}
|
|
catch {
|
|
$c->unhandled_exception($_);
|
|
};
|
|
}
|
|
|
|
=head3 guarantors_can_see_checkouts
|
|
|
|
Method for setting whether guarantors can see the patron's checkouts.
|
|
|
|
=cut
|
|
|
|
sub guarantors_can_see_checkouts {
|
|
my $c = shift->openapi->valid_input or return;
|
|
|
|
return try {
|
|
$c->auth->public( $c->param('patron_id') );
|
|
|
|
if ( C4::Context->preference('AllowPatronToSetCheckoutsVisibilityForGuarantor') ) {
|
|
my $patron = $c->stash( 'koha.user' );
|
|
my $privacy_setting = ( $c->req->json->{allowed} ) ? 1 : 0;
|
|
|
|
$patron->privacy_guarantor_checkouts( $privacy_setting )->store;
|
|
|
|
return $c->render(
|
|
status => 200,
|
|
openapi => {}
|
|
);
|
|
}
|
|
else {
|
|
return $c->render(
|
|
status => 403,
|
|
openapi => {
|
|
error =>
|
|
'The current configuration doesn\'t allow the requested action.'
|
|
}
|
|
);
|
|
}
|
|
}
|
|
catch {
|
|
$c->unhandled_exception($_);
|
|
};
|
|
}
|
|
|
|
1;
|