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

(-)a/Koha/REST/V1/Items.pm (+216 lines)
Line 0 Link Here
1
package Koha::REST::V1::Items;
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
use Mojo::JSON;
22
23
use C4::Auth qw( haspermission );
24
use C4::Items qw( GetHiddenItemnumbers );
25
26
use Koha::Items;
27
28
use Try::Tiny;
29
30
sub get {
31
    my $c = shift->openapi->valid_input or return;
32
33
    my $item;
34
    try {
35
        $item = Koha::Items->find($c->validation->param('item_id'));
36
        return $c->render( status => 200, openapi => _to_api( $item->TO_JSON ) );
37
    }
38
    catch {
39
        unless ( defined $item ) {
40
            return $c->render( status => 404,
41
                               openapi => { error => 'Item not found'} );
42
        }
43
        if ( $_->isa('DBIx::Class::Exception') ) {
44
            return $c->render( status  => 500,
45
                               openapi => { error => $_->{msg} } );
46
        }
47
        else {
48
            return $c->render( status => 500,
49
                openapi => { error => "Something went wrong, check the logs."} );
50
        }
51
    };
52
}
53
54
=head3 _to_api
55
56
Helper function that maps unblessed Koha::Hold objects into REST api
57
attribute names.
58
59
=cut
60
61
sub _to_api {
62
    my $item = shift;
63
64
    # Rename attributes
65
    foreach my $column ( keys %{ $Koha::REST::V1::Items::to_api_mapping } ) {
66
        my $mapped_column = $Koha::REST::V1::Items::to_api_mapping->{$column};
67
        if (    exists $item->{ $column }
68
             && defined $mapped_column )
69
        {
70
            # key != undef
71
            $item->{ $mapped_column } = delete $item->{ $column };
72
        }
73
        elsif (    exists $item->{ $column }
74
                && !defined $mapped_column )
75
        {
76
            # key == undef
77
            delete $item->{ $column };
78
        }
79
    }
80
81
    return $item;
82
}
83
84
=head3 _to_model
85
86
Helper function that maps REST api objects into Koha::Hold
87
attribute names.
88
89
=cut
90
91
sub _to_model {
92
    my $item = shift;
93
94
    foreach my $attribute ( keys %{ $Koha::REST::V1::Items::to_model_mapping } ) {
95
        my $mapped_attribute = $Koha::REST::V1::Items::to_model_mapping->{$attribute};
96
        if (    exists $item->{ $attribute }
97
             && defined $mapped_attribute )
98
        {
99
            # key => !undef
100
            $item->{ $mapped_attribute } = delete $item->{ $attribute };
101
        }
102
        elsif (    exists $item->{ $attribute }
103
                && !defined $mapped_attribute )
104
        {
105
            # key => undef / to be deleted
106
            delete $item->{ $attribute };
107
        }
108
    }
109
110
    return $item;
111
}
112
113
=head2 Global variables
114
115
=head3 $to_api_mapping
116
117
=cut
118
119
our $to_api_mapping = {
120
    itemnumber => 'item_id',
121
    biblionumber => 'biblio_id',
122
    biblioitemnumber => undef,
123
    barcode => 'external_id',
124
    dateaccessioned => 'acquisition_date',
125
    booksellerid => 'acquisition_source',
126
    homebranch => 'home_library_id',
127
    price => 'purchase_price',
128
    replacementprice => 'replacement_price',
129
    replacementpricedate => 'replacement_price_date',
130
    datelastborrowed => 'last_checkout_date',
131
    datelastseen => 'last_seen_date',
132
    stack => undef,
133
    notforloan => 'not_for_loan_status',
134
    damaged => 'damaged_status',
135
    damaged_on => 'damaged_date',
136
    itemlost => 'lost_status',
137
    itemlost_on => 'lost_date',
138
    withdrawn => 'withdrawn',
139
    withdrawn_on => 'withdrawn_date',
140
    itemcallnumber => 'callnumber',
141
    coded_location_qualifier => 'coded_location_qualifier',
142
    issues => 'checkouts_count',
143
    renewals => 'renewals_count',
144
    reserves => 'holds_count',
145
    restricted => 'restricted_status',
146
    itemnotes => 'public_notes',
147
    itemnotes_nonpublic => 'internal_notes',
148
    holdingbranch => 'holding_library_id',
149
    paidfor => undef,
150
    timestamp => 'timestamp',
151
    location => 'location',
152
    permanent_location => 'permanent_location',
153
    onloan => 'checked_out_date',
154
    cn_source => 'call_number_source',
155
    cn_sort => 'call_number_sort',
156
    ccode => 'collection_code',
157
    materials => 'materials_notes',
158
    uri => 'uri',
159
    itype => 'item_type',
160
    more_subfields_xml => 'extended_subfields',
161
    enumchron => 'serial_issue_number',
162
    copynumber => 'copy_number',
163
    stocknumber => 'inventory_number',
164
    new_status => 'new_status'
165
};
166
167
=head3 $to_model_mapping
168
169
=cut
170
171
our $to_model_mapping = {
172
    item_id => 'itemnumber',
173
    biblio_id => 'biblionumber',
174
    external_id => 'barcode',
175
    acquisition_date => 'dateaccessioned',
176
    acquisition_source => 'booksellerid',
177
    home_library_id => 'homebranch',
178
    purchase_price => 'price',
179
    replacement_price => 'replacementprice',
180
    replacement_price_date => 'replacementpricedate',
181
    last_checkout_date => 'datelastborrowed',
182
    last_seen_date => 'datelastseen',
183
    not_for_loan_status => 'notforloan',
184
    damaged_status => 'damaged',
185
    damaged_date => 'damaged_on',
186
    lost_status => 'itemlost',
187
    lost_date => 'itemlost_on',
188
    withdrawn => 'withdrawn',
189
    withdrawn_date => 'withdrawn_on',
190
    callnumber => 'itemcallnumber',
191
    coded_location_qualifier => 'coded_location_qualifier',
192
    checkouts_count => 'issues',
193
    renewals_count => 'renewals',
194
    holds_count => 'reserves',
195
    restricted_status => 'restricted',
196
    public_notes => 'itemnotes',
197
    internal_notes => 'itemnotes_nonpublic',
198
    holding_library_id => 'holdingbranch',
199
    timestamp => 'timestamp',
200
    location => 'location',
201
    permanent_location => 'permanent_location',
202
    checked_out_date => 'onloan',
203
    call_number_source => 'cn_source',
204
    call_number_sort => 'cn_sort',
205
    collection_code => 'ccode',
206
    materials_notes => 'materials',
207
    uri => 'uri',
208
    item_type => 'itype',
209
    extended_subfields => 'more_subfields_xml',
210
    serial_issue_number => 'enumchron',
211
    copy_number => 'copynumber',
212
    inventory_number => 'stocknumber',
213
    new_status => 'new_status'
214
};
215
216
1;
(-)a/api/v1/swagger/definitions.json (+3 lines)
Lines 23-28 Link Here
23
  "library": {
23
  "library": {
24
    "$ref": "definitions/library.json"
24
    "$ref": "definitions/library.json"
25
  },
25
  },
