From 3d81f85292b4e8b1006677a06501c6216743ce89 Mon Sep 17 00:00:00 2001
From: Jiri Kozlovsky <mail@jkozlovsky.cz>
Date: Sat, 10 Dec 2016 11:43:49 +0000
Subject: [PATCH] Implemented suggestions REST API

https://bugs.koha-community.org/show_bug.cgi?id=17314
---
 Koha/Patron.pm                             |  19 +-
 Koha/REST/V1/Suggestions.pm                | 264 ++++++++++++++++++++++++
 api/v1/swagger/definitions.json            |   3 +
 api/v1/swagger/definitions/suggestion.json | 131 ++++++++++++
 api/v1/swagger/parameters.json             |   3 +
 api/v1/swagger/parameters/suggestion.json  |   9 +
 api/v1/swagger/paths.json                  |   6 +
 api/v1/swagger/paths/suggestions.json      | 311 +++++++++++++++++++++++++++++
 8 files changed, 745 insertions(+), 1 deletion(-)
 create mode 100644 Koha/REST/V1/Suggestions.pm
 create mode 100644 api/v1/swagger/definitions/suggestion.json
 create mode 100644 api/v1/swagger/parameters/suggestion.json
 create mode 100644 api/v1/swagger/paths/suggestions.json

diff --git a/Koha/Patron.pm b/Koha/Patron.pm
index 6a25d717c1..4900b40757 100644
--- a/Koha/Patron.pm
+++ b/Koha/Patron.pm
@@ -1390,10 +1390,27 @@ sub _anonymize_column {
     $self->$col($val);
 }
 
+=head3 get_suggestions
+
+my $suggestions = $patron->get_suggestions
+
+Return user's suggestions
+
+=cut
+
+sub get_suggestions {
+    my ($self) = @_;
+    my $suggestions = Koha::Suggestions->search(
+        {
+            'suggestedby' => $self->borrowernumber
+        }
+    );
+    return $suggestions;
+}
+
 =head2 Internal methods
 
 =head3 _type
