View | Details | Raw Unified | Return to bug 13903
Collapse All | Expand All

(-)a/Koha/REST/V1/Reserve.pm (+160 lines)
Line 0 Link Here
1
package Koha::REST::V1::Reserve;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use C4::Biblio;
23
use C4::Reserves;
24
25
use Koha::Borrowers;
26
use Koha::DateUtils;
27
28
sub list {
29
    my ($c, $args, $cb) = @_;
30
31
    my $borrowernumber = $c->param('borrowernumber');
32
    my $borrower = Koha::Borrowers->find($borrowernumber);
33
    unless ($borrower) {
34
        return $c->$cb({error => "Borrower not found"}, 404);
35
    }
36
37
    my @reserves = C4::Reserves::GetReservesFromBorrowernumber($borrowernumber);
38
39
    return $c->$cb(\@reserves, 200);
40
}
41
42
sub add {
43
    my ($c, $args, $cb) = @_;
44
45
    my $body = $c->req->json;
46
47
    my $borrowernumber = $body->{borrowernumber};
48
    my $biblionumber = $body->{biblionumber};
49
    my $itemnumber = $body->{itemnumber};
50
    my $branchcode = $body->{branchcode};
51
    my $expirationdate = $body->{expirationdate};
52
53
    my $borrower = Koha::Borrowers->find($borrowernumber);
54
    unless ($borrower) {
55
        return $c->$cb({error => "Borrower not found"}, 404);
56
    }
57
58
    unless ($biblionumber or $itemnumber) {
59
        return $c->$cb({
60
            error => "At least one of biblionumber, itemnumber should be given"
61
        }, 400);
62
    }
63
    unless ($branchcode) {
64
        return $c->$cb({
65
            error => "Branchcode is required"
66
        }, 400);
67
    }
68
69
    my $item_biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
70
    if ($biblionumber and $biblionumber != $item_biblionumber) {
71
        return $c->$cb({
72
            error => "Item $itemnumber doesn't belong to biblio $biblionumber"
73
        }, 400);
74
    }
75
76
    $biblionumber ||= $item_biblionumber;
77
    my $biblio = C4::Biblio::GetBiblio($biblionumber);
78
79
    my $can_reserve =
80
      $itemnumber
81
      ? CanItemBeReserved( $borrowernumber, $itemnumber )
82
      : CanBookBeReserved( $borrowernumber, $biblionumber );
83
84
    unless ($can_reserve eq 'OK') {
85
        return $c->$cb({
86
            error => "Reserve cannot be placed. Reason: $can_reserve"
87
        }, 403);
88
    }
89
90
    my $priority = C4::Reserves::CalculatePriority($biblionumber);
91
    $itemnumber ||= undef;
92
93
    # AddReserve expects date to be in syspref format
94
    if ($expirationdate) {
95
        $expirationdate = output_pref(dt_from_string($expirationdate, 'iso'));
96
    }
97
98
    my $reserve_id = C4::Reserves::AddReserve($branchcode, $borrowernumber,
99
        $biblionumber, undef, $priority, undef, $expirationdate, undef,
100
        $biblio->{title}, $itemnumber);
101
102
    unless ($reserve_id) {
103
        return $c->$cb({
104
            error => "Error while placing reserve. See Koha logs for details."
105
        }, 500);
106
    }
107
108
    my $reserve = C4::Reserves::GetReserve($reserve_id);
109
110
    return $c->$cb($reserve, 201);
111
}
112
113
sub edit {
114
    my ($c, $args, $cb) = @_;
115
116
    my $reserve_id = $args->{reserve_id};
117
    my $reserve = C4::Reserves::GetReserve($reserve_id);
118
119
    unless ($reserve) {
120
        return $c->$cb({error => "Reserve not found"}, 404);
121
    }
122
123
    my $body = $c->req->json;
124
125
    my $branchcode = $body->{branchcode};
126
    my $priority = $body->{priority};
127
    my $suspend_until = $body->{suspend_until};
128
129
    if ($suspend_until) {
130
        $suspend_until = output_pref(dt_from_string($suspend_until, 'iso'));
131
    }
132
133
    my $params = {
134
        reserve_id => $reserve_id,
135
        branchcode => $branchcode,
136
        rank => $priority,
137
        suspend_until => $suspend_until,
138
    };
139
    C4::Reserves::ModReserve($params);
140
    $reserve = C4::Reserves::GetReserve($reserve_id);
141
142
    return $c->$cb($reserve, 200);
143
}
144
145
sub delete {
146
    my ($c, $args, $cb) = @_;
147
148
    my $reserve_id = $args->{reserve_id};
149
    my $reserve = C4::Reserves::GetReserve($reserve_id);
150
151
    unless ($reserve) {
152
        return $c->$cb({error => "Reserve not found"}, 404);
153
    }
154
155
    C4::Reserves::CancelReserve({ reserve_id => $reserve_id });
156
157
    return $c->$cb({}, 200);
158
}
159
160
1;
(-)a/api/v1/definitions/index.json (+2 lines)
Lines 1-4 Link Here
1
{
1
{
2
    "patron": { "$ref": "patron.json" },
2
    "patron": { "$ref": "patron.json" },
3
    "reserves": { "$ref": "reserves.json" },
4
    "reserve": { "$ref": "reserve.json" },
3
    "error": { "$ref": "error.json" }
5
    "error": { "$ref": "error.json" }
4
}
6
}
(-)a/api/v1/definitions/reserve.json (+63 lines)
Line 0 Link Here
1
{
2
    "type": "object",
3
    "properties": {
4
        "reserve_id": {
5
            "description": "Internal reserve identifier"
6
        },
7
        "borrowernumber": {
8
            "type": "string",
9
            "description": "internally assigned user identifier"
10
        },
11
        "reservedate": {
12
            "description": "the date the reserve was placed"
13
        },
14
        "biblionumber": {
15
            "type": "string",
16
            "description": "internally assigned biblio identifier"
17
        },
18
        "branchcode": {
19
            "type": ["string", "null"],
20
            "description": "internally assigned branch identifier"
21
        },
22
        "notificationdate": {
23
            "description": "currently unused"
24
        },
25
        "reminderdate": {
26
            "description": "currently unused"
27
        },
28
        "cancellationdate": {
29
            "description": "the date the reserve was cancelled"
30
        },
31
        "reservenotes": {
32
            "description": "notes related to this reserve"
33
        },
34
        "priority": {
35
            "description": "where in the queue the patron sits"
36
        },
37
        "found": {
38
            "description": "a one letter code defining what the status of the reserve is after it has been confirmed"
39
        },
40
        "timestamp": {
41
            "description": "date and time the reserve was last updated"
42
        },
43
        "itemnumber": {
44
            "type": ["string", "null"],
45
            "description": "internally assigned item identifier"
46
        },
47
        "waitingdate": {
48
            "description": "the date the item was marked as waiting for the patron at the library"
49
        },
50
        "expirationdate": {
51
            "description": "the date the reserve expires"
52
        },
53
        "lowestPriority": {
54
            "description": ""
55
        },
56
        "suspend": {
57
            "description": ""
58
        },
59
        "suspend_until": {
60
            "description": ""
61
        }
62
    }
63
}
(-)a/api/v1/definitions/reserves.json (+4 lines)
Line 0 Link Here
1
{
2
    "type": "array",
3
    "items": { "$ref": "reserve.json" }
4
}
(-)a/api/v1/swagger.json (+165 lines)
Lines 73-78 Link Here
73
          }
