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

(-)a/C4/Reserves.pm (-11 / +12 lines)
Lines 241-258 sub AddReserve { Link Here
241
    }
241
    }
242
242
243
    #}
243
    #}
244
    ($const eq "o" || $const eq "e") or return;   # FIXME: why not have a useful return value?
244
    if ($const eq 'o' || $const eq 'e') {
245
    $query = qq{
245
        $query = qq{
246
        INSERT INTO reserveconstraints
246
            INSERT INTO reserveconstraints
247
            (borrowernumber,biblionumber,reservedate,biblioitemnumber)
247
                (borrowernumber,biblionumber,reservedate,biblioitemnumber)
248
        VALUES
248
            VALUES
249
            (?,?,?,?)
249
                (?,?,?,?)
250
    };
250
        };
251
    $sth = $dbh->prepare($query);    # keep prepare outside the loop!
251
        $sth = $dbh->prepare($query);    # keep prepare outside the loop!
252
    foreach (@$bibitems) {
252
        foreach (@$bibitems) {
253
        $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
253
            $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
254
        }
254
    }
255
    }
255
        
256
256
    return $reserve_id;
257
    return $reserve_id;
257
}
258
}
258
259
(-)a/Koha/REST/V1/Borrowers/Reserves.pm (+99 lines)
Line 0 Link Here
1
package Koha::REST::V1::Borrowers::Reserves;
2
3
use Modern::Perl;
4
5
use Mojo::Base 'Mojolicious::Controller';
6
7
use Koha::Borrowers;
8
use Koha::Borrower;
9
10
use C4::Biblio;
11
use C4::Dates;
12
use C4::Reserves;
13
14
sub list_borrower_reserves {
15
    my ($c, $args, $cb) = @_;
16
17
    my $borrowernumber = $args->{borrowernumber};
18
    my $borrower = Koha::Borrowers->find($borrowernumber);
19
    unless ($borrower) {
20
        return $c->$cb({error => "Borrower not found"}, 404);
21
    }
22
23
    my @reserves = C4::Reserves::GetReservesFromBorrowernumber($borrowernumber);
24
25
    return $c->$cb(\@reserves, 200);
26
}
27
28
sub add_borrower_reserve {
29
    my ($c, $args, $cb) = @_;
30
31
    my $borrowernumber = $args->{borrowernumber};
32
    my $borrower = Koha::Borrowers->find($borrowernumber);
33
    unless ($borrower) {
34
        return $c->$cb({error => "Borrower not found"}, 404);
35
    }
36
37
    my $body = $c->req->json;
38
39
    my $biblionumber = $body->{biblionumber};
40
    my $itemnumber = $body->{itemnumber};
41
    my $branchcode = $body->{branchcode};
42
    my $expirationdate = $body->{expirationdate};
43
44
    unless ($biblionumber or $itemnumber) {
45
        return $c->$cb({
46
            error => "At least one of biblionumber, itemnumber should be given"
47
        }, 400);
48
    }
49
    unless ($branchcode) {
50
        return $c->$cb({
51
            error => "Branchcode is required"
52
        }, 400);
53
    }
54
55
    my $item_biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
56
    if ($biblionumber and $biblionumber != $item_biblionumber) {
57
        return $c->$cb({
58
            error => "Item $itemnumber doesn't belong to biblio $biblionumber"
59
        }, 400);
60
    }
61
62
    $biblionumber ||= $item_biblionumber;
63
    my $biblio = C4::Biblio::GetBiblio($biblionumber);
64
65
    my $can_reserve =
66
      $itemnumber
67
      ? CanItemBeReserved( $borrowernumber, $itemnumber )
68
      : CanBookBeReserved( $borrowernumber, $biblionumber );
69
70
    unless ($can_reserve eq 'OK') {
71
        return $c->$cb({
72
            error => "Reserve cannot be placed. Reason: $can_reserve"
73
        }, 403);
74
    }
75
76
    my $priority = C4::Reserves::CalculatePriority($biblionumber);
77
    $itemnumber ||= undef;
78
79
    # AddReserve expects date to be in syspref format
80
    if ($expirationdate) {
81
        $expirationdate = C4::Dates->new($expirationdate, 'iso')->output;
82
    }
83
84
    my $reserve_id = C4::Reserves::AddReserve($branchcode, $borrowernumber,
85
        $biblionumber, 'a', undef, $priority, undef, $expirationdate, undef,
86
        $biblio->{title}, $itemnumber);
87
88
    unless ($reserve_id) {
89
        return $c->$cb({
90
            error => "Error while placing reserve. See Koha logs for details."
91
        }, 500);
92
    }
93
94
    my $reserve = C4::Reserves::GetReserve($reserve_id);
95
96
    return $c->$cb($reserve, 201);
97
}
98
99
1;
(-)a/Koha/REST/V1/Reserves.pm (+57 lines)
Line 0 Link Here
1
package Koha::REST::V1::Reserves;
2
3
use Modern::Perl;
4
5
use Mojo::Base 'Mojolicious::Controller';
6
7
use C4::Dates;
8
use C4::Reserves;
9
10
sub edit_reserve {
11
    my ($c, $args, $cb) = @_;
12
13
    my $reserve_id = $args->{reserve_id};
14
    my $reserve = C4::Reserves::GetReserve($reserve_id);
15
16
    unless ($reserve) {
17
        return $c->$cb({error => "Reserve not found"}, 404);
18
    }
19
20
    my $body = $c->req->json;
21
22
    my $branchcode = $body->{branchcode};
23
    my $priority = $body->{priority};
24
    my $suspend_until = $body->{suspend_until};
25
26
    if ($suspend_until) {
27
        $suspend_until = C4::Dates->new($suspend_until, 'iso')->output;
28
    }
29
30
    my $params = {
31
        reserve_id => $reserve_id,
32
        branchcode => $branchcode,
33
        rank => $priority,
34
        suspend_until => $suspend_until,
35
    };
36
    C4::Reserves::ModReserve($params);
37
    $reserve = C4::Reserves::GetReserve($reserve_id);
38
39
    return $c->$cb($reserve, 200);
40
}
41
42
sub delete_reserve {
43
    my ($c, $args, $cb) = @_;
44
45
    my $reserve_id = $args->{reserve_id};
46
    my $reserve = C4::Reserves::GetReserve($reserve_id);
47
48
    unless ($reserve) {
49
        return $c->$cb({error => "Reserve not found"}, 404);
50
    }
51
52
    C4::Reserves::CancelReserve({ reserve_id => $reserve_id });
53
54
    return $c->$cb(undef, 204);
55
}
56
57
1;
(-)a/api/v1/swagger.json (-1 / +225 lines)
Lines 138-143 Link Here
138
          }
