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

(-)a/Koha/REST/V1/Checkout.pm (+89 lines)
Line 0 Link Here
1
package Koha::REST::V1::Checkout;
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::Auth qw( haspermission );
23
use C4::Context;
24
use C4::Circulation;
25
use Koha::Checkouts;
26
27
sub list {
28
    my ($c, $args, $cb) = @_;
29
30
    my $borrowernumber = $c->param('borrowernumber');
31
    my $checkouts = C4::Circulation::GetIssues({
32
        borrowernumber => $borrowernumber
33
    });
34
35
    $c->$cb($checkouts, 200);
36
}
37
38
sub get {
39
    my ($c, $args, $cb) = @_;
40
41
    my $checkout_id = $args->{checkout_id};
42
    my $checkout = Koha::Checkouts->find($checkout_id);
43
44
    if (!$checkout) {
45
        return $c->$cb({
46
            error => "Checkout doesn't exist"
47
        }, 404);
48
    }
49
50
    return $c->$cb($checkout->unblessed, 200);
51
}
52
53
sub renew {
54
    my ($c, $args, $cb) = @_;
55
56
    my $checkout_id = $args->{checkout_id};
57
    my $checkout = Koha::Checkouts->find($checkout_id);
58
59
    if (!$checkout) {
60
        return $c->$cb({
61
            error => "Checkout doesn't exist"
62
        }, 404);
63
    }
64
65
    my $borrowernumber = $checkout->borrowernumber;
66
    my $itemnumber = $checkout->itemnumber;
67
68
    # Disallow renewal if OpacRenewalAllowed is off and user has insufficient rights
69
    unless (C4::Context->preference('OpacRenewalAllowed')) {
70
        my $user = $c->stash('koha.user');
71
        unless ($user && haspermission($user->userid, { circulate => "circulate_remaining_permissions" })) {
72
            return $c->$cb({error => "Opac Renewal not allowed"}, 403);
73
        }
74
    }
75
76
    my ($can_renew, $error) = C4::Circulation::CanBookBeRenewed(
77
        $borrowernumber, $itemnumber);
78
79
    if (!$can_renew) {
80
        return $c->$cb({error => "Renewal not authorized ($error)"}, 403);
81
    }
82
83
    AddRenewal($borrowernumber, $itemnumber, $checkout->branchcode);
84
    $checkout = Koha::Checkouts->find($checkout_id);
85
86
    return $c->$cb($checkout->unblessed, 200);
87
}
88
89
1;
(-)a/api/v1/swagger/definitions.json (+6 lines)
Lines 11-16 Link Here
11
  "hold": {
11
  "hold": {
12
    "$ref": "definitions/hold.json"
12
    "$ref": "definitions/hold.json"
13
  },
13
  },
14
  "checkouts": {
15
    "$ref": "definitions/checkouts.json"
16
  },
17
  "checkout": {
18
    "$ref": "definitions/checkout.json"
19
  },
14
  "holds": {
20
  "holds": {
15
    "$ref": "definitions/holds.json"
21
    "$ref": "definitions/holds.json"
16
  },
22
  },
