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

(-)a/C4/Reserves.pm (-11 / +12 lines)
Lines 239-256 sub AddReserve { Link Here
239
    }
239
    }
240
240
241
    #}
241
    #}
242
    ($const eq "o" || $const eq "e") or return;   # FIXME: why not have a useful return value?
242
    if ($const eq 'o' || $const eq 'e') {
243
    $query = qq{
243
        $query = qq{
244
        INSERT INTO reserveconstraints
244
            INSERT INTO reserveconstraints
245
            (borrowernumber,biblionumber,reservedate,biblioitemnumber)
245
                (borrowernumber,biblionumber,reservedate,biblioitemnumber)
246
        VALUES
246
            VALUES
247
            (?,?,?,?)
247
                (?,?,?,?)
248
    };
248
        };
249
    $sth = $dbh->prepare($query);    # keep prepare outside the loop!
249
        $sth = $dbh->prepare($query);    # keep prepare outside the loop!
250
    foreach (@$bibitems) {
250
        foreach (@$bibitems) {
251
        $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
251
            $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
252
        }
252
    }
253
    }
253
        
254
254
    return $reserve_id;
255
    return $reserve_id;
255
}
256
}
256
257
(-)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 / +231 lines)
Lines 63-68 Link Here
63
          }
63
          }
64
        }
64
        }
65
      }
65
      }
66
    },
67
    "/borrowers/{borrowernumber}/reserves": {
68
      "get": {
69
        "x-mojo-controller": "Koha::REST::V1::Borrowers::Reserves",
70
        "operationId": "listBorrowerReserves",
71
        "tags": ["borrowers", "reserves"],
72
        "parameters": [
73
          { "$ref": "#/parameters/borrowernumberPathParam" }
74
        ],
75
        "produces": ["application/json"],
76
        "responses": {
77
          "200": {
78
            "description": "A list of reserves",
79
            "schema": { "$ref": "#/definitions/reserves" }
80
          },
81
          "404": {
82
            "description": "Borrower not found",
83
            "schema": { "$ref": "#/definitions/error" }
84
          }
85
        }
86
      },
87
      "post": {
88
        "x-mojo-controller": "Koha::REST::V1::Borrowers::Reserves",
89
        "operationId": "addBorrowerReserve",
90
        "tags": ["borrowers", "reserves"],
91
        "parameters": [
92
          { "$ref": "#/parameters/borrowernumberPathParam" },
93
          {
94
            "name": "body",
95
            "in": "body",
96
            "description": "A JSON object containing informations about the new reserve",
97
            "required": true,
98
            "schema": { "$ref": "#/definitions/newBorrowerReserveBody" }
99
          }
100
        ],
101
        "consumes": ["application/json"],
102
        "produces": ["application/json"],
103
        "responses": {
104
          "201": {
105
            "description": "Created reserve",
106
            "schema": { "$ref": "#/definitions/reserve" }
107
          },
108
          "400": {
109
            "description": "Missing or wrong parameters",
110
            "schema": { "$ref": "#/definitions/error" }
111
          },
112
          "403": {
113
            "description": "Reserve not allowed",
114
            "schema": { "$ref": "#/definitions/error" }
115
          },
116
          "404": {
117
            "description": "Borrower not found",
118
            "schema": { "$ref": "#/definitions/error" }
119
          },
120
          "500": {
121
            "description": "Internal error",
122
            "schema": { "$ref": "#/definitions/error" }
123
          }
124
        }
125
      }
126
    },
127
    "/reserves/{reserve_id}": {
128
      "put": {
129
        "x-mojo-controller": "Koha::REST::V1::Reserves",
130
        "operationId": "editReserve",
131
        "tags": ["reserves"],
132
        "parameters": [
133
          { "$ref": "#/parameters/reserveIdPathParam" },
134
          {
135
            "name": "body",
136
            "in": "body",
137
            "description": "A JSON object containing fields to modify",
138
            "required": true,
139
            "schema": { "$ref": "#/definitions/editReserveBody" }
140
          }
141
        ],
142
        "consumes": ["application/json"],
143
        "produces": ["application/json"],
144
        "responses": {
145
          "200": {
146
            "description": "Updated reserve",
147
            "schema": { "$ref": "#/definitions/reserve" }
148
          },
149
          "400": {
150
            "description": "Missing or wrong parameters",
151
            "schema": { "$ref": "#/definitions/error" }
152
          },
153
          "404": {
154
            "description": "Reserve not found",
155
            "schema": { "$ref": "#/definitions/error" }
156
          }
157
        }
158
      },
159
      "delete": {
160
        "x-mojo-controller": "Koha::REST::V1::Reserves",
161
        "operationId": "deleteReserve",
162
        "tags": ["reserves"],
163
        "parameters": [
164
          { "$ref": "#/parameters/reserveIdPathParam" }
165
        ],
166
        "produces": ["application/json"],
167
        "responses": {
168
          "204": {
169
            "description": "No content",
170
            "schema": {
171
              "type": "null"
172
            }
173
          },
174
          "404": {
175
            "description": "Reserve not found",
176
            "schema": { "$ref": "#/definitions/error" }
177
          }
178
        }
179
      }
66
    }