73
          }
74
        }
74
        }
75
      }
75
      }
76
    },
77
    "/reserves": {
78
      "get": {
79
        "operationId": "listReserves",
80
        "tags": ["borrowers", "reserves"],
81
        "parameters": [
82
          {
83
            "name": "borrowernumber",
84
            "in": "query",
85
            "description": "Internal borrower identifier",
86
            "required": true,
87
            "type": "integer"
88
          }
89
        ],
90
        "produces": ["application/json"],
91
        "responses": {
92
          "200": {
93
            "description": "A list of reserves",
94
            "schema": { "$ref": "#/definitions/reserves" }
95
          },
96
          "404": {
97
            "description": "Borrower not found",
98
            "schema": { "$ref": "#/definitions/error" }
99
          }
100
        }
101
      },
102
      "post": {
103
        "operationId": "addReserve",
104
        "tags": ["borrowers", "reserves"],
105
        "parameters": [
106
          {
107
            "name": "body",
108
            "in": "body",
109
            "description": "A JSON object containing informations about the new reserve",
110
            "required": true,
111
            "schema": {
112
              "type": "object",
113
              "properties": {
114
                "borrowernumber": {
115
                  "description": "Borrower internal identifier",
116
                  "type": "integer"
117
                },
118
                "biblionumber": {
119
                  "description": "Biblio internal identifier",
120
                  "type": "integer"
121
                },
122
                "itemnumber": {
123
                  "description": "Item internal identifier",
124
                  "type": "integer"
125
                },
126
                "branchcode": {
127
                  "description": "Pickup location",
128
                  "type": "string"
129
                },
130
                "expirationdate": {
131
                  "description": "Reserve end date",
132
                  "type": "string",
133
                  "format": "date"
134
                }
135
              }
136
            }
137
          }
138
        ],
139
        "consumes": ["application/json"],
140
        "produces": ["application/json"],
141
        "responses": {
142
          "201": {
143
            "description": "Created reserve",
144
            "schema": { "$ref": "#/definitions/reserve" }
145
          },
146
          "400": {
147
            "description": "Missing or wrong parameters",
148
            "schema": { "$ref": "#/definitions/error" }
149
          },
150
          "403": {
151
            "description": "Reserve not allowed",
152
            "schema": { "$ref": "#/definitions/error" }
153
          },
154
          "404": {
155
            "description": "Borrower not found",
156
            "schema": { "$ref": "#/definitions/error" }
157
          },
158
          "500": {
159
            "description": "Internal error",
160
            "schema": { "$ref": "#/definitions/error" }
161
          }
162
        }
163
      }
164
    },