(-)a/api/v1/swagger/definitions/checkout.json (+48 lines)
Line 0 Link Here
1
{
2
  "type": "object",
3
  "properties": {
4
    "issue_id": {
5
      "type": "string",
6
      "description": "internally assigned checkout identifier"
7
    },
8
    "borrowernumber": {
9
      "$ref": "../x-primitives.json#/borrowernumber"
10
    },
11
    "itemnumber": {
12
      "$ref": "../x-primitives.json#/itemnumber"
13
    },
14
    "date_due": {
15
      "description": "Due date"
16
    },
17
    "branchcode": {
18
      "$ref": "../x-primitives.json#/branchcode"
19
    },
20
    "issuingbranch": {
21
      "description": "Code of the branch where issue was made"
22
    },
23
    "returndate": {
24
      "description": "Date the item was returned"
25
    },
26
    "lastreneweddate": {
27
      "description": "Date the item was last renewed"
28
    },
29
    "return": {
30
      "description": "?"
31
    },
32
    "renewals": {
33
      "description": "Number of renewals"
34
    },
35
    "auto_renew": {
36
      "description": "Auto renewal"
37
    },
38
    "timestamp": {
39
      "description": "Last update time"
40
    },
41
    "issuedate": {
42
      "description": "Date the item was issued"
43
    },
44
    "onsite_checkout": {
45
      "description": "On site checkout"
46
    }
47
  }
48
}
(-)a/api/v1/swagger/definitions/checkouts.json (+6 lines)
Line 0 Link Here
1
{
2
  "type": "array",
3
  "items": {
4
    "$ref": "checkout.json"
5
  }
6
}
(-)a/api/v1/swagger/parameters.json (+3 lines)
Lines 17-22 Link Here
17
  "vendoridPathParam": {
17
  "vendoridPathParam": {
18
    "$ref": "parameters/vendor.json#/vendoridPathParam"
18
    "$ref": "parameters/vendor.json#/vendoridPathParam"
19
  },
19
  },
20
  "checkoutIdPathParam": {
21
    "$ref": "parameters/checkout.json#/checkoutIdPathParam"
22
  },