26
  "item": {
27
    "$ref": "definitions/item.json"
28
  },
26
  "patron": {
29
  "patron": {
27
    "$ref": "definitions/patron.json"
30
    "$ref": "definitions/patron.json"
28
  },
31
  },
(-)a/api/v1/swagger/definitions/item.json (+183 lines)
Line 0 Link Here
1
{
2
  "type": "object",
3
  "properties": {
4
    "item_id": {
5
      "type": "integer",
6
      "description": "Internal item identifier"
7
    },
8
    "biblio_id": {
9
      "type": "integer",
10
      "description": "Internal identifier for the parent bibliographic record"
11
    },
12
    "external_id": {
13
      "type": ["string", "null"],
14
      "description": "The item's barcode"
15
    },
16
    "acquisition_date": {
17
      "type": ["string", "null"],
18
      "format": "date",
19
      "description": "The date the item was acquired"
20
    },
21
    "acquisition_source": {
22
      "type": ["string", "null"],
23
      "description": "Information about the acquisition source (it is not really a vendor id)"
24
    },
25
    "home_library_id": {
26
      "type": ["string", "null"],
27
      "description": "Internal library id for the library the item belongs to"
28
    },
29
    "purchase_price": {
30
      "type": ["number", "null"],
31
      "description": "Purchase price"
32
    },
33
    "replacement_price": {
34
      "type": ["number", "null"],
35
      "description": "Cost the library charges to replace the item (e.g. if lost)"
36
    },
37
    "replacement_price_date": {
38
      "type": ["string", "null"],
39
      "format": "date",
40
      "description": "The date the replacement price is effective from"
41
    },
42
    "last_checkout_date": {
43
      "type": ["string", "null"],
44
      "format": "date",
45
      "description": "The date the item was last checked out"
46
    },
47
    "last_seen_date": {
48
      "type": ["string", "null"],
49
      "format": "date",
50
      "description": "The date the item barcode was last scanned"
51
    },
52
    "not_for_loan_status": {
53
      "type": "integer",
54
      "description": "Authorized value defining why this item is not for loan"
55
    },
56
    "damaged_status": {
57
      "type": "integer",
58
      "description": "Authorized value defining this item as damaged"
59
    },
60
    "damaged_date": {
61
      "type": ["string", "null"],
62
      "description": "The date and time an item was last marked as damaged, NULL if not damaged"
63
    },
64
    "lost_status": {
65
      "type": "integer",
66
      "description": "Authorized value defining this item as lost"
67
    },
68
    "lost_date": {
69
      "type": ["string", "null"],
70
      "format": "date-time",
71
      "description": "The date and time an item was last marked as lost, NULL if not lost"
72
    },
73
    "withdrawn": {
74
      "type": "integer",
75
      "description": "Authorized value defining this item as withdrawn"
76
    },
77
    "withdrawn_date": {
78
      "type": ["string", "null"],
79
      "format": "date-time",
80
      "description": "The date and time an item was last marked as withdrawn, NULL if not withdrawn"
81
    },
82
    "callnumber": {
83
      "type": ["string", "null"],
84
      "description": "Call number for this item"
85
    },
86
    "coded_location_qualifier": {
87
      "type": ["string", "null"],
88
      "description": "Coded location qualifier"
89
    },
90
    "checkouts_count": {
91
      "type": ["integer", "null"],
92
      "description": "Number of times this item has been checked out/issued"
93
    },
94
    "renewals_count": {
95
      "type": ["integer", "null"],
96
      "description": "Number of times this item has been renewed"
97
    },
98
    "holds_count": {
99
      "type": ["integer", "null"],
100
      "description": "Number of times this item has been placed on hold/reserved"
101
    },
102
    "restricted_status": {
103
      "type": ["integer", "null"],
104
      "description": "Authorized value defining use restrictions for this item"
105
    },
106
    "public_notes": {
107
      "type": ["string", "null"],
108
      "description": "Public notes on this item"
109
    },
110
    "internal_notes": {
111
      "type": ["string", "null"],
112
      "description": "Non-public notes on this item"
113
    },
114
    "holding_library_id": {
115
      "type": ["string", "null"],
116
      "description": "Library that is currently in possession item"
117
    },
118
    "timestamp": {
119
      "type": "string",
120
      "format": "date-time",
121
      "description": "Date and time this item was last altered"
122
    },
123
    "location": {
124
      "type": ["string", "null"],
125
      "description": "Authorized value for the shelving location for this item"
126
    },
127
    "permanent_location": {
128
      "type": ["string", "null"],
129
      "description": "Linked to the CART and PROC temporary locations feature, stores the permanent shelving location"
130
    },
131
    "checked_out_date": {
132
      "type": ["string", "null"],
133
      "format": "date",
134
      "description": "Defines if item is checked out (NULL for not checked out, and checkout date for checked out)"
135
    },
136
    "call_number_source": {
137
      "type": ["string", "null"],
138
      "description": "Classification source used on this item"
139
    },
140
    "call_number_sort": {
141
      "type": ["string", "null"],
142
      "description": "?"
143
    },
144
    "collection_code": {
145
      "type": ["string", "null"],
146
      "description": "Authorized value for the collection code associated with this item"
147
    },
148
    "materials_notes": {
149
      "type": ["string", "null"],
150
      "description": "Materials specified"
151
    },
152
    "uri": {
153
      "type": ["string", "null"],
154
      "description": "URL for the item"
155
    },
156
    "item_type": {
157
      "type": ["string", "null"],
158
      "description": "Itemtype defining the type for this item"
159
    },
160
    "extended_subfields": {
161
      "type": ["string", "null"],
162
      "description": "Additional 952 subfields in XML format"
163
    },
164
    "serial_issue_number": {
165
      "type": ["string", "null"],
166
      "description": "serial enumeration/chronology for the item"
167
    },
168
    "copy_number": {
169
      "type": ["string", "null"],
170
      "description": "Copy number"
171
    },
172
    "inventory_number": {
173
      "type": ["string", "null"],
174
      "description": "Inventory number"
175
    },
176
    "new_status": {
177
      "type": ["string", "null"],
178
      "description": "'new' value, whatever free-text information."
179
    }
180
  },
181
  "additionalProperties": false,
182
  "required": ["item_id", "biblio_id", "not_for_loan_status", "damaged_status", "lost_status", "withdrawn"]
183
}
(-)a/api/v1/swagger/parameters.json (+3 lines)
Lines 17-22 Link Here
17
  "library_id_pp": {
17
  "library_id_pp": {
18
    "$ref": "parameters/library.json#/library_id_pp"
18
    "$ref": "parameters/library.json#/library_id_pp"
19
  },
19
  },