-
 =cut
 
 sub _type {
diff --git a/Koha/REST/V1/Suggestions.pm b/Koha/REST/V1/Suggestions.pm
new file mode 100644
index 0000000000..4b771ff25c
--- /dev/null
+++ b/Koha/REST/V1/Suggestions.pm
@@ -0,0 +1,264 @@
+package Koha::REST::V1::Suggestions;
+
+# 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Mojo::Base 'Mojolicious::Controller';
+
+use C4::Auth qw( haspermission );
+use C4::Context;
+use C4::Koha;
+
+use Koha::Suggestion;
+use Koha::Suggestions;
+
+use Koha::ItemTypes;
+use Koha::Libraries;
+
+use Try::Tiny;
+
+sub list {
+    my ($c, $args, $cb) = @_;
+
+    my $suggestions;
+    my $filter;
+    $args //= {};
+
+    for my $filter_param ( keys %$args ) {
+        $filter->{$filter_param} = { LIKE => $args->{$filter_param} . '%' };
+    }
+
+    return try {
+        $suggestions = Koha::Suggestions->search($filter)->unblessed;
+        return $c->$cb( $suggestions, 200 );
+    }
+    catch {
+        if ( $_->isa('DBIx::Class::Exception') ) {
+            return $c->$cb( { error => $_->{msg} }, 500 );
+        }
+        else {
+            return $c->$cb(
+                { error => 'Something went wrong, check the logs.' }, 500 );
+        }
+    };
+}
+
+sub get {
+    my ($c, $args, $cb) = @_;
+
+    my $suggestion = Koha::Suggestions->find($args->{suggestionid});
+    unless ($suggestion) {
+        return $c->$cb({error => 'Suggestion not found'}, 404);
+    }
+
+    return $c->$cb($suggestion->unblessed, 200);
+}
+
+sub add {
+    my ( $c, $args, $cb ) = @_;
+
+    my $error = _validate_body($c, $args, $cb, 0);
+    return $error if $error;
+
+    my $suggestion = Koha::Suggestion->new( $args->{body} );
+
+    return try {
+        $suggestion->store;
+        return $c->$cb( $suggestion->unblessed, 200 );
+    }
+    catch {
+        if ( $_->isa('DBIx::Class::Exception') ) {
+            return $c->$cb( { error => $_->msg }, 500 );
+        }
+        else {
+            return $c->$cb(
+                { error => 'Something went wrong, check the logs.' }, 500 );
+        }
+    };
+}
+
+sub update {
+    my ( $c, $args, $cb ) = @_;
+
+    my $suggestion;
+
+    return try {
+
+        $suggestion = Koha::Suggestions->find( $args->{suggestionid} );
+
+        my $body = $args->{body};
+
+        # Remove unchaned fields so that we can use our body validation from the add subroutine
+        foreach my $param ( keys %{ $body } ) {
+            if (exists $body->{$param}) {
+                delete $body->{$param} if $body->{$param} eq $suggestion->unblessed->{$param};
+            }
+        }
+
+        my $error = _validate_body($c, $args, $cb, 1);
+        return $error if $error;
+
+        $suggestion->set( $body );
+        $suggestion->store();
+        return $c->$cb( $suggestion->unblessed, 200 );
+    }
+    catch {
+        if ( not defined $suggestion ) {
+            return $c->$cb( { error => 'Object not found' }, 404 );
+        }
+        elsif ( $_->isa('Koha::Exceptions::Object') ) {
+            return $c->$cb( { error => $_->message }, 500 );
+        }
+        else {
+            return $c->$cb(
+                { error => 'Something went wrong, check the logs.' }, 500 );
+        }
+    };
+
+}
+
+sub delete {
+    my ( $c, $args, $cb ) = @_;
+
+    my $suggestion;
+
+    return try {
+        $suggestion = Koha::Suggestions->find( $args->{suggestionid} );
+        $suggestion->delete;
+        return $c->$cb( '', 200 );
+    }
+    catch {
+        if ( not defined $suggestion ) {
+            return $c->$cb( { error => 'Object not found' }, 404 );
+        }
+        elsif ( $_->isa('DBIx::Class::Exception') ) {
+            return $c->$cb( { error => $_->msg }, 500 );
+        }
+        else {
+            return $c->$cb(
+                { error => 'Something went wrong, check the logs.' }, 500 );
+        }
+    };
+
+}
+
+sub _validate_body {
+    my ( $c, $args, $cb, $updating ) = @_;
+
+    my $body = $args->{body};
+    my $user = $c->stash('koha.user');
+
+    my $has_acquisition = C4::Auth::haspermission($user->userid, {acquisition => 1});
+
+    if (not $has_acquisition) {
+        # Regular user cannot change anything ...
+        my @allowed_fields = ('suggestedby', 'title', 'author', 'copyrightdate', 'isbn',
+            'publishercode', 'collectiontitle', 'place', 'itemtype', 'patronreason', 'note');
+
+        # Hmm, how about branches?
+        if ( C4::Context->preference('AllowPurchaseSuggestionBranchChoice') ) {
+            push(@allowed_fields, 'branchcode');
+        }
+
+        foreach my $param ( keys %{ $body } ) {
+            unless (/^$param$/ ~~ @allowed_fields) {
+                # Ouch ! Some mandatory field is missing!
+                my $verb = $updating ? 'updated ' : 'specified ';
+                return $c->$cb({error => 'You ' . $verb . $param . ', but allowed fields are only ' .
+                        join(', ', @allowed_fields)}, 403);
+            }
+        }
+    }
+
+    if (not $updating) {
+        # Check for missing fields
+        my @mandatory_fields = split /,/, C4::Context->preference('OPACSuggestionMandatoryFields');
+        my @missing_fields = ();
+        for my $mandatory_field (@mandatory_fields) {
+            push(@missing_fields, $mandatory_field) if (not exists $body->{$mandatory_field});
+        }
+
+        if ( @missing_fields ) {
+            return $c->$cb({error => 'Missing mandatory fields: ' . join(', ', @missing_fields)}, 400);
+        }
+    }
+
+    # Is suggester anonymous?
+    my $is_anonymous = not (defined $body->{suggestedby} and
+        $body->{suggestedby} ne '' and
+        $body->{suggestedby} ne C4::Context->preference('AnonymousPatron'));
+
+    # Refuse if are anonymous suggestions disabled
+    if ( $is_anonymous ) {
+        return $c->$cb({error => 'Anonymous suggestions are disabled'}, 403)
+        unless C4::Context->preference('AnonSuggestions');
+    }
+
+    # Refuse adding another suggestion if max reached for a user
+    my $max_open_suggestions = C4::Context->preference('MaxOpenSuggestions');
+    if ( $max_open_suggestions gt 0 and not $is_anonymous ) {
+        my $count = Koha::Suggestions->search({suggestedby => $body->{suggestedby}})->count();
+
+        return $c->$cb({error =>
+                'You have ' . $count . ' opened suggestions out of ' . $max_open_suggestions}, 403)
+        if ( $count >= $max_open_suggestions );
+    }
+
+    # Check STATUS is valid
+    if ( exists $body->{STATUS} ) {
+        return $c->$cb({error => 'STATUS must be one of ASKED, CHECKED, ACCEPTED, or REJECTED'}, 400)
+        unless ($body->{STATUS} =~ m/^(ASKED|CHECKED|ACCEPTED|REJECTED)$/);
+    }
+
+    # Check itemtype is valid
+    if ( exists $body->{itemtype} ) {
+        my @item_types = map {$_->unblessed->{itemtype}} Koha::ItemTypes->search;
+        return $c->$cb({error => 'itemtype must be one of ' . join(', ', @item_types)}, 400)
+        unless /^$body->{itemtype}$/ ~~ @item_types;
+    }
+
+    # Check branchcode is valid
+    if ( exists $body->{branchcode} ) {
+        my @branch_codes = map {$_->unblessed->{branchcode}} Koha::Libraries->search;
+        return $c->$cb({error => 'branchcode must be one of ' . join(', ', @branch_codes)}, 400)
+        unless /^$body->{branchcode}$/ ~~ @branch_codes;
+    }
+
+    # Check patron reason is valid
+    if ( exists $body->{patronreason} ) {
+        my @authorized_values = map { $_->{authorised_value} } @{ C4::Koha::GetAuthorisedValues('OPAC_SUG') };
+        return $c->$cb({error => 'patronreason must be one of ' . join(', ', @authorized_values)}, 400)
+        unless /^$body->{patronreason}$/ ~~ @authorized_values;
+    }
+
+    # Check suggestedby patron exists
+    if ( exists $body->{suggestedby} ) {
+        return $c->$cb({error => 'suggestedby patron not found'}, 400)
+        unless Koha::Patrons->find($body->{suggestedby});
+    }
+
+    # Check managedby patron exists
+    if ( exists $body->{managedby} ) {
+        return $c->$cb({error => 'managedby patron not found'}, 400)
+        unless Koha::Patrons->find($body->{managedby});
+    }
+
+    # Everything's fine ..
+    return 0;
+}
+
+1;
diff --git a/api/v1/swagger/definitions.json b/api/v1/swagger/definitions.json
index 1dc90f3f58..e8891b8184 100644
--- a/api/v1/swagger/definitions.json
+++ b/api/v1/swagger/definitions.json
@@ -37,5 +37,8 @@
   },
   "fund": {
     "$ref": "definitions/fund.json"
+  },
+  "suggestion": {
+    "$ref": "definitions/suggestion.json"
   }
 }