138
          }
139
        }
139
        }
140
      }
140
      }
141
    },
142
    "/borrowers/{borrowernumber}/reserves": {
143
      "get": {
144
        "x-mojo-controller": "Koha::REST::V1::Borrowers::Reserves",
145
        "operationId": "listBorrowerReserves",
146
        "tags": ["borrowers", "reserves"],
147
        "parameters": [
148
          { "$ref": "#/parameters/borrowernumberPathParam" }
149
        ],
150
        "produces": ["application/json"],
151
        "responses": {
152
          "200": {
153
            "description": "A list of reserves",
154
            "schema": { "$ref": "#/definitions/reserves" }
155
          },
156
          "404": {
157
            "description": "Borrower not found",
158
            "schema": { "$ref": "#/definitions/error" }
159
          }
160
        }
161
      },
162
      "post": {
163
        "x-mojo-controller": "Koha::REST::V1::Borrowers::Reserves",
164
        "operationId": "addBorrowerReserve",
165
        "tags": ["borrowers", "reserves"],
166
        "parameters": [
167
          { "$ref": "#/parameters/borrowernumberPathParam" },
168
          {
169
            "name": "body",
170
            "in": "body",
171
            "description": "A JSON object containing informations about the new reserve",
172
            "required": true,
173
            "schema": { "$ref": "#/definitions/newBorrowerReserveBody" }
174
          }
175
        ],
176
        "consumes": ["application/json"],
177
        "produces": ["application/json"],
178
        "responses": {
179
          "201": {
180
            "description": "Created reserve",
181
            "schema": { "$ref": "#/definitions/reserve" }
182
          },
183
          "400": {
184
            "description": "Missing or wrong parameters",
185
            "schema": { "$ref": "#/definitions/error" }
186
          },
187
          "403": {
188
            "description": "Reserve not allowed",
189
            "schema": { "$ref": "#/definitions/error" }
190
          },
191
          "404": {
192
            "description": "Borrower not found",
193
            "schema": { "$ref": "#/definitions/error" }
194
          },
195
          "500": {
196
            "description": "Internal error",
197
            "schema": { "$ref": "#/definitions/error" }
198
          }
199
        }
200
      }
201
    },