20
  "match": {
23
  "match": {
21
    "name": "_match",
24
    "name": "_match",
22
    "in": "query",
25
    "in": "query",
(-)a/api/v1/swagger/parameters/checkout.json (+9 lines)
Line 0 Link Here
1
{
2
  "checkoutIdPathParam": {
3
    "name": "checkout_id",
4
    "in": "path",
5
    "description": "Internal checkout identifier",
6
    "required": true,
7
    "type": "integer"
8
  }
9
}
(-)a/api/v1/swagger/paths.json (+6 lines)
Lines 8-13 Link Here
8
  "/acquisitions/vendors/{vendor_id}": {
8
  "/acquisitions/vendors/{vendor_id}": {
9
    "$ref": "paths/acquisitions_vendors.json#/~1acquisitions~1vendors~1{vendor_id}"
9
    "$ref": "paths/acquisitions_vendors.json#/~1acquisitions~1vendors~1{vendor_id}"
10
  },
10
  },
11
  "/checkouts": {
12
    "$ref": "paths/checkouts.json#/~1checkouts"
13
  },
14
  "/checkouts/{checkout_id}": {
15
    "$ref": "paths/checkouts.json#/~1checkouts~1{checkout_id}"
16
  },
11
  "/cities": {
17
  "/cities": {
12
    "$ref": "paths/cities.json#/~1cities"
18
    "$ref": "paths/cities.json#/~1cities"
13
  },
19
  },
(-)a/api/v1/swagger/paths/checkouts.json (+97 lines)
Line 0 Link Here
1
{
2
  "/checkouts": {
3
    "get": {
4
      "operationId": "listCheckouts",
5
      "tags": ["patrons", "checkouts"],
6
      "parameters": [{
7
        "$ref": "../parameters.json#/borrowernumberQueryParam"
8
      }],
9
      "produces": [
10
        "application/json"
11
      ],
12
      "responses": {
13
        "200": {
14
          "description": "A list of checkouts",
15
          "schema": {
16
            "$ref": "../definitions.json#/checkouts"
17
          }
18
        },
19
        "403": {
20
          "description": "Access forbidden",
21
          "schema": { "$ref": "../definitions.json#/error" }
22
        },
23
        "404": {
24
          "description": "Patron not found",
25
          "schema": { "$ref": "../definitions.json#/error" }
26
        }
27
      },
28
      "x-koha-authorization": {
29
        "allow-owner": true,
30
        "allow-guarantor": true,
31
        "permissions": {
32
          "circulate": "circulate_remaining_permissions"
33
        }
34
      }
35
    }
36
  },
37
  "/checkouts/{checkout_id}": {
38
    "get": {
39
      "operationId": "getCheckout",
40
      "tags": ["patrons", "checkouts"],
41
      "parameters": [{
42
        "$ref": "../parameters.json#/checkoutIdPathParam"
43
      }],
44
      "produces": ["application/json"],
45
      "responses": {
46
        "200": {
47
          "description": "Updated borrower's checkout",
48
          "schema": { "$ref": "../definitions.json#/checkout" }
49
        },
50
        "403": {
51
          "description": "Access forbidden",
52
          "schema": { "$ref": "../definitions.json#/error" }
53
        },
54
        "404": {
55
          "description": "Checkout not found",
56
          "schema": { "$ref": "../definitions.json#/error" }
57
        }
58
      },
59
      "x-koha-authorization": {
60
        "allow-owner": true,
61
        "allow-guarantor": true,
62
        "permissions": {
63
          "circulate": "circulate_remaining_permissions"
64
        }
65
      }
66
    },
67
    "put": {
68
      "operationId": "renewCheckout",
69
      "tags": ["patrons", "checkouts"],
70
      "parameters": [{
71
        "$ref": "../parameters.json#/checkoutIdPathParam"
72
      }],
73
      "produces": ["application/json"],
74
      "responses": {
75
        "200": {
76
          "description": "Updated borrower's checkout",
77
          "schema": { "$ref": "../definitions.json#/checkout" }
78
        },
79
        "403": {
80
          "description": "Cannot renew checkout",
81
          "schema": { "$ref": "../definitions.json#/error" }
82
        },
83
        "404": {
84
          "description": "Checkout not found",
85
          "schema": { "$ref": "../definitions.json#/error" }
86
        }
87
      },
88
      "x-koha-authorization": {
89
        "allow-owner": true,
90
        "allow-guarantor": true,
91
        "permissions": {
92
          "circulate": "circulate_remaining_permissions"
93
        }
94
      }
95
    }
96
  }
97
}
(-)a/t/db_dependent/api/v1/checkouts.t (-1 / +228 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 => 57;
21
use Test::MockModule;
22
use Test::Mojo;
23
use t::lib::Mocks;
24
use t::lib::TestBuilder;
25
26
use DateTime;
27
use MARC::Record;
28
29
use C4::Context;
30
use C4::Biblio;
31
use C4::Circulation;
32
use C4::Items;
33
34
use Koha::Database;
35
use Koha::Patron;
36
37
my $schema = Koha::Database->schema;
38
$schema->storage->txn_begin;
39
my $dbh = C4::Context->dbh;
40
my $builder = t::lib::TestBuilder->new;
41
$dbh->{RaiseError} = 1;
42
43
$ENV{REMOTE_ADDR} = '127.0.0.1';
44
my $t = Test::Mojo->new('Koha::REST::V1');
45
46
$dbh->do('DELETE FROM issues');
47
$dbh->do('DELETE FROM items');
48
$dbh->do('DELETE FROM issuingrules');
49
my $loggedinuser = $builder->build({ source => 'Borrower' });
50
51
$dbh->do(q{
52
    INSERT INTO user_permissions (borrowernumber, module_bit, code)
53
    VALUES (?, 1, 'circulate_remaining_permissions')
54
}, undef, $loggedinuser->{borrowernumber});
55
56
my $session = C4::Auth::get_session('');
57
$session->param('number', $loggedinuser->{ borrowernumber });
58
$session->param('id', $loggedinuser->{ userid });
59
$session->param('ip', '127.0.0.1');
60
$session->param('lasttime', time());
61
$session->flush;
62
63
my $patron = $builder->build({ source => 'Borrower', value => { flags => 0 } });
64
my $borrowernumber = $patron->{borrowernumber};
65
my $patron_session = C4::Auth::get_session('');
66
$patron_session->param('number', $borrowernumber);
67
$patron_session->param('id', $patron->{ userid });
68
$patron_session->param('ip', '127.0.0.1');
69
$patron_session->param('lasttime', time());
70
$patron_session->flush;
71
72
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
73
my $module = new Test::MockModule('C4::Context');
74
$module->mock('userenv', sub { { branch => $branchcode } });
75
76
my $tx = $t->ua->build_tx(GET => "/api/v1/checkouts?borrowernumber=$borrowernumber");
77
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
78
$t->request_ok($tx)
79
  ->status_is(200)
80
  ->json_is([]);
81
82
my $notexisting_borrowernumber = $borrowernumber + 1;
83
$tx = $t->ua->build_tx(GET => "/api/v1/checkouts?borrowernumber=$notexisting_borrowernumber");
84
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
85
$t->request_ok($tx)
86
  ->status_is(200)
87
  ->json_is([]);
88
89
my $biblionumber = create_biblio('RESTful Web APIs');
90
my $itemnumber1 = create_item($biblionumber, 'TEST000001');
91
my $itemnumber2 = create_item($biblionumber, 'TEST000002');
92
my $itemnumber3 = create_item($biblionumber, 'TEST000003');
93
94
my $date_due = DateTime->now->add(weeks => 2);
95
my $issue1 = C4::Circulation::AddIssue($patron, 'TEST000001', $date_due);
96
my $date_due1 = Koha::DateUtils::dt_from_string( $issue1->date_due );
97
my $issue2 = C4::Circulation::AddIssue($patron, 'TEST000002', $date_due);
98
my $date_due2 = Koha::DateUtils::dt_from_string( $issue2->date_due );
99
my $issue3 = C4::Circulation::AddIssue($loggedinuser, 'TEST000003', $date_due);
100
my $date_due3 = Koha::DateUtils::dt_from_string( $issue3->date_due );
101
102
$tx = $t->ua->build_tx(GET => "/api/v1/checkouts?borrowernumber=$borrowernumber");
103
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
104
$t->request_ok($tx)
105
  ->status_is(200)
106
  ->json_is('/0/borrowernumber' => $borrowernumber)
107
  ->json_is('/0/itemnumber' => $itemnumber1)
108
  ->json_is('/0/date_due' => $date_due1->ymd . ' ' . $date_due1->hms)
109
  ->json_is('/1/borrowernumber' => $borrowernumber)
110
  ->json_is('/1/itemnumber' => $itemnumber2)
111
  ->json_is('/1/date_due' => $date_due2->ymd . ' ' . $date_due2->hms)
112
  ->json_hasnt('/2');
113
114
$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/".$issue3->issue_id);
115
$tx->req->cookies({name => 'CGISESSID', value => $patron_session->id});
116
$t->request_ok($tx)
117
  ->status_is(403)
118
  ->json_is({ error => "Authorization failure. Missing required permission(s).",
119
              required_permissions => { circulate => "circulate_remaining_permissions" }
120
						});
121
122
$tx = $t->ua->build_tx(GET => "/api/v1/checkouts?borrowernumber=".$loggedinuser->{borrowernumber});
123
$tx->req->cookies({name => 'CGISESSID', value => $patron_session->id});
124
$t->request_ok($tx)
125
  ->status_is(403)
126
  ->json_is({ error => "Authorization failure. Missing required permission(s).",
127
						  required_permissions => { circulate => "circulate_remaining_permissions" }
128
					  });
129
130
$tx = $t->ua->build_tx(GET => "/api/v1/checkouts?borrowernumber=$borrowernumber");
131
$tx->req->cookies({name => 'CGISESSID', value => $patron_session->id});
132
$t->request_ok($tx)
133
  ->status_is(200)
134
  ->json_is('/0/borrowernumber' => $borrowernumber)
135
  ->json_is('/0/itemnumber' => $itemnumber1)
136
  ->json_is('/0/date_due' => $date_due1->ymd . ' ' . $date_due1->hms)
137
  ->json_is('/1/borrowernumber' => $borrowernumber)
138
  ->json_is('/1/itemnumber' => $itemnumber2)
139
  ->json_is('/1/date_due' => $date_due2->ymd . ' ' . $date_due2->hms)
140
  ->json_hasnt('/2');
141
142
$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/" . $issue1->issue_id);
143
$tx->req->cookies({name => 'CGISESSID', value => $patron_session->id});
144
$t->request_ok($tx)
145
  ->status_is(200)
146
  ->json_is('/borrowernumber' => $borrowernumber)
147
  ->json_is('/itemnumber' => $itemnumber1)
148
  ->json_is('/date_due' => $date_due1->ymd . ' ' . $date_due1->hms)
149
  ->json_hasnt('/1');
150
151
$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/" . $issue1->issue_id);
152
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
153
$t->request_ok($tx)
154
  ->status_is(200)
155
  ->json_is('/date_due' => $date_due1->ymd . ' ' . $date_due1->hms);
156
157
$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/" . $issue2->issue_id);
158
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
159
$t->request_ok($tx)
160
  ->status_is(200)
161
  ->json_is('/date_due' => $date_due2->ymd . ' ' . $date_due2->hms);
162
163
164
$dbh->do('DELETE FROM issuingrules');
165
$dbh->do(q{
166
    INSERT INTO issuingrules (categorycode, branchcode, itemtype, renewalperiod, renewalsallowed)
167
    VALUES (?, ?, ?, ?, ?)
168
}, {}, '*', '*', '*', 7, 1);
169
170
my $expected_datedue = DateTime->now->add(days => 14)->set(hour => 23, minute => 59, second => 0);
171
$tx = $t->ua->build_tx(PUT => "/api/v1/checkouts/" . $issue1->issue_id);
172
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
173
$t->request_ok($tx)
174
  ->status_is(200)
175
  ->json_is('/date_due' => $expected_datedue->ymd . ' ' . $expected_datedue->hms);
176
177
$tx = $t->ua->build_tx(PUT => "/api/v1/checkouts/" . $issue3->issue_id);
178
$tx->req->cookies({name => 'CGISESSID', value => $patron_session->id});
179
$t->request_ok($tx)
180
  ->status_is(403)
181
  ->json_is({ error => "Authorization failure. Missing required permission(s).",
182
              required_permissions => { circulate => "circulate_remaining_permissions" }
183
						});
184
185
t::lib::Mocks::mock_preference( "OpacRenewalAllowed", 0 );
186
$tx = $t->ua->build_tx(PUT => "/api/v1/checkouts/" . $issue2->issue_id);
187
$tx->req->cookies({name => 'CGISESSID', value => $patron_session->id});
188
$t->request_ok($tx)
189
  ->status_is(403)
190
  ->json_is({ error => "Opac Renewal not allowed"	});
191
192
t::lib::Mocks::mock_preference( "OpacRenewalAllowed", 1 );
193
$tx = $t->ua->build_tx(PUT => "/api/v1/checkouts/" . $issue2->issue_id);
194
$tx->req->cookies({name => 'CGISESSID', value => $patron_session->id});
195
$t->request_ok($tx)
196
  ->status_is(200)
197
  ->json_is('/date_due' => $expected_datedue->ymd . ' ' . $expected_datedue->hms);
198
199
$tx = $t->ua->build_tx(PUT => "/api/v1/checkouts/" . $issue1->issue_id);
200
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
201
$t->request_ok($tx)
202
  ->status_is(403)
203
  ->json_is({ error => 'Renewal not authorized (too_many)' });
204
205
sub create_biblio {
206
    my ($title) = @_;
207
208
    my $record = new MARC::Record;
209
    $record->append_fields(
210
        new MARC::Field('200', ' ', ' ', a => $title),
211
    );
212
213
    my ($biblionumber) = C4::Biblio::AddBiblio($record, '');
214
215
    return $biblionumber;
216
}
217
218
sub create_item {
219
    my ($biblionumber, $barcode) = @_;
220
221
    my $item = {
222
        barcode => $barcode,
223
    };
224
225
    my $itemnumber = C4::Items::AddItem($item, $biblionumber);
226
227
    return $itemnumber;
228
}

Return to bug 13895