165
    "/reserves/{reserve_id}": {
166
      "put": {
167
        "operationId": "editReserve",
168
        "tags": ["reserves"],
169
        "parameters": [
170
          { "$ref": "#/parameters/reserveIdPathParam" },
171
          {
172
            "name": "body",
173
            "in": "body",
174
            "description": "A JSON object containing fields to modify",
175
            "required": true,
176
            "schema": {
177
              "type": "object",
178
              "properties": {
179
                "priority": {
180
                  "description": "Position in waiting queue",
181
                  "type": "integer",
182
                  "minimum": 1
183
                },
184
                "branchcode": {
185
                  "description": "Pickup location",
186
                  "type": "string"
187
                },
188
                "suspend_until": {
189
                  "description": "Suspend until",
190
                  "type": "string",
191
                  "format": "date"
192
                }
193
              }
194
            }
195
          }
196
        ],
197
        "consumes": ["application/json"],
198
        "produces": ["application/json"],
199
        "responses": {
200
          "200": {
201
            "description": "Updated reserve",
202
            "schema": { "$ref": "#/definitions/reserve" }
203
          },
204
          "400": {
205
            "description": "Missing or wrong parameters",
206
            "schema": { "$ref": "#/definitions/error" }
207
          },
208
          "404": {
209
            "description": "Reserve not found",
210
            "schema": { "$ref": "#/definitions/error" }
211
          }
212
        }
213
      },
214
      "delete": {
215
        "operationId": "deleteReserve",
216
        "tags": ["reserves"],
217
        "parameters": [
218
          { "$ref": "#/parameters/reserveIdPathParam" }
219
        ],
220
        "produces": ["application/json"],
221
        "responses": {
222
          "200": {
223
            "description": "Successful deletion",
224
            "schema": {
225
              "type": "object"
226
            }
227
          },
228
          "404": {
229
            "description": "Reserve not found",
230
            "schema": { "$ref": "#/definitions/error" }
231
          }
232
        }
233
      }
76
    }
234
    }
77
  },
235
  },
78
  "definitions": {
236
  "definitions": {
Lines 85-90 Link Here
85
      "description": "Internal patron identifier",
243
      "description": "Internal patron identifier",
86
      "required": true,
244
      "required": true,
87
      "type": "integer"
245
      "type": "integer"
246
    },
247
    "reserveIdPathParam": {
248
      "name": "reserve_id",
249
      "in": "path",
250
      "description": "Internal reserve identifier",
251
      "required": true,
252
      "type": "integer"
88
    }
253
    }
89
  }
254
  }