202
    "/reserves/{reserve_id}": {
203
      "put": {
204
        "x-mojo-controller": "Koha::REST::V1::Reserves",
205
        "operationId": "editReserve",
206
        "tags": ["reserves"],
207
        "parameters": [
208
          { "$ref": "#/parameters/reserveIdPathParam" },
209
          {
210
            "name": "body",
211
            "in": "body",
212
            "description": "A JSON object containing fields to modify",
213
            "required": true,
214
            "schema": { "$ref": "#/definitions/editReserveBody" }
215
          }
216
        ],
217
        "consumes": ["application/json"],
218
        "produces": ["application/json"],
219
        "responses": {
220
          "200": {
221
            "description": "Updated reserve",
222
            "schema": { "$ref": "#/definitions/reserve" }
223
          },
224
          "400": {
225
            "description": "Missing or wrong parameters",
226
            "schema": { "$ref": "#/definitions/error" }
227
          },
228
          "404": {
229
            "description": "Reserve not found",
230
            "schema": { "$ref": "#/definitions/error" }
231
          }
232
        }
233
      },
234
      "delete": {
235
        "x-mojo-controller": "Koha::REST::V1::Reserves",
236
        "operationId": "deleteReserve",
237
        "tags": ["reserves"],
238
        "parameters": [
239
          { "$ref": "#/parameters/reserveIdPathParam" }
240
        ],
241
        "produces": ["application/json"],
242
        "responses": {
243
          "204": {
244
            "description": "No content",
245
            "schema": {
246
              "type": "null"
247
            }
248
          },
249
          "404": {
250
            "description": "Reserve not found",
251
            "schema": { "$ref": "#/definitions/error" }
252
          }
253
        }
254
      }
141
    }
255
    }
142
  },
256
  },
143
  "definitions": {
257
  "definitions": {
Lines 208-213 Link Here
208
        }
322
        }
209
      }
323
      }
210
    },
324
    },
325
    "reserves": {
326
      "type": "array",
327
      "items": { "$ref": "#/definitions/reserve" }
328
    },
329
    "reserve": {
330
      "type": "object",
331
      "properties": {
332
        "reserve_id": {
333
          "description": "Internal reserve identifier"
334
        },
335
        "borrowernumber": { "$ref" : "#/definitions/borrowernumber" },
336
        "reservedate": {
337
          "description": "the date the reserve was placed"
338
        },
339
        "biblionumber": { "$ref": "#/definitions/biblionumber" },
340
        "constrainttype": {
341
          "description": "type of constraint"
342
        },
343
        "branchcode": { "$ref": "#/definitions/branchcode" },
344
        "notificationdate": {
345
          "description": "currently unused"
346
        },
347
        "reminderdate": {
348
          "description": "currently unused"
349
        },
350
        "cancellationdate": {
351
          "description": "the date the reserve was cancelled"
352
        },
353
        "reservenotes": {
354
          "description": "notes related to this reserve"
355
        },
356
        "priority": {
357
          "description": "where in the queue the patron sits"
358
        },
359
        "found": {
360
          "description": "a one letter code defining what the status of the reserve is after it has been confirmed"
361
        },
362
        "timestamp": {
363
          "description": "date and time the reserve was last updated"
364
        },
365
        "itemnumber": { "$ref": "#/definitions/itemnumber" },
366
        "waitingdate": {
367
          "description": "the date the item was marked as waiting for the patron at the library"
368
        },
369
        "expirationdate": {
370
          "description": "the date the reserve expires"
371
        },
372
        "lowestPriority": {
373
          "description": ""
374
        },
375
        "suspend": {
376
          "description": ""
377
        },
378
        "suspend_until": {
379
          "description": ""
380
        }
381
      }
382
    },