20
  "item_id_pp": {
21
    "$ref": "parameters/item.json#/item_id_pp"
22
  },
20
  "vendoridPathParam": {
23
  "vendoridPathParam": {
21
    "$ref": "parameters/vendor.json#/vendoridPathParam"
24
    "$ref": "parameters/vendor.json#/vendoridPathParam"
22
  },
25
  },
(-)a/api/v1/swagger/parameters/item.json (+9 lines)
Line 0 Link Here
1
{
2
  "item_id_pp": {
3
    "name": "item_id",
4
    "in": "path",
5
    "description": "Internal item identifier",
6
    "required": true,
7
    "type": "integer"
8
  }
9
}
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 47-52 Link Here
47
  "/checkouts/{checkout_id}/allows_renewal": {
47
  "/checkouts/{checkout_id}/allows_renewal": {
48
    "$ref": "paths/checkouts.json#/~1checkouts~1{checkout_id}~1allows_renewal"
48
    "$ref": "paths/checkouts.json#/~1checkouts~1{checkout_id}~1allows_renewal"
49
  },
49
  },
50
  "/items/{item_id}": {
51
    "$ref": "paths/items.json#/~1items~1{item_id}"
52
  },
50
  "/patrons": {
53
  "/patrons": {
51
    "$ref": "paths/patrons.json#/~1patrons"
54
    "$ref": "paths/patrons.json#/~1patrons"
52
  },
55
  },