diff --git a/api/v1/swagger/definitions/suggestion.json b/api/v1/swagger/definitions/suggestion.json
new file mode 100644
index 0000000000..12ff222722
--- /dev/null
+++ b/api/v1/swagger/definitions/suggestion.json
@@ -0,0 +1,131 @@
+{
+  "type": "object",
+  "properties": {
+    "suggestionid": {
+      "type": "string",
+      "description": "unique identifier assigned automatically by Koha"
+    },
+    "suggestedby": {
+      "type": "string",
+      "description": "borrowernumber for the person making the suggestion, foreign key linking to the borrowers table"
+    },
+    "suggesteddate": {
+      "type": "string",
+      "description": "the suggestion was submitted"
+    },
+    "managedby": {
+      "type": ["string", "null"],
+      "description": "borrowernumber for the librarian managing the suggestion, foreign key linking to the borrowers table"
+    },
+    "manageddate": {
+      "type": ["string", "null"],
+      "description": "date the suggestion was updated"
+    },
+    "acceptedby": {
+      "type": ["string", "null"],
+      "description": "borrowernumber for the librarian who accepted the suggestion, foreign key linking to the borrowers table"
+    },
+    "accepteddate": {
+      "type": ["string", "null"],
+      "description": "date the suggestion was marked as accepted"
+    },
+    "rejectedby": {
+      "type": ["string", "null"],
+      "description": "borrowernumber for the librarian who rejected the suggestion, foreign key linking to the borrowers table"
+    },
+    "rejecteddate": {
+      "type": ["string", "null"],
+      "description": "date the suggestion was marked as rejected"
+    },
+    "STATUS": {
+      "type": "string",
+      "description": "suggestion status (ASKED, CHECKED, ACCEPTED, or REJECTED)"
+    },
+    "note": {
+      "type": ["string", "null"],
+      "description": "note entered on the suggestion"
+    },
+    "author": {
+      "type": ["string", "null"],
+      "description": "author of the suggested item"
+    },
+    "title": {
+      "type": ["string", "null"],
+      "description": "title of the suggested item"
+    },
+    "copyrightdate": {
+      "type": ["string", "null"],
+      "description": "copyright date of the suggested item"
+    },
+    "publishercode": {
+      "type": ["string", "null"],
+      "description": "publisher of the suggested item"
+    },
+    "date": {
+      "type": ["string", "null"],
+      "description": "date created"
+    },
+    "volumedesc": {
+      "type": ["string", "null"],
+      "description": "volume description"
+    },
+    "publicationyear": {
+      "type": "string",
+      "description": "year of publication"
+    },
+    "place": {
+      "type": ["string", "null"],
+      "description": "publication place of the suggested item"
+    },
+    "isbn": {
+      "type": ["string", "null"],
+      "description": "isbn of the suggested item"
+    },
+    "biblionumber": {
+      "type": ["string", "null"],
+      "description": "foreign key linking the suggestion to the biblio table after the suggestion has been ordered"
+    },
+    "reason": {
+      "type": ["string", "null"],
+      "description": "reason for accepting or rejecting the suggestion"
+    },
+    "patronreason": {
+      "type": ["string", "null"],
+      "description": "reason for making the suggestion"
+    },
+    "budgetid": {
+      "type": ["string", "null"],
+      "description": "foreign key linking the suggested budget to the aqbudgets table"
+    },
+    "branchcode": {
+      "type": ["string", "null"],
+      "description": "foreign key linking the suggested branch to the branches table"
+    },
+    "collectiontitle": {
+      "type": ["string", "null"],
+      "description": "collection name for the suggested item"
+    },
+    "itemtype": {
+      "type": ["string", "null"],
+      "description": "suggested item type"
+    },
+    "quantity": {
+      "type": ["string", "null"],
+      "description": "suggested quantity to be purchased"
+    },
+    "currency": {
+      "type": ["string", "null"],
+      "description": "suggested currency for the suggested price"
+    },
+    "price": {
+      "type": ["string", "null"],
+      "description": "suggested price"
+    },
+    "total": {
+      "type": ["string", "null"],
+      "description": "suggested total cost (price*quantity updated for currency)"
+    }
+  },
+  "additionalProperties": false,
+  "required": ["title"]
+}
diff --git a/api/v1/swagger/parameters.json b/api/v1/swagger/parameters.json
index fce13be958..4717020935 100644
--- a/api/v1/swagger/parameters.json
+++ b/api/v1/swagger/parameters.json
@@ -63,5 +63,8 @@
    },
   "fundidPathParam": {
     "$ref": "parameters/fund.json#/fundidPathParam"
+  },
+  "suggestionidPathParam": {
+    "$ref": "parameters/suggestion.json#/suggestionidPathParam"
   }
 }