383
    "newBorrowerReserveBody": {
384
      "type": "object",
385
      "properties": {
386
        "biblionumber": {
387
          "description": "Biblio internal identifier",
388
          "type": "integer"
389
        },
390
        "itemnumber": {
391
          "description": "Item internal identifier",
392
          "type": "integer"
393
        },
394
        "branchcode": {
395
          "description": "Pickup location",
396
          "type": "string",
397
          "required": true
398
        },
399
        "expirationdate": {
400
          "description": "Reserve end date",
401
          "type": "string",
402
          "format": "date"
403
        }
404
      }
405
    },
406
    "editReserveBody": {
407
      "type": "object",
408
      "properties": {
409
        "priority": {
410
          "description": "Position in waiting queue",
411
          "type": "integer",
412
          "minimum": 1
413
        },
414
        "branchcode": {
415
          "description": "Pickup location",
416
          "type": "string"
417
        },
418
        "suspend_until": {
419
          "description": "Suspend until",
420
          "type": "string",
421
          "format": "date"
422
        }
423
      }
424
    },
425
    "biblionumber": {
426
      "description": "Biblio internal identifier"
427
    },
211
    "borrowernumber": {
428
    "borrowernumber": {
212
      "description": "Borrower internal identifier"
429
      "description": "Borrower internal identifier"
213
    },
430
    },
Lines 232-238 Link Here
232
      "name": "borrowernumber",
449
      "name": "borrowernumber",
233
      "in": "path",
450
      "in": "path",
234
      "description": "Internal borrower identifier",
451
      "description": "Internal borrower identifier",
235
      "required": "true",
452
      "required": true,
453
      "type": "integer"
454
    },
455
    "reserveIdPathParam": {
456
      "name": "reserve_id",
457
      "in": "path",
458
      "description": "Internal reserve identifier",
459
      "required": true,
236
      "type": "integer"
460
      "type": "integer"
237
    },
461
    },