(-)a/api/v1/swagger/paths/items.json (+52 lines)
Line 0 Link Here
1
{
2
  "/items/{item_id}": {
3
    "get": {
4
      "x-mojo-to": "Items#get",
5
      "operationId": "getItem",
6
      "tags": ["items"],
7
      "parameters": [{
8
          "$ref": "../parameters.json#/item_id_pp"
9
        }
10
      ],
11
      "consumes": ["application/json"],
12
      "produces": ["application/json"],
13
      "responses": {
14
        "200": {
15
          "description": "An item",
16
          "schema": {
17
            "$ref": "../definitions.json#/item"
18
          }
19
        },
20
        "400": {
21
          "description": "Missing or wrong parameters",
22
          "schema": {
23
            "$ref": "../definitions.json#/error"
24
          }
25
        },
26
        "404": {
27
          "description": "Item not found",
28
          "schema": {
29
            "$ref": "../definitions.json#/error"
30
          }
31
        },
32
        "500": {
33
          "description": "Internal server error",
34
          "schema": {
35
            "$ref": "../definitions.json#/error"
36
          }
37
        },
38
        "503": {
39
          "description": "Under maintenance",
40
          "schema": {
41
            "$ref": "../definitions.json#/error"
42
          }
43
        }
44
      },
45
      "x-koha-authorization": {
46
        "permissions": {
47
          "catalogue": "1"
48
        }
49
      }
50
    }
51
  }
52
}
(-)a/t/db_dependent/api/v1/items.t (-1 / +81 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env perl
2
3
# Copyright 2016 Koha-Suomi
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use Test::More tests => 1;
23
use Test::Mojo;
24
use Test::Warn;
25
26
use t::lib::TestBuilder;
27
use t::lib::Mocks;
28
29
use C4::Auth;
30
use Koha::Items;
31
use Koha::Database;
32
33
my $schema  = Koha::Database->new->schema;
34
my $builder = t::lib::TestBuilder->new;
35
36
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
37
38
my $t = Test::Mojo->new('Koha::REST::V1');
39
40
subtest 'get() tests' => sub {
41
42
    plan tests => 9;
43
44
    $schema->storage->txn_begin;
45
46
    my $item = $builder->build_object( { class => 'Koha::Items' } );
47
    my $patron = $builder->build_object({
48
        class => 'Koha::Patrons',
49
        value => { flags => 4 }
50
    });
51
52
    my $nonprivilegedpatron = $builder->build_object({
53
        class => 'Koha::Patrons',
54
        value => { flags => 0 }
55
    });
56
57
    my $password = 'thePassword123';
58
59
    $nonprivilegedpatron->set_password({ password => $password, skip_validation => 1 });
60
    my $userid = $nonprivilegedpatron->userid;
61
62
    $t->get_ok( "//$userid:$password@/api/v1/items/" . $item->itemnumber )
63
      ->status_is(403)
64
      ->json_is( '/error' => 'Authorization failure. Missing required permission(s).' );
65
66
    $patron->set_password({ password => $password, skip_validation => 1 });
67
    $userid = $patron->userid;
68
69
    $t->get_ok( "//$userid:$password@/api/v1/items/" . $item->itemnumber )
70
      ->status_is( 200, 'SWAGGER3.2.2' )
71
      ->json_is( '' => Koha::REST::V1::Items::_to_api( $item->TO_JSON ), 'SWAGGER3.3.2' );
72
73
    my $non_existent_code = $item->itemnumber;
74
    $item->delete;
75
76
    $t->get_ok( "//$userid:$password@/api/v1/items/" . $non_existent_code )
77
      ->status_is(404)
78
      ->json_is( '/error' => 'Item not found' );
79
80
    $schema->storage->txn_rollback;
81
};

Return to bug 16825