90
}
255
}
(-)a/t/db_dependent/api/v1/reserves.t (-1 / +154 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
20
use Test::More tests => 30;
21
use Test::Mojo;
22
23
use DateTime;
24
25
use C4::Context;
26
use C4::Biblio;
27
use C4::Items;
28
use C4::Reserves;
29
30
use Koha::Database;
31
use Koha::Borrower;
32
33
my $dbh = C4::Context->dbh;
34
$dbh->{AutoCommit} = 0;
35
$dbh->{RaiseError} = 1;
36
37
my $t = Test::Mojo->new('Koha::REST::V1');
38
39
my $categorycode = Koha::Database->new()->schema()->resultset('Category')->first()->categorycode();
40
my $branchcode = Koha::Database->new()->schema()->resultset('Branch')->first()->branchcode();
41
42
my $borrower = Koha::Borrower->new;
43
$borrower->categorycode( $categorycode );
44
$borrower->branchcode( $branchcode );
45
$borrower->surname("Test Surname");
46
$borrower->store;
47
my $borrowernumber = $borrower->borrowernumber;
48
49
my $borrower2 = Koha::Borrower->new;
50
$borrower2->categorycode( $categorycode );
51
$borrower2->branchcode( $branchcode );
52
$borrower2->surname("Test Surname 2");
53
$borrower2->store;
54
my $borrowernumber2 = $borrower2->borrowernumber;
55
56
my $biblionumber = create_biblio('RESTful Web APIs');
57
my $itemnumber = create_item($biblionumber, 'TEST000001');
58
59
my $reserve_id = C4::Reserves::AddReserve($branchcode, $borrowernumber,
60
    $biblionumber, undef, 1, undef, undef, undef, '', $itemnumber);
61
62
# Add another reserve to be able to change first reserve's rank
63
C4::Reserves::AddReserve($branchcode, $borrowernumber2,
64
    $biblionumber, undef, 2, undef, undef, undef, '', $itemnumber);
65
66
my $suspend_until = DateTime->now->add(days => 10)->ymd;
67
my $put_data = {
68
    priority => 2,
69
    suspend_until => $suspend_until,
70
};
71
$t->put_ok("/api/v1/reserves/$reserve_id" => json => $put_data)
72
  ->status_is(200)
73
  ->json_is('/reserve_id', $reserve_id)
74
  ->json_is('/suspend_until', $suspend_until . ' 00:00:00')
75
  ->json_is('/priority', 2);
76
77
$t->delete_ok("/api/v1/reserves/$reserve_id")
78
  ->status_is(200);
79
80
$t->put_ok("/api/v1/reserves/$reserve_id" => json => $put_data)
81
  ->status_is(404)
82
  ->json_has('/error');
83
84
$t->delete_ok("/api/v1/reserves/$reserve_id")
85
  ->status_is(404)
86
  ->json_has('/error');
87
88
89
$t->get_ok("/api/v1/reserves?borrowernumber=$borrowernumber")
90
  ->status_is(200)
91
  ->json_is([]);
92
93
my $inexisting_borrowernumber = $borrowernumber2 + 1;
94
$t->get_ok("/api/v1/reserves?borrowernumber=$inexisting_borrowernumber")
95
  ->status_is(404)
96
  ->json_has('/error');
97
98
$dbh->do('DELETE FROM issuingrules');
99
$dbh->do(q{
100
    INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed)
101
    VALUES (?, ?, ?, ?)
102
}, {}, '*', '*', '*', 1);
103
104
my $expirationdate = DateTime->now->add(days => 10)->ymd;
105
my $post_data = {
106
    borrowernumber => int($borrowernumber),
107
    biblionumber => int($biblionumber),
108
    itemnumber => int($itemnumber),
109
    branchcode => $branchcode,
110
    expirationdate => $expirationdate,
111
};
112
$t->post_ok("/api/v1/reserves" => json => $post_data)
113
  ->status_is(201)
114
  ->json_has('/reserve_id');
115
116
$reserve_id = $t->tx->res->json->{reserve_id};
117
118
$t->get_ok("/api/v1/reserves?borrowernumber=$borrowernumber")
119
  ->status_is(200)
120
  ->json_is('/0/reserve_id', $reserve_id)
121
  ->json_is('/0/expirationdate', $expirationdate)
122
  ->json_is('/0/branchcode', $branchcode);
123
124
$t->post_ok("/api/v1/reserves" => json => $post_data)
125
  ->status_is(403)
126
  ->json_like('/error', qr/tooManyReserves/);
127
128
129
$dbh->rollback;
130
131
sub create_biblio {
132
    my ($title) = @_;
133
134
    my $record = new MARC::Record;
135
    $record->append_fields(
136
        new MARC::Field('200', ' ', ' ', a => $title),
137
    );
138
139
    my ($biblionumber) = C4::Biblio::AddBiblio($record, '');
140
141
    return $biblionumber;
142
}
143
144
sub create_item {
145
    my ($biblionumber, $barcode) = @_;
146
147
    my $item = {
148
        barcode => $barcode,
149
    };
150
151
    my $itemnumber = C4::Items::AddItem($item, $biblionumber);
152
153
    return $itemnumber;
154
}

Return to bug 13903