238
    "itemnumberPathParam": {
462
    "itemnumberPathParam": {
(-)a/t/db_dependent/api/v1/borrowers/reserves.t (+99 lines)
Line 0 Link Here
1
#!/usr/bin/env perl
2
3
use Modern::Perl;
4
5
use Test::More tests => 17;
6
use Test::Mojo;
7
8
use DateTime;
9
10
use C4::Context;
11
use C4::Biblio;
12
use C4::Items;
13
14
use Koha::Database;
15
use Koha::Borrower;
16
17
my $dbh = C4::Context->dbh;
18
$dbh->{AutoCommit} = 0;
19
$dbh->{RaiseError} = 1;
20
21
my $t = Test::Mojo->new('Koha::REST::V1');
22
23
my $categorycode = Koha::Database->new()->schema()->resultset('Category')->first()->categorycode();
24
my $branchcode = Koha::Database->new()->schema()->resultset('Branch')->first()->branchcode();
25
26
my $borrower = Koha::Borrower->new;
27
$borrower->categorycode( $categorycode );
28
$borrower->branchcode( $branchcode );
29
$borrower->surname("Test Surname");
30
$borrower->store;
31
my $borrowernumber = $borrower->borrowernumber;
32
33
$t->get_ok("/v1/borrowers/$borrowernumber/reserves")
34
  ->status_is(200)
35
  ->json_is([]);
36
37
my $inexisting_borrowernumber = $borrowernumber + 1;
38
$t->get_ok("/v1/borrowers/$inexisting_borrowernumber/reserves")
39
  ->status_is(404)
40
  ->json_has('/error');
41
42
my $biblionumber = create_biblio('RESTful Web APIs');
43
my $itemnumber = create_item($biblionumber, 'TEST000001');
44
45
$dbh->do('DELETE FROM issuingrules');
46
$dbh->do(q{
47
    INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed)
48
    VALUES (?, ?, ?, ?)
49
}, {}, '*', '*', '*', 1);
50
51
my $expirationdate = DateTime->now->add(days => 10)->ymd;
52
my $post_data = {
53
    biblionumber => int($biblionumber),
54
    itemnumber => int($itemnumber),
55
    branchcode => $branchcode,
56
    expirationdate => $expirationdate,
57
};
58
$t->post_ok("/v1/borrowers/$borrowernumber/reserves" => json => $post_data)
59
  ->status_is(201)
60
  ->json_has('/reserve_id');
61
62
my $reserve_id = $t->tx->res->json->{reserve_id};
63
64
$t->get_ok("/v1/borrowers/$borrowernumber/reserves")
65
  ->status_is(200)
66
  ->json_is('/0/reserve_id', $reserve_id)
67
  ->json_is('/0/expirationdate', $expirationdate)
68
  ->json_is('/0/branchcode', $branchcode);
69
70
$t->post_ok("/v1/borrowers/$borrowernumber/reserves" => json => $post_data)
71
  ->status_is(403)
72
  ->json_like('/error', qr/tooManyReserves/);
73
74
$dbh->rollback;
75
76
sub create_biblio {
77
    my ($title) = @_;
78
79
    my $record = new MARC::Record;
80
    $record->append_fields(
81
        new MARC::Field('200', ' ', ' ', a => $title),
82
    );
83
84
    my ($biblionumber) = C4::Biblio::AddBiblio($record, '');
85
86
    return $biblionumber;
87
}
88
89
sub create_item {
90
    my ($biblionumber, $barcode) = @_;
91
92
    my $item = {
93
        barcode => $barcode,
94
    };
95
96
    my $itemnumber = C4::Items::AddItem($item, $biblionumber);
97
98
    return $itemnumber;
99
}
(-)a/t/db_dependent/api/v1/reserves.t (-1 / +91 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env perl
2
3
use Modern::Perl;
4
5
use Test::More tests => 13;
6
use Test::Mojo;
7
8
use DateTime;
9
10
use C4::Context;
11
use C4::Biblio;
12
use C4::Items;
13
use C4::Reserves;
14
15
use Koha::Database;
16
use Koha::Borrower;
17
18
my $dbh = C4::Context->dbh;
19
$dbh->{AutoCommit} = 0;
20
$dbh->{RaiseError} = 1;
21
22
my $t = Test::Mojo->new('Koha::REST::V1');
23
24
my $categorycode = Koha::Database->new()->schema()->resultset('Category')->first()->categorycode();
25
my $branchcode = Koha::Database->new()->schema()->resultset('Branch')->first()->branchcode();
26
27
my $borrower = Koha::Borrower->new;
28
$borrower->categorycode( $categorycode );
29
$borrower->branchcode( $branchcode );
30
$borrower->surname("Test Surname");
31
$borrower->store;
32
my $borrowernumber = $borrower->borrowernumber;
33
34
my $biblionumber = create_biblio('RESTful Web APIs');
35
my $itemnumber = create_item($biblionumber, 'TEST000001');
36
37
my $reserve_id = C4::Reserves::AddReserve($branchcode, $borrowernumber,
38
    $biblionumber, 'a', undef, 1, undef, undef, undef, '', $itemnumber);
39
40
# Add another reserve to be able to change first reserve's rank
41
C4::Reserves::AddReserve($branchcode, $borrowernumber,
42
    $biblionumber, 'a', undef, 2, undef, undef, undef, '', $itemnumber);
43
44
my $suspend_until = DateTime->now->add(days => 10)->ymd;
45
my $put_data = {
46
    priority => 2,
47
    suspend_until => $suspend_until,
48
};
49
$t->put_ok("/v1/reserves/$reserve_id" => json => $put_data)
50
  ->status_is(200)
51
  ->json_is('/reserve_id', $reserve_id)
52
  ->json_is('/suspend_until', $suspend_until . ' 00:00:00')
53
  ->json_is('/priority', 2);
54
55
$t->delete_ok("/v1/reserves/$reserve_id")
56
  ->status_is(204);
57
58
$t->put_ok("/v1/reserves/$reserve_id" => json => $put_data)
59
  ->status_is(404)
60
  ->json_has('/error');
61
62
$t->delete_ok("/v1/reserves/$reserve_id")
63
  ->status_is(404)
64
  ->json_has('/error');
65
66
$dbh->rollback;
67
68
sub create_biblio {
69
    my ($title) = @_;
70
71
    my $record = new MARC::Record;
72
    $record->append_fields(
73
        new MARC::Field('200', ' ', ' ', a => $title),
74
    );
75
76
    my ($biblionumber) = C4::Biblio::AddBiblio($record, '');
77
78
    return $biblionumber;
79
}
80
81
sub create_item {
82
    my ($biblionumber, $barcode) = @_;
83
84
    my $item = {
85
        barcode => $barcode,
86
    };
87
88
    my $itemnumber = C4::Items::AddItem($item, $biblionumber);
89
90
    return $itemnumber;
91
}

Return to bug 13903