180
    }
67
  },
181
  },
68
  "definitions": {
182
  "definitions": {
Lines 83-91 Link Here
83
        }
197
        }
84
      }
198
      }
85
    },
199
    },
200
    "reserves": {
201
      "type": "array",
202
      "items": { "$ref": "#/definitions/reserve" }
203
    },
204
    "reserve": {
205
      "type": "object",
206
      "properties": {
207
        "reserve_id": {
208
          "description": "Internal reserve identifier"
209
        },
210
        "borrowernumber": { "$ref" : "#/definitions/borrowernumber" },
211
        "reservedate": {
212
          "description": "the date the reserve was placed"
213
        },
214
        "biblionumber": { "$ref": "#/definitions/biblionumber" },
215
        "constrainttype": {
216
          "description": "type of constraint"
217
        },
218
        "branchcode": { "$ref": "#/definitions/branchcode" },
219
        "notificationdate": {
220
          "description": "currently unused"
221
        },
222
        "reminderdate": {
223
          "description": "currently unused"
224
        },
225
        "cancellationdate": {
226
          "description": "the date the reserve was cancelled"
227
        },
228
        "reservenotes": {
229
          "description": "notes related to this reserve"
230
        },
231
        "priority": {
232
          "description": "where in the queue the patron sits"
233
        },
234
        "found": {
235
          "description": "a one letter code defining what the status of the reserve is after it has been confirmed"
236
        },
237
        "timestamp": {
238
          "description": "date and time the reserve was last updated"
239
        },
240
        "itemnumber": { "$ref": "#/definitions/itemnumber" },
241
        "waitingdate": {
242
          "description": "the date the item was marked as waiting for the patron at the library"
243
        },
244
        "expirationdate": {
245
          "description": "the date the reserve expires"
246
        },
247
        "lowestPriority": {
248
          "description": ""
249
        },
250
        "suspend": {
251
          "description": ""
252
        },
253
        "suspend_until": {
254
          "description": ""
255
        }
256
      }
257
    },
258
    "newBorrowerReserveBody": {
259
      "type": "object",
260
      "properties": {
261
        "biblionumber": {
262
          "description": "Biblio internal identifier",
263
          "type": "integer"
264
        },
265
        "itemnumber": {
266
          "description": "Item internal identifier",
267
          "type": "integer"
268
        },
269
        "branchcode": {
270
          "description": "Pickup location",
271
          "type": "string",
272
          "required": true
273
        },
274
        "expirationdate": {
275
          "description": "Reserve end date",
276
          "type": "string",
277
          "format": "date"
278
        }
279
      }
280
    },
281
    "editReserveBody": {
282
      "type": "object",
283
      "properties": {
284
        "priority": {
285
          "description": "Position in waiting queue",
286
          "type": "integer",
287
          "minimum": 1
288
        },
289
        "branchcode": {
290
          "description": "Pickup location",
291
          "type": "string"
292
        },
293
        "suspend_until": {
294
          "description": "Suspend until",
295
          "type": "string",
296
          "format": "date"
297
        }
298
      }
299
    },
300
    "biblionumber": {
301
      "description": "Biblio internal identifier"
302
    },
86
    "borrowernumber": {
303
    "borrowernumber": {
87
      "description": "Borrower internal identifier"
304
      "description": "Borrower internal identifier"
88
    },
305
    },
306
    "branchcode": {
307
      "description": "Branch code"
308
    },
309
    "itemnumber": {
310
      "description": "Item internal identifier"
311
    },
89
    "error": {
312
    "error": {
90
      "type": "object",
313
      "type": "object",
91
      "properties": {
314
      "properties": {
Lines 101-107 Link Here
101
      "name": "borrowernumber",
324
      "name": "borrowernumber",
102
      "in": "path",
325
      "in": "path",
103
      "description": "Internal borrower identifier",
326
      "description": "Internal borrower identifier",
104
      "required": "true",
327
      "required": true,
328
      "type": "integer"
329
    },
330
    "reserveIdPathParam": {
331
      "name": "reserve_id",
332
      "in": "path",
333
      "description": "Internal reserve identifier",
334
      "required": true,
105
      "type": "integer"
335
      "type": "integer"
106
    }
336
    }
107
  }
337
  }
(-)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