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 152-157 Link Here
152
          }
152
          }
153
        }
153
        }
154
      }
154
      }
155
    },
156
    "/borrowers/{borrowernumber}/reserves": {
157
      "get": {
158
        "x-mojo-controller": "Koha::REST::V1::Borrowers::Reserves",
159
        "operationId": "listBorrowerReserves",
160
        "tags": ["borrowers", "reserves"],
161
        "parameters": [
162
          { "$ref": "#/parameters/borrowernumberPathParam" }
163
        ],
164
        "produces": ["application/json"],
165
        "responses": {
166
          "200": {
167
            "description": "A list of reserves",
168
            "schema": { "$ref": "#/definitions/reserves" }
169
          },
170
          "404": {
171
            "description": "Borrower not found",
172
            "schema": { "$ref": "#/definitions/error" }
173
          }
174
        }
175
      },
176
      "post": {
177
        "x-mojo-controller": "Koha::REST::V1::Borrowers::Reserves",
178
        "operationId": "addBorrowerReserve",
179
        "tags": ["borrowers", "reserves"],
180
        "parameters": [
181
          { "$ref": "#/parameters/borrowernumberPathParam" },
182
          {
183
            "name": "body",
184
            "in": "body",
185
            "description": "A JSON object containing informations about the new reserve",
186
            "required": true,
187
            "schema": { "$ref": "#/definitions/newBorrowerReserveBody" }
188
          }
189
        ],
190
        "consumes": ["application/json"],
191
        "produces": ["application/json"],
192
        "responses": {
193
          "201": {
194
            "description": "Created reserve",
195
            "schema": { "$ref": "#/definitions/reserve" }
196
          },
197
          "400": {
198
            "description": "Missing or wrong parameters",
199
            "schema": { "$ref": "#/definitions/error" }
200
          },
201
          "403": {
202
            "description": "Reserve not allowed",
203
            "schema": { "$ref": "#/definitions/error" }
204
          },
205
          "404": {
206
            "description": "Borrower not found",
207
            "schema": { "$ref": "#/definitions/error" }
208
          },
209
          "500": {
210
            "description": "Internal error",
211
            "schema": { "$ref": "#/definitions/error" }
212
          }
213
        }
214
      }
215
    },
216
    "/reserves/{reserve_id}": {
217
      "put": {
218
        "x-mojo-controller": "Koha::REST::V1::Reserves",
219
        "operationId": "editReserve",
220
        "tags": ["reserves"],
221
        "parameters": [
222
          { "$ref": "#/parameters/reserveIdPathParam" },
223
          {
224
            "name": "body",
225
            "in": "body",
226
            "description": "A JSON object containing fields to modify",
227
            "required": true,
228
            "schema": { "$ref": "#/definitions/editReserveBody" }
229
          }
230
        ],
231
        "consumes": ["application/json"],
232
        "produces": ["application/json"],
233
        "responses": {
234
          "200": {
235
            "description": "Updated reserve",
236
            "schema": { "$ref": "#/definitions/reserve" }
237
          },
238
          "400": {
239
            "description": "Missing or wrong parameters",
240
            "schema": { "$ref": "#/definitions/error" }
241
          },
242
          "404": {
243
            "description": "Reserve not found",
244
            "schema": { "$ref": "#/definitions/error" }
245
          }
246
        }
247
      },
248
      "delete": {
249
        "x-mojo-controller": "Koha::REST::V1::Reserves",
250
        "operationId": "deleteReserve",
251
        "tags": ["reserves"],
252
        "parameters": [
253
          { "$ref": "#/parameters/reserveIdPathParam" }
254
        ],
255
        "produces": ["application/json"],
256
        "responses": {
257
          "204": {
258
            "description": "No content",
259
            "schema": {
260
              "type": "null"
261
            }
262
          },
263
          "404": {
264
            "description": "Reserve not found",
265
            "schema": { "$ref": "#/definitions/error" }
266
          }
267
        }
268
      }
155
    }