diff --git a/api/v1/swagger/parameters/suggestion.json b/api/v1/swagger/parameters/suggestion.json
new file mode 100644
index 0000000000..75647ea81c
--- /dev/null
+++ b/api/v1/swagger/parameters/suggestion.json
@@ -0,0 +1,9 @@
+{
+  "suggestionidPathParam": {
+    "name": "suggestionid",
+    "in": "path",
+    "description": "Internal suggestion identifier",
+    "required": true,
+    "type": "integer"
+  }
+}
diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json
index 33cfd3aa5a..eea69468f2 100644
--- a/api/v1/swagger/paths.json
+++ b/api/v1/swagger/paths.json
@@ -67,5 +67,11 @@
   },
   "/public/patrons/{patron_id}/password": {
     "$ref": "paths/public_patrons.json#/~1public~1patrons~1{patron_id}~1password"
+  },
+  "/suggestions": {
+    "$ref": "paths/suggestions.json#/~1suggestions"
+  },
+  "/suggestions/{suggestionid}": {
+    "$ref": "paths/suggestions.json#/~1suggestions~1{suggestionid}"
   }
 }
diff --git a/api/v1/swagger/paths/suggestions.json b/api/v1/swagger/paths/suggestions.json
new file mode 100644
index 0000000000..dfef6cba05
--- /dev/null
+++ b/api/v1/swagger/paths/suggestions.json
@@ -0,0 +1,311 @@
+{
+  "/suggestions": {
+    "get": {
+      "x-mojo-controller": "Koha::REST::V1::Suggestions",
+      "operationId": "list",
+      "tags": ["patrons", "suggestions"],
+      "parameters": [
+        {
+          "name": "suggestionid",
+          "in": "query",
+          "type": "integer",
+          "description": "Internal suggestion identifier"
+        },
+        {
+          "name": "suggestedby",
+          "in": "query",
+          "type": "integer",
+          "description": "borrowernumber for the person making the suggestion, foreign key linking to the borrowers table"
+        },
+        {
+          "name": "managedby",
+          "in": "query",
+          "type": "integer",
+          "description": "borrowernumber for the librarian managing the suggestion, foreign key linking to the borrowers table"
+        },
+        {
+          "name": "acceptedby",
+          "in": "query",
+          "type": "integer",
+          "description": "borrowernumber for the librarian who accepted the suggestion, foreign key linking to the borrowers table"
+        },
+        {
+          "name": "rejectedby",
+          "in": "query",
+          "type": "integer",
+          "description": "borrowernumber for the librarian who rejected the suggestion, foreign key linking to the borrowers table"
+        },
+        {
+          "name": "STATUS",
+          "in": "query",
+          "type": "string",
+          "description": "suggestion status (ASKED, CHECKED, ACCEPTED, or REJECTED)"
+        },
+        {
+          "name": "author",
+          "in": "query",
+          "type": "string",
+          "description": "author of the suggested item"
+        },
+        {
+          "name": "title",
+          "in": "query",
+          "type": "string",
+          "description": "title of the suggested item"
+        },
+        {
+          "name": "publishercode",
+          "in": "query",
+          "type": "string",
+          "description": "publisher of the suggested item"
+        },
+        {
+          "name": "date",
+          "in": "query",
+          "type": "string",
+          "description": "date created"
+        },
+        {
+          "name": "publicationyear",
+          "in": "query",
+          "type": "string",
+          "description": "year of publication"
+        },
+        {
+          "name": "isbn",
+          "in": "query",
+          "type": "string",
+          "description": "isbn of the suggested item"
+        },
+        {
+          "name": "collectiontitle",
+          "in": "query",
+          "type": "string",
+          "description": "collection name for the suggested item"
+        },
+        {
+          "name": "itemtype",
+          "in": "query",
+          "type": "string",
+          "description": "suggested item type"
+        }
+      ],
+      "produces": [
+          "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "A list of suggestions",
+          "schema": {
+            "type": "array",
+            "items": {
+              "$ref": "../definitions.json#/suggestion"
+            }
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "allow-owner": true,
+        "permissions": {
+          "acquisition": "1"
+        }
+      }
+    },
+    "post": {
+      "x-mojo-controller": "Koha::REST::V1::Suggestions",
+      "operationId": "add",
+      "tags": ["patrons", "suggestions"],
+      "parameters": [{
+        "name": "body",
+        "in": "body",
+        "description": "A JSON object containing informations about the new suggestion",
+        "required": true,
+        "schema": {
+          "$ref": "../definitions.json#/suggestion"
+        }
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "Suggestion added",
+          "schema": {
+            "$ref": "../definitions.json#/suggestion"
+          }
+        },
+        "400": {
+          "description": "Bad request",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "allow-owner": true,
+        "permissions": {
+          "acquisition": "1"
+        }
+      }
+    }
+  },
+  "/suggestions/{suggestionid}": {
+    "get": {
+      "x-mojo-controller": "Koha::REST::V1::Suggestions",
+      "operationId": "get",
+      "tags": ["patrons", "suggestions"],
+      "parameters": [{
+          "$ref": "../parameters.json#/suggestionidPathParam"
+        }
+      ],
+      "produces": [
+          "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "A suggestion",
+          "schema": {
+            "$ref": "../definitions.json#/suggestion"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "404": {
+          "description": "Suggestion not found",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "allow-owner": true,
+        "allow-guarantor": true,
+        "permissions": {
+          "acquisition": "1"
+        }
+      }
+    },
+    "put": {
+      "x-mojo-controller": "Koha::REST::V1::Suggestions",
+      "operationId": "update",
+      "tags": ["patrons", "suggestions"],
+      "parameters": [{
+        "$ref": "../parameters.json#/suggestionidPathParam"
+      }, {
+        "name": "body",
+        "in": "body",
+        "description": "A JSON object containing informations about the new hold",
+        "required": true,
+        "schema": {
+          "$ref": "../definitions.json#/suggestion"
+        }
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "A suggestion",
+          "schema": {
+            "$ref": "../definitions.json#/suggestion"
+          }
+        },
+        "400": {
+          "description": "Bad request",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "404": {
+          "description": "Suggestion not found",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "allow-owner": true,
+        "permissions": {
+          "acquisition": "1"
+        }
+      }
+    },
+    "delete": {
+      "x-mojo-controller": "Koha::REST::V1::Suggestions",
+      "operationId": "delete",
+      "tags": ["patrons", "suggestions"],
+      "parameters": [{
+        "$ref": "../parameters.json#/suggestionidPathParam"
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "Suggestion deleted",
+          "schema": {
+            "type": "string"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "404": {
+          "description": "Suggestion not found",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "allow-owner": true,
+        "permissions": {
+          "acquisition": "1"
+        }
+      }
+    }
+  }
+}
-- 
2.11.0