269
    }
156
  },
270
  },
157
  "definitions": {
271
  "definitions": {
Lines 222-227 Link Here
222
        }
336
        }
223
      }
337
      }
224
    },
338
    },
339
    "reserves": {
340
      "type": "array",
341
      "items": { "$ref": "#/definitions/reserve" }
342
    },
343
    "reserve": {
344
      "type": "object",
345
      "properties": {
346
        "reserve_id": {
347
          "description": "Internal reserve identifier"
348
        },
349
        "borrowernumber": { "$ref" : "#/definitions/borrowernumber" },
350
        "reservedate": {
351
          "description": "the date the reserve was placed"
352
        },
353
        "biblionumber": { "$ref": "#/definitions/biblionumber" },
354
        "constrainttype": {
355
          "description": "type of constraint"
356
        },
357
        "branchcode": { "$ref": "#/definitions/branchcode" },
358
        "notificationdate": {
359
          "description": "currently unused"
360
        },
361
        "reminderdate": {
362
          "description": "currently unused"
363
        },
364
        "cancellationdate": {
365
          "description": "the date the reserve was cancelled"
366
        },
367
        "reservenotes": {
368
          "description": "notes related to this reserve"
369
        },
370
        "priority": {
371
          "description": "where in the queue the patron sits"
372
        },
373
        "found": {
374
          "description": "a one letter code defining what the status of the reserve is after it has been confirmed"
375
        },
376
        "timestamp": {
377
          "description": "date and time the reserve was last updated"
378
        },
379
        "itemnumber": { "$ref": "#/definitions/itemnumber" },
380
        "waitingdate": {
381
          "description": "the date the item was marked as waiting for the patron at the library"
382
        },
383
        "expirationdate": {
384
          "description": "the date the reserve expires"
385
        },
386
        "lowestPriority": {
387
          "description": ""
388
        },
389
        "suspend": {
390
          "description": ""
391
        },
392
        "suspend_until": {
393
          "description": ""
394
        }
395
      }
396
    },
397
    "newBorrowerReserveBody": {
398
      "type": "object",
399
      "properties": {
400
        "biblionumber": {
401
          "description": "Biblio internal identifier",
402
          "type": "integer"
403
        },
404
        "itemnumber": {
405
          "description": "Item internal identifier",
406
          "type": "integer"
407
        },
408
        "branchcode": {
409
          "description": "Pickup location",
410
          "type": "string",
411
          "required": true
412
        },
413
        "expirationdate": {
414
          "description": "Reserve end date",
415
          "type": "string",
416
          "format": "date"
417
        }
418
      }
419
    },
420
    "editReserveBody": {
421
      "type": "object",
422
      "properties": {
423
        "priority": {
424
          "description": "Position in waiting queue",
425
          "type": "integer",
426
          "minimum": 1
427
        },
428
        "branchcode": {
429
          "description": "Pickup location",
430
          "type": "string"
431
        },
432
        "suspend_until": {
433
          "description": "Suspend until",
434
          "type": "string",
435
          "format": "date"
436
        }
437
      }
438
    },
439
    "biblionumber": {
440
      "description": "Biblio internal identifier"
441
    },
225
    "borrowernumber": {
442
    "borrowernumber": {
226
      "description": "Borrower internal identifier"
443
      "description": "Borrower internal identifier"
227
    },
444
    },
Lines 246-252 Link Here
246
      "name": "borrowernumber",
463
      "name": "borrowernumber",
247
      "in": "path",
464
      "in": "path",
248
      "description": "Internal borrower identifier",
465
      "description": "Internal borrower identifier",
249
      "required": "true",
466
      "required": true,
467
      "type": "integer"
468
    },
469
    "reserveIdPathParam": {
470
      "name": "reserve_id",
471
      "in": "path",
472
      "description": "Internal reserve identifier",
473
      "required": true,
250
      "type": "integer"
474
      "type": "integer"
251
    },
475
    },
252
    "itemnumberPathParam": {
476
    "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