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

(-)a/Koha/Biblio.pm (-7 / +7 lines)
Lines 33-40 use base qw(Koha::Object); Link Here
33
use Koha::Acquisition::Orders;
33
use Koha::Acquisition::Orders;
34
use Koha::ArticleRequest::Status;
34
use Koha::ArticleRequest::Status;
35
use Koha::ArticleRequests;
35
use Koha::ArticleRequests;
36
use Koha::Biblio::ItemGroups;
36
use Koha::Biblio::Metadatas;
37
use Koha::Biblio::Metadatas;
37
use Koha::Biblio::Volumes;
38
use Koha::Biblioitems;
38
use Koha::Biblioitems;
39
use Koha::CirculationRules;
39
use Koha::CirculationRules;
40
use Koha::Item::Transfer::Limits;
40
use Koha::Item::Transfer::Limits;
Lines 113-131 sub active_orders { Link Here
113
    return $self->orders->search({ datecancellationprinted => undef });
113
    return $self->orders->search({ datecancellationprinted => undef });
114
}
114
}
115
115
116
=head3 volumes
116
=head3 item_groups
117
117
118
my $volumes = $biblio->volumes();
118
my $item_groups = $biblio->item_groups();
119
119
120
Returns a Koha::Biblio::Volumes object
120
Returns a Koha::Biblio::ItemGroups object
121
121
122
=cut
122
=cut
123
123
124
sub volumes {
124
sub item_groups {
125
    my ( $self ) = @_;
125
    my ( $self ) = @_;
126
126
127
    my $volumes = $self->_result->volumes;
127
    my $item_groups = $self->_result->item_groups;
128
    return Koha::Biblio::Volumes->_new_from_dbic($volumes);
128
    return Koha::Biblio::ItemGroups->_new_from_dbic($item_groups);
129
}
129
}
130
130
131
=head3 can_article_request
131
=head3 can_article_request
(-)a/Koha/Biblio/Volume.pm (-21 / +19 lines)
Lines 1-4 Link Here
1
package Koha::Biblio::Volume;
1
package Koha::Biblio::ItemGroup;
2
2
3
# This file is part of Koha.
3
# This file is part of Koha.
4
#
4
#
Lines 19-31 use Modern::Perl; Link Here
19
19
20
use base qw(Koha::Object);
20
use base qw(Koha::Object);
21
21
22
use Koha::Biblio::Volume::Items;
22
use Koha::Biblio::ItemGroup::Items;
23
use Koha::Exceptions::Object;
23
use Koha::Exceptions::Object;
24
use Koha::Items;
24
use Koha::Items;
25
25
26
=head1 NAME
26
=head1 NAME
27
27
28
Koha::Volume - Koha Volume Object class
28
Koha::Biblio::ItemGroup - Koha ItemGroup Object class
29
29
30
=head1 API
30
=head1 API
31
31
Lines 33-39 Koha::Volume - Koha Volume Object class Link Here
33
33
34
=head3 store
34
=head3 store
35
35
36
    $volume->store;
36
    $item_group->store;
37
37
38
Overloaded I<store> method that takes care of creation date handling.
38
Overloaded I<store> method that takes care of creation date handling.
39
39
Lines 56-79 sub store { Link Here
56
56
57
=head3 items
57
=head3 items
58
58
59
    my $items = $volume->items;
59
    my $items = $item_group->items;
60
60
61
Returns all the items linked to the volume.
61
Returns all the items linked to the item group.
62
62
63
=cut
63
=cut
64
64
65
sub items {
65
sub items {
66
    my ($self) = @_;
66
    my ($self) = @_;
67
67
68
    my $items_rs = $self->_result->volume_items;
68
    my $items_rs = $self->_result->item_group_items;
69
    my @item_numbers = $items_rs->get_column('itemnumber')->all;
69
    my @item_ids = $items_rs->get_column('item_id')->all;
70
70
71
    return Koha::Items->new->empty unless @item_numbers;
71
    return Koha::Items->new->empty unless @item_ids;
72
72
73
    return Koha::Items->search(
73
    return Koha::Items->search(
74
        {
74
        {
75
            itemnumber => {
75
            itemnumber => {
76
                -in => \@item_numbers
76
                -in => \@item_ids
77
            }
77
            }
78
        }
78
        }
79
    );
79
    );
Lines 81-87 sub items { Link Here
81
81
82
=head3 add_item
82
=head3 add_item
83
83
84
    $volume->add_item({ item_id => $item_id });
84
    $item_group->add_item({ item_id => $item_id });
85
85
86
=cut
86
=cut
87
87
Lines 91-106 sub add_item { Link Here
91
    my $item_id = $params->{item_id};
91
    my $item_id = $params->{item_id};
92
92
93
    my $item = Koha::Items->find( $item_id );
93
    my $item = Koha::Items->find( $item_id );
94
    unless ( $item->biblionumber == $self->biblionumber ) {
94
    unless ( $item->biblionumber == $self->biblio_id ) {
95
        Koha::Exceptions::Object::FKConstraint->throw(
95
        Koha::Exceptions::Object::FKConstraint->throw(
96
            broken_fk => 'biblionumber'
96
            broken_fk => 'biblio_id'
97
        );
97
        );
98
    }
98
    }
99
99
100
    Koha::Biblio::Volume::Item->new(
100
    Koha::Biblio::ItemGroup::Item->new(
101
        {
101
        {
102
            itemnumber => $item_id,
102
            item_group_id => $self->id,
103
            volume_id  => $self->id
103
            item_id       => $item_id,
104
        }
104
        }
105
    )->store;
105
    )->store;
106
106
Lines 109-123 sub add_item { Link Here
109
109
110
=head3 to_api_mapping
110
=head3 to_api_mapping
111
111
112
This method returns the mapping for representing a Koha::Biblio::Volume object
112
This method returns the mapping for representing a Koha::Biblio::ItemGroup object
113
on the API.
113
on the API.
114
114
115
=cut
115
=cut
116
116
117
sub to_api_mapping {
117
sub to_api_mapping {
118
    return {
118
    return {
119
        id           => 'volume_id',
120
        biblionumber => 'biblio_id',
121
        created_on   => 'creation_date',
119
        created_on   => 'creation_date',
122
        updated_on   => 'modification_date'
120
        updated_on   => 'modification_date'
123
    };
121
    };
Lines 130-136 sub to_api_mapping { Link Here
130
=cut
128
=cut
131
129
132
sub _type {
130
sub _type {
133
    return 'Volume';
131
    return 'ItemGroup';
134
}
132
}
135
133
136
=head3 object_class
134
=head3 object_class
Lines 138-144 sub _type { Link Here
138
=cut
136
=cut
139
137
140
sub object_class {
138
sub object_class {
141
    return 'Koha::Biblio::Volume';
139
    return 'Koha::Biblio::ItemGroup';
142
}
140
}
143
141
144
1;
142
1;
(-)a/Koha/Biblio/Volume/Item.pm (-4 / +4 lines)
Lines 1-4 Link Here
1
package Koha::Biblio::Volume::Item;
1
package Koha::Biblio::ItemGroup::Item;
2
2
3
# This file is part of Koha.
3
# This file is part of Koha.
4
#
4
#
Lines 21-27 use base qw(Koha::Object); Link Here
21
21
22
=head1 NAME
22
=head1 NAME
23
23
24
Koha::Volume::Item - Koha Volume Item Object class
24
Koha::Biblio::ItemGroup::Item - Koha ItemGroup Item Object class
25
25
26
=head1 API
26
=head1 API
27
27
Lines 32-38 Koha::Volume::Item - Koha Volume Item Object class Link Here
32
=cut
32
=cut
33
33
34
sub _type {
34
sub _type {
35
    return 'VolumeItem';
35
    return 'ItemGroupItem';
36
}
36
}
37
37
38
=head3 object_class
38
=head3 object_class
Lines 40-46 sub _type { Link Here
40
=cut
40
=cut
41
41
42
sub object_class {
42
sub object_class {
43
    return 'Koha::Biblio::Volume::Item';
43
    return 'Koha::Biblio::ItemGroup::Item';
44
}
44
}
45
45
46
1;
46
1;
(-)a/Koha/Biblio/Volume/Items.pm (-5 / +5 lines)
Lines 1-4 Link Here
1
package Koha::Biblio::Volume::Items;
1
package Koha::Biblio::ItemGroup::Items;
2
2
3
# This file is part of Koha.
3
# This file is part of Koha.
4
#
4
#
Lines 17-29 package Koha::Biblio::Volume::Items; Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Koha::Biblio::Volume::Item;
20
use Koha::Biblio::ItemGroup::Item;
21
21
22
use base qw(Koha::Objects);
22
use base qw(Koha::Objects);
23
23
24
=head1 NAME
24
=head1 NAME
25
25
26
Koha::Biblio::Volume::Items - Koha Volume Items Object set class
26
Koha::Biblio::ItemGroup::Items - Koha ItemGroup Items Object set class
27
27
28
=head1 API
28
=head1 API
29
29
Lines 34-40 Koha::Biblio::Volume::Items - Koha Volume Items Object set class Link Here
34
=cut
34
=cut
35
35
36
sub _type {
36
sub _type {
37
    return 'VolumeItem';
37
    return 'ItemGroupItem';
38
}
38
}
39
39
40
=head3 object_class
40
=head3 object_class
Lines 42-48 sub _type { Link Here
42
=cut
42
=cut
43
43
44
sub object_class {
44
sub object_class {
45
    return 'Koha::Biblio::Volume::Item';
45
    return 'Koha::Biblio::ItemGroup::Item';
46
}
46
}
47
47
48
1;
48
1;
(-)a/Koha/Biblio/Volumes.pm (-5 / +5 lines)
Lines 1-4 Link Here
1
package Koha::Biblio::Volumes;
1
package Koha::Biblio::ItemGroups;
2
2
3
# This file is part of Koha.
3
# This file is part of Koha.
4
#
4
#
Lines 17-29 package Koha::Biblio::Volumes; Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Koha::Biblio::Volume;
20
use Koha::Biblio::ItemGroup;
21
21
22
use base qw(Koha::Objects);
22
use base qw(Koha::Objects);
23
23
24
=head1 NAME
24
=head1 NAME
25
25
26
Koha::Biblio::Volumes - Koha Volume Object set class
26
Koha::Biblio::ItemGroups - Koha ItemGroup Object set class
27
27
28
=head1 API
28
=head1 API
29
29
Lines 34-40 Koha::Biblio::Volumes - Koha Volume Object set class Link Here
34
=cut
34
=cut
35
35
36
sub _type {
36
sub _type {
37
    return 'Volume';
37
    return 'ItemGroup';
38
}
38
}
39
39
40
=head3 object_class
40
=head3 object_class
Lines 42-48 sub _type { Link Here
42
=cut
42
=cut
43
43
44
sub object_class {
44
sub object_class {
45
    return 'Koha::Biblio::Volume';
45
    return 'Koha::Biblio::ItemGroup';
46
}
46
}
47
47
48
1;
48
1;
(-)a/Koha/Item.pm (-16 / +11 lines)
Lines 32-37 use C4::ClassSource qw( GetClassSort ); Link Here
32
use C4::Log qw( logaction );
32
use C4::Log qw( logaction );
33
use C4::Reserves;
33
use C4::Reserves;
34
34
35
use Koha::Biblio::ItemGroups;
35
use Koha::Checkouts;
36
use Koha::Checkouts;
36
use Koha::CirculationRules;
37
use Koha::CirculationRules;
37
use Koha::CoverImages;
38
use Koha::CoverImages;
Lines 223-236 sub delete { Link Here
223
    # FIXME check the item has no current issues
224
    # FIXME check the item has no current issues
224
    # i.e. raise the appropriate exception
225
    # i.e. raise the appropriate exception
225
226
226
    # Get the volume so we can delete it later if it has no items left
227
    my $volume = C4::Context->preference('EnableVolumes') ? $self->volume : undef;
228
229
    my $result = $self->SUPER::delete;
227
    my $result = $self->SUPER::delete;
230
228
231
    # Delete the volume if it has no items left
232
    $volume->delete if ( $volume && $volume->items->count == 0 );
233
234
    my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
229
    my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
235
    $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
230
    $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
236
        unless $params->{skip_record_index};
231
        unless $params->{skip_record_index};
Lines 400-424 sub checkout { Link Here
400
    return Koha::Checkout->_new_from_dbic( $checkout_rs );
395
    return Koha::Checkout->_new_from_dbic( $checkout_rs );
401
}
396
}
402
397
403
=head3 volume
398
=head3 item_group
404
399
405
my $volume = $item->volume;
400
my $item_group = $item->item_group;
406
401
407
Return the volume for this item
402
Return the item group for this item
408
403
409
=cut
404
=cut
410
405
411
sub volume {
406
sub item_group {
412
    my ( $self ) = @_;
407
    my ( $self ) = @_;
413
408
414
    my $volume_item = $self->_result->volume_items->first;
409
    my $item_group_item = $self->_result->item_group_item;
415
    return unless $volume_item;
410
    return unless $item_group_item;
416
411
417
    my $volume_rs = $volume_item->volume;
412
    my $item_group_rs = $item_group_item->item_group;
418
    return unless $volume_rs;
413
    return unless $item_group_rs;
419
414
420
    my $volume = Koha::Biblio::Volume->_new_from_dbic( $volume_rs );
415
    my $item_group = Koha::Biblio::ItemGroup->_new_from_dbic( $item_group_rs );
421
    return $volume;
416
    return $item_group;
422
}
417
}
423
418
424
=head3 holds
419
=head3 holds
(-)a/Koha/REST/V1/Biblios/Volumes.pm (-41 / +46 lines)
Lines 1-4 Link Here
1
package Koha::REST::V1::Biblios::Volumes;
1
package Koha::REST::V1::Biblios::ItemGroups;
2
2
3
# This file is part of Koha.
3
# This file is part of Koha.
4
#
4
#
Lines 19-32 use Modern::Perl; Link Here
19
19
20
use Mojo::Base 'Mojolicious::Controller';
20
use Mojo::Base 'Mojolicious::Controller';
21
21
22
use Koha::Biblio::Volumes;
22
use Koha::Biblio::ItemGroups;
23
23
24
use Scalar::Util qw(blessed);
24
use Scalar::Util qw(blessed);
25
use Try::Tiny;
25
use Try::Tiny;
26
26
27
=head1 NAME
27
=head1 NAME
28
28
29
Koha::REST::V1::Biblios::Volumes - Koha REST API for handling volumes (V1)
29
Koha::REST::V1::Biblios::ItemGroups - Koha REST API for handling item groups (V1)
30
30
31
=head1 API
31
=head1 API
32
32
Lines 36-42 Koha::REST::V1::Biblios::Volumes - Koha REST API for handling volumes (V1) Link Here
36
36
37
=head3 list
37
=head3 list
38
38
39
Controller function that handles listing Koha::Biblio::Volume objects
39
Controller function that handles listing Koha::Biblio::ItemGroup objects
40
40
41
=cut
41
=cut
42
42
Lines 44-54 sub list { Link Here
44
    my $c = shift->openapi->valid_input or return;
44
    my $c = shift->openapi->valid_input or return;
45
45
46
    return try {
46
    return try {
47
        my $volumes_set = Koha::Biblio::Volumes->new;
47
        my $item_groups_set = Koha::Biblio::ItemGroups->new;
48
        my $volumes     = $c->objects->search( $volumes_set );
48
        my $item_groups     = $c->objects->search( $item_groups_set );
49
        return $c->render(
49
        return $c->render(
50
            status  => 200,
50
            status  => 200,
51
            openapi => $volumes
51
            openapi => $item_groups
52
        );
52
        );
53
    }
53
    }
54
    catch {
54
    catch {
Lines 58-64 sub list { Link Here
58
58
59
=head3 get
59
=head3 get
60
60
61
Controller function that handles retrieving a single Koha::Biblio::Volume
61
Controller function that handles retrieving a single Koha::Biblio::ItemGroup
62
62
63
=cut
63
=cut
64
64
Lines 66-87 sub get { Link Here
66
    my $c = shift->openapi->valid_input or return;
66
    my $c = shift->openapi->valid_input or return;
67
67
68
    try {
68
    try {
69
        my $volume_id = $c->validation->param('volume_id');
69
        my $item_group_id = $c->validation->param('item_group_id');
70
        my $biblio_id = $c->validation->param('biblio_id');
70
        my $biblio_id = $c->validation->param('biblio_id');
71
71
72
        my $volume = $c->objects->find( Koha::Biblio::Volumes->new, $volume_id );
72
        my $item_group = $c->objects->find( Koha::Biblio::ItemGroups->new, $item_group_id );
73
73
74
        if ( $volume && $volume->{biblio_id} eq $biblio_id ) {
74
        if ( $item_group && $item_group->{biblio_id} eq $biblio_id ) {
75
            return $c->render(
75
            return $c->render(
76
                status  => 200,
76
                status  => 200,
77
                openapi => $volume
77
                openapi => $item_group
78
            );
78
            );
79
        }
79
        }
80
        else {
80
        else {
81
            return $c->render(
81
            return $c->render(
82
                status  => 404,
82
                status  => 404,
83
                openapi => {
83
                openapi => {
84
                    error => 'Volume not found'
84
                    error => 'Item group not found'
85
                }
85
                }
86
            );
86
            );
87
        }
87
        }
Lines 93-99 sub get { Link Here
93
93
94
=head3 add
94
=head3 add
95
95
96
Controller function to handle adding a Koha::Biblio::Volume object
96
Controller function to handle adding a Koha::Biblio::ItemGroup object
97
97
98
=cut
98
=cut
99
99
Lines 101-126 sub add { Link Here
101
    my $c = shift->openapi->valid_input or return;
101
    my $c = shift->openapi->valid_input or return;
102
102
103
    return try {
103
    return try {
104
        my $volume_data = $c->validation->param('body');
104
        my $item_group_data = $c->validation->param('body');
105
        # biblio_id comes from the path
105
        # biblio_id comes from the path
106
        $volume_data->{biblio_id} = $c->validation->param('biblio_id');
106
        $item_group_data->{biblio_id} = $c->validation->param('biblio_id');
107
107
108
        my $volume = Koha::Biblio::Volume->new_from_api($volume_data);
108
        my $item_group = Koha::Biblio::ItemGroup->new_from_api($item_group_data);
109
        $volume->store->discard_changes();
109
        $item_group->store->discard_changes();
110
110
111
        $c->res->headers->location( $c->req->url->to_string . '/' . $volume->id );
111
        $c->res->headers->location( $c->req->url->to_string . '/' . $item_group->id );
112
112
113
        return $c->render(
113
        return $c->render(
114
            status  => 201,
114
            status  => 201,
115
            openapi => $volume->to_api
115
            openapi => $item_group->to_api
116
        );
116
        );
117
    }
117
    }
118
    catch {
118
    catch {
119
        if ( blessed($_) ) {
119
        if ( blessed($_) ) {
120
            my $to_api_mapping = Koha::Biblio::Volume->new->to_api_mapping;
120
            my $to_api_mapping = Koha::Biblio::ItemGroup->new->to_api_mapping;
121
121
122
            if ( $_->isa('Koha::Exceptions::Object::FKConstraint') and
122
            if (    $_->isa('Koha::Exceptions::Object::FKConstraint')
123
                 $to_api_mapping->{ $_->broken_fk } eq 'biblio_id') {
123
                and $_->broken_fk eq 'biblio_id' )
124
            {
124
                return $c->render(
125
                return $c->render(
125
                    status  => 404,
126
                    status  => 404,
126
                    openapi => { error => "Biblio not found" }
127
                    openapi => { error => "Biblio not found" }
Lines 134-140 sub add { Link Here
134
135
135
=head3 update
136
=head3 update
136
137
137
Controller function to handle updating a Koha::Biblio::Volume object
138
Controller function to handle updating a Koha::Biblio::ItemGroup object
138
139
139
=cut
140
=cut
140
141
Lines 142-172 sub update { Link Here
142
    my $c = shift->openapi->valid_input or return;
143
    my $c = shift->openapi->valid_input or return;
143
144
144
    return try {
145
    return try {
145
        my $volume_id = $c->validation->param('volume_id');
146
        my $item_group_id = $c->validation->param('item_group_id');
146
        my $biblio_id = $c->validation->param('biblio_id');
147
        my $biblio_id     = $c->validation->param('biblio_id');
147
148
148
        my $volume = Koha::Biblio::Volumes->find( $volume_id );
149
        my $item_group = Koha::Biblio::ItemGroups->find( $item_group_id );
149
150
150
        unless ( $volume && $volume->biblionumber eq $biblio_id ) {
151
        unless ( $item_group && $item_group->biblio_id eq $biblio_id ) {
151
            return $c->render(
152
            return $c->render(
152
                status  => 404,
153
                status  => 404,
153
                openapi => {
154
                openapi => {
154
                    error => 'Volume not found'
155
                    error => 'Item group not found'
155
                }
156
                }
156
            );
157
            );
157
        }
158
        }
158
159
159
        my $volume_data = $c->validation->param('body');
160
        my $item_group_data = $c->validation->param('body');
160
        $volume->set_from_api( $volume_data )->store->discard_changes();
161
        $item_group->set_from_api( $item_group_data )->store->discard_changes();
161
162
162
        return $c->render(
163
        return $c->render(
163
            status  => 200,
164
            status  => 200,
164
            openapi => $volume->to_api
165
            openapi => $item_group->to_api
165
        );
166
        );
166
    }
167
    }
167
    catch {
168
    catch {
168
        if ( blessed($_) ) {
169
        if ( blessed($_) ) {
169
            my $to_api_mapping = Koha::Biblio::Volume->new->to_api_mapping;
170
            my $to_api_mapping = Koha::Biblio::ItemGroup->new->to_api_mapping;
170
171
171
            if ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
172
            if ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
172
                return $c->render(
173
                return $c->render(
Lines 184-190 sub update { Link Here
184
185
185
=head3 delete
186
=head3 delete
186
187
187
Controller function that handles deleting a Koha::Biblio::Volume object
188
Controller function that handles deleting a Koha::Biblio::ItemGroup object
188
189
189
=cut
190
=cut
190
191
Lines 192-209 sub delete { Link Here
192
193
193
    my $c = shift->openapi->valid_input or return;
194
    my $c = shift->openapi->valid_input or return;
194
195
195
    my $volume_id = $c->validation->param( 'volume_id' );
196
    my $item_group_id = $c->validation->param('item_group_id');
196
    my $biblio_id = $c->validation->param( 'biblio_id' );
197
    my $biblio_id     = $c->validation->param('biblio_id');
197
198
198
    my $volume = Koha::Biblio::Volumes->find({ id => $volume_id, biblionumber => $biblio_id });
199
    my $item_group = Koha::Biblio::ItemGroups->find(
200
        { item_group_id => $item_group_id, biblio_id => $biblio_id } );
199
201
200
    if ( not defined $volume ) {
202
    if ( not defined $item_group ) {
201
        return $c->render( status => 404, openapi => { error => "Volume not found" } );
203
        return $c->render(
204
            status  => 404,
205
            openapi => { error => "Item group not found" }
206
        );
202
    }
207
    }
203
208
204
    return try {
209
    return try {
205
        $volume->delete;
210
        $item_group->delete;
206
        return $c->render( status => 204, openapi => '');
211
        return $c->render( status => 204, openapi => '' );
207
    }
212
    }
208
    catch {
213
    catch {
209
        $c->unhandled_exception($_);
214
        $c->unhandled_exception($_);
(-)a/Koha/REST/V1/Biblios/Volumes/Items.pm (-22 / +22 lines)
Lines 1-4 Link Here
1
package Koha::REST::V1::Biblios::Volumes::Items;
1
package Koha::REST::V1::Biblios::ItemGroups::Items;
2
2
3
# This file is part of Koha.
3
# This file is part of Koha.
4
#
4
#
Lines 19-32 use Modern::Perl; Link Here
19
19
20
use Mojo::Base 'Mojolicious::Controller';
20
use Mojo::Base 'Mojolicious::Controller';
21
21
22
use Koha::Biblio::Volume::Items;
22
use Koha::Biblio::ItemGroup::Items;
23
23
24
use Scalar::Util qw(blessed);
24
use Scalar::Util qw(blessed);
25
use Try::Tiny;
25
use Try::Tiny;
26
26
27
=head1 NAME
27
=head1 NAME
28
28
29
Koha::REST::V1::Biblios::Volumes::Items - Koha REST API for handling volume items (V1)
29
Koha::REST::V1::Biblios::ItemGroups::Items - Koha REST API for handling item group items (V1)
30
30
31
=head1 API
31
=head1 API
32
32
Lines 34-40 Koha::REST::V1::Biblios::Volumes::Items - Koha REST API for handling volume item Link Here
34
34
35
=head3 add
35
=head3 add
36
36
37
Controller function to handle adding a Koha::Biblio::Volume object
37
Controller function to handle linking an item to a Koha::Biblio::ItemGroup object
38
38
39
=cut
39
=cut
40
40
Lines 43-66 sub add { Link Here
43
43
44
    return try {
44
    return try {
45
45
46
        my $volume = Koha::Biblio::Volumes->find(
46
        my $item_group = Koha::Biblio::ItemGroups->find(
47
            $c->validation->param('volume_id')
47
            $c->validation->param('item_group_id')
48
        );
48
        );
49
49
50
        unless ( $volume ) {
50
        unless ( $item_group ) {
51
            return $c->render(
51
            return $c->render(
52
                status  => 404,
52
                status  => 404,
53
                openapi => {
53
                openapi => {
54
                    error => 'Volume not found'
54
                    error => 'Item group not found'
55
                }
55
                }
56
            );
56
            );
57
        }
57
        }
58
58
59
        unless ( $volume->biblionumber eq $c->validation->param('biblio_id') ) {
59
        unless ( $item_group->biblio_id eq $c->validation->param('biblio_id') ) {
60
            return $c->render(
60
            return $c->render(
61
                status  => 409,
61
                status  => 409,
62
                openapi => {
62
                openapi => {
63
                    error => 'Volume does not belong to passed biblio_id'
63
                    error => 'Item group does not belong to passed biblio_id'
64
                }
64
                }
65
            );
65
            );
66
        }
66
        }
Lines 69-75 sub add { Link Here
69
        my $body    = $c->validation->param('body');
69
        my $body    = $c->validation->param('body');
70
        my $item_id = $body->{item_id};
70
        my $item_id = $body->{item_id};
71
71
72
        $volume->add_item({ item_id => $item_id });
72
        $item_group->add_item({ item_id => $item_id });
73
73
74
        $c->res->headers->location( $c->req->url->to_string . '/' . $item_id );
74
        $c->res->headers->location( $c->req->url->to_string . '/' . $item_id );
75
75
Lines 77-83 sub add { Link Here
77
77
78
        return $c->render(
78
        return $c->render(
79
            status  => 201,
79
            status  => 201,
80
            openapi => $volume->to_api({ embed => $embed })
80
            openapi => $item_group->to_api({ embed => $embed })
81
        );
81
        );
82
    }
82
    }
83
    catch {
83
    catch {
Lines 92-102 sub add { Link Here
92
                        }
92
                        }
93
                    );
93
                    );
94
                }
94
                }
95
                elsif ( $_->broken_fk eq 'biblionumber' ) {
95
                elsif ( $_->broken_fk eq 'biblio_id' ) {
96
                    return $c->render(
96
                    return $c->render(
97
                        status  => 409,
97
                        status  => 409,
98
                        openapi => {
98
                        openapi => {
99
                            error => "Given item_id does not belong to the volume's biblio"
99
                            error => "Given item_id does not belong to the item group's biblio"
100
                        }
100
                        }
101
                    );
101
                    );
102
                }
102
                }
Lines 106-112 sub add { Link Here
106
                return $c->render(
106
                return $c->render(
107
                    status  => 409,
107
                    status  => 409,
108
                    openapi => {
108
                    openapi => {
109
                        error => "The given item_id is already linked to the volume"
109
                        error => "The given item_id is already linked to the item group"
110
                    }
110
                    }
111
                );
111
                );
112
            }
112
            }
Lines 118-137 sub add { Link Here
118
118
119
=head3 delete
119
=head3 delete
120
120
121
Controller function that handles deleting a Koha::Biblio::Volume object
121
Controller function that handles unlinking an item from a Koha::Biblio::ItemGroup object
122
122
123
=cut
123
=cut
124
124
125
sub delete {
125
sub delete {
126
    my $c = shift->openapi->valid_input or return;
126
    my $c = shift->openapi->valid_input or return;
127
127
128
    my $volume_id = $c->validation->param('volume_id');
128
    my $item_group_id = $c->validation->param('item_group_id');
129
    my $item_id   = $c->validation->param('item_id');
129
    my $item_id       = $c->validation->param('item_id');
130
130
131
    my $item_link = Koha::Biblio::Volume::Items->find(
131
    my $item_link = Koha::Biblio::ItemGroup::Items->find(
132
        {
132
        {
133
            itemnumber => $item_id,
133
            item_id       => $item_id,
134
            volume_id  => $volume_id
134
            item_group_id => $item_group_id
135
        }
135
        }
136
    );
136
    );
137
137
Lines 139-145 sub delete { Link Here
139
        return $c->render(
139
        return $c->render(
140
            status  => 404,
140
            status  => 404,
141
            openapi => {
141
            openapi => {
142
                error => 'No such volume <-> item relationship'
142
                error => 'No such item group <-> item relationship'
143
            }
143
            }
144
        );
144
        );
145
    }
145
    }
(-)a/Koha/Schema/Result/ItemGroup.pm (-1 / +6 lines)
Lines 151-156 __PACKAGE__->has_many( Link Here
151
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-08-18 14:00:35
151
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-08-18 14:00:35
152
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1WBTrtEB25MCOwR4nqPpVA
152
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1WBTrtEB25MCOwR4nqPpVA
153
153
154
sub koha_object_class {
155
    'Koha::Biblio::ItemGroup';
156
}
157
sub koha_objects_class {
158
    'Koha::Biblio::ItemGroups';
159
}
154
160
155
# You can replace this text with custom code or comments, and it will be preserved on regeneration
156
1;
161
1;
(-)a/Koha/Schema/Result/ItemGroupItem.pm (-1 / +6 lines)
Lines 134-139 __PACKAGE__->belongs_to( Link Here
134
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-08-18 14:00:35
134
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-08-18 14:00:35
135
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:UkFl9MLWlH8wy3T/AaVIdQ
135
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:UkFl9MLWlH8wy3T/AaVIdQ
136
136
137
sub koha_object_class {
138
    'Koha::Biblio::ItemGroup::Item';
139
}
140
sub koha_objects_class {
141
    'Koha::Biblio::ItemGroup::Items';
142
}
137
143
138
# You can replace this text with custom code or comments, and it will be preserved on regeneration
139
1;
144
1;
(-)a/api/v1/swagger/definitions.json (-3 / +3 lines)
Lines 26-31 Link Here
26
  "invoice": {
26
  "invoice": {
27
    "$ref": "definitions/invoice.json"
27
    "$ref": "definitions/invoice.json"
28
  },
28
  },
29
  "item_group": {
30
    "$ref": "definitions/item_group.json"
31
  },
29
  "checkouts": {
32
  "checkouts": {
30
    "$ref": "definitions/checkouts.json"
33
    "$ref": "definitions/checkouts.json"
31
  },
34
  },
Lines 83-91 Link Here
83
  "vendor": {
86
  "vendor": {
84
    "$ref": "definitions/vendor.json"
87
    "$ref": "definitions/vendor.json"
85
  },
88
  },
86
  "volume": {
87
    "$ref": "definitions/volume.json"
88
  },
89
  "return_claim": {
89
  "return_claim": {
90
    "$ref": "definitions/return_claim.json"
90
    "$ref": "definitions/return_claim.json"
91
  },
91
  },
(-)a/api/v1/swagger/definitions/volume.json (-8 / +8 lines)
Lines 1-10 Link Here
1
{
1
{
2
    "type": "object",
2
    "type": "object",
3
    "properties": {
3
    "properties": {
4
        "volume_id": {
4
        "item_group_id": {
5
            "type": "integer",
5
            "type": "integer",
6
            "readOnly": true,
6
            "readOnly": true,
7
            "description": "Internal identifier for the volume"
7
            "description": "Internal identifier for the item group"
8
        },
8
        },
9
        "biblio_id": {
9
        "biblio_id": {
10
            "type": "integer",
10
            "type": "integer",
Lines 13-35 Link Here
13
        },
13
        },
14
        "description": {
14
        "description": {
15
            "type": "string",
15
            "type": "string",
16
            "description": "Volume description"
16
            "description": "Item group description"
17
        },
17
        },
18
        "display_order": {
18
        "display_order": {
19
            "type": "integer",
19
            "type": "integer",
20
            "description": "Volume description"
20
            "description": "Display order"
21
        },
21
        },
22
        "creation_date": {
22
        "creation_date": {
23
            "type": "string",
23
            "type": "string",
24
            "format": "date-time",
24
            "format": "date-time",
25
            "readOnly": true,
25
            "readOnly": true,
26
            "description": "Date and time the volume was created"
26
            "description": "Date and time the item group was created"
27
        },
27
        },
28
        "modification_date": {
28
        "modification_date": {
29
            "type": "string",
29
            "type": "string",
30
            "format": "date-time",
30
            "format": "date-time",
31
            "readOnly": true,
31
            "readOnly": true,
32
            "description": "Date and time the volume was last modified"
32
            "description": "Date and time the item group was last modified"
33
        },
33
        },
34
        "items": {
34
        "items": {
35
            "type": [
35
            "type": [
Lines 37-48 Link Here
37
                "null"
37
                "null"
38
            ],
38
            ],
39
            "readOnly": true,
39
            "readOnly": true,
40
            "description": "A list of items that belong to the volume (x-koha-embed)"
40
            "description": "A list of items that belong to the item group (x-koha-embed)"
41
        }
41
        }
42
    },
42
    },
43
    "additionalProperties": false,
43
    "additionalProperties": false,
44
    "required": [
44
    "required": [
45
        "volume_id",
45
        "item_group_id",
46
        "biblio_id"
46
        "biblio_id"
47
    ]
47
    ]
48
}
48
}
(-)a/api/v1/swagger/paths.json (-8 / +8 lines)
Lines 26-42 Link Here
26
  "/biblios/{biblio_id}/pickup_locations": {
26
  "/biblios/{biblio_id}/pickup_locations": {
27
    "$ref": "paths/biblios.json#/~1biblios~1{biblio_id}~1pickup_locations"
27
    "$ref": "paths/biblios.json#/~1biblios~1{biblio_id}~1pickup_locations"
28
  },
28
  },
29
  "/biblios/{biblio_id}/volumes": {
29
  "/biblios/{biblio_id}/item_groups": {
30
    "$ref": "paths/biblios_volumes.json#/~1biblios~1{biblio_id}~1volumes"
30
    "$ref": "paths/biblios_item_groups.json#/~1biblios~1{biblio_id}~1item_groups"
31
  },
31
  },
32
  "/biblios/{biblio_id}/volumes/{volume_id}": {
32
  "/biblios/{biblio_id}/item_groups/{item_group_id}": {
33
    "$ref": "paths/biblios_volumes.json#/~1biblios~1{biblio_id}~1volumes~1{volume_id}"
33
    "$ref": "paths/biblios_item_groups.json#/~1biblios~1{biblio_id}~1item_groups~1{item_group_id}"
34
  },
34
  },
35
  "/biblios/{biblio_id}/volumes/{volume_id}/items": {
35
  "/biblios/{biblio_id}/item_groups/{item_group_id}/items": {
36
    "$ref": "paths/biblios_volumes.json#/~1biblios~1{biblio_id}~1volumes~1{volume_id}~1items"
36
    "$ref": "paths/biblios_item_groups.json#/~1biblios~1{biblio_id}~1item_groups~1{item_group_id}~1items"
37
  },
37
  },
38
  "/biblios/{biblio_id}/volumes/{volume_id}/items/{item_id}": {
38
  "/biblios/{biblio_id}/item_groups/{item_group_id}/items/{item_id}": {
39
    "$ref": "paths/biblios_volumes.json#/~1biblios~1{biblio_id}~1volumes~1{volume_id}~1items~1{item_id}"
39
    "$ref": "paths/biblios_item_groups.json#/~1biblios~1{biblio_id}~1item_groups~1{item_group_id}~1items~1{item_id}"
40
  },
40
  },
41
  "/cash_registers/{cash_register_id}/cashups": {
41
  "/cash_registers/{cash_register_id}/cashups": {
42
    "$ref": "paths/cash_registers.json#/~1cash_registers~1{cash_register_id}~1cashups"
42
    "$ref": "paths/cash_registers.json#/~1cash_registers~1{cash_register_id}~1cashups"
(-)a/api/v1/swagger/paths/biblios_volumes.json (-69 / +69 lines)
Lines 1-10 Link Here
1
{
1
{
2
    "/biblios/{biblio_id}/volumes": {
2
    "/biblios/{biblio_id}/item_groups": {
3
        "get": {
3
        "get": {
4
            "x-mojo-to": "Biblios::Volumes#list",
4
            "x-mojo-to": "Biblios::ItemGroups#list",
5
            "operationId": "listVolumes",
5
            "operationId": "listItemGroups",
6
            "tags": [ "volumes" ],
6
            "tags": [ "item_groups" ],
7
            "summary": "List volumes",
7
            "summary": "List item groups",
8
            "parameters": [
8
            "parameters": [
9
                {
9
                {
10
                    "name": "biblio_id",
10
                    "name": "biblio_id",
Lines 43-53 Link Here
43
            ],
43
            ],
44
            "responses": {
44
            "responses": {
45
                "200": {
45
                "200": {
46
                    "description": "A list of volumes",
46
                    "description": "A list of item groups",
47
                    "schema": {
47
                    "schema": {
48
                        "type": "array",
48
                        "type": "array",
49
                        "items": {
49
                        "items": {
50
                            "$ref": "../definitions.json#/volume"
50
                            "$ref": "../definitions.json#/item_group"
51
                        }
51
                        }
52
                    }
52
                    }
53
                },
53
                },
Lines 78-84 Link Here
78
            },
78
            },
79
            "x-koha-authorization": {
79
            "x-koha-authorization": {
80
                "permissions": {
80
                "permissions": {
81
                    "catalogue": "manage_volumes"
81
                    "catalogue": "manage_item_groups"
82
                }
82
                }
83
            },
83
            },
84
            "x-koha-embed": [
84
            "x-koha-embed": [
Lines 86-95 Link Here
86
            ]
86
            ]
87
        },
87
        },
88
        "post": {
88
        "post": {
89
            "x-mojo-to": "Biblios::Volumes#add",
89
            "x-mojo-to": "Biblios::ItemGroups#add",
90
            "operationId": "addVolume",
90
            "operationId": "addItemGroup",
91
            "tags": [ "volumes" ],
91
            "tags": [ "item_groups" ],
92
            "summary": "Add volume",
92
            "summary": "Add item group",
93
            "parameters": [
93
            "parameters": [
94
                {
94
                {
95
                    "name": "biblio_id",
95
                    "name": "biblio_id",
Lines 101-118 Link Here
101
                {
101
                {
102
                    "name": "body",
102
                    "name": "body",
103
                    "in": "body",
103
                    "in": "body",
104
                    "description": "A JSON object representing a volume",
104
                    "description": "A JSON object representing an item group",
105
                    "required": true,
105
                    "required": true,
106
                    "schema": {
106
                    "schema": {
107
                        "type": "object",
107
                        "type": "object",
108
                        "properties": {
108
                        "properties": {
109
                            "description": {
109
                            "description": {
110
                                "type": "string",
110
                                "type": "string",
111
                                "description": "Volume description"
111
                                "description": "Item group description"
112
                            },
112
                            },
113
                            "display_order": {
113
                            "display_order": {
114
                                "type": "integer",
114
                                "type": "integer",
115
                                "description": "Position in waiting queue"
115
                                "description": "Display order"
116
                            }
116
                            }
117
                        }
117
                        }
118
                    }
118
                    }
Lines 126-134 Link Here
126
            ],
126
            ],
127
            "responses": {
127
            "responses": {
128
                "201": {
128
                "201": {
129
                    "description": "A successfully created volume",
129
                    "description": "A successfully created item group",
130
                    "schema": {
130
                    "schema": {
131
                        "$ref": "../definitions.json#/volume"
131
                        "$ref": "../definitions.json#/item_group"
132
                    }
132
                    }
133
                },
133
                },
134
                "400": {
134
                "400": {
Lines 176-192 Link Here
176
            },
176
            },
177
            "x-koha-authorization": {
177
            "x-koha-authorization": {
178
                "permissions": {
178
                "permissions": {
179
                    "catalogue": "manage_volumes"
179
                    "catalogue": "manage_item_groups"
180
                }
180
                }
181
            }
181
            }
182
        }
182
        }
183
    },
183
    },
184
    "/biblios/{biblio_id}/volumes/{volume_id}": {
184
    "/biblios/{biblio_id}/item_groups/{item_group_id}": {
185
        "get": {
185
        "get": {
186
            "x-mojo-to": "Biblios::Volumes#get",
186
            "x-mojo-to": "Biblios::ItemGroups#get",
187
            "operationId": "getVolume",
187
            "operationId": "getItemGroup",
188
            "tags": [ "volumes" ],
188
            "tags": [ "item_groups" ],
189
            "summary": "Get volume",
189
            "summary": "Get item group",
190
            "parameters": [
190
            "parameters": [
191
                {
191
                {
192
                    "name": "biblio_id",
192
                    "name": "biblio_id",
Lines 196-204 Link Here
196
                    "type": "string"
196
                    "type": "string"
197
                },
197
                },
198
                {
198
                {
199
                    "name": "volume_id",
199
                    "name": "item_group_id",
200
                    "in": "path",
200
                    "in": "path",
201
                    "description": "Internal identifier for the volume",
201
                    "description": "Internal identifier for the item group",
202
                    "required": true,
202
                    "required": true,
203
                    "type": "string"
203
                    "type": "string"
204
                }
204
                }
Lines 211-219 Link Here
211
            ],
211
            ],
212
            "responses": {
212
            "responses": {
213
                "200": {
213
                "200": {
214
                    "description": "A volume",
214
                    "description": "An item group",
215
                    "schema": {
215
                    "schema": {
216
                        "$ref": "../definitions.json#/volume"
216
                        "$ref": "../definitions.json#/item_group"
217
                    }
217
                    }
218
                },
218
                },
219
                "400": {
219
                "400": {
Lines 223-229 Link Here
223
                    }
223
                    }
224
                },
224
                },
225
                "404": {
225
                "404": {
226
                    "description": "Volume not found",
226
                    "description": "Item group not found",
227
                    "schema": {
227
                    "schema": {
228
                        "$ref": "../definitions.json#/error"
228
                        "$ref": "../definitions.json#/error"
229
                    }
229
                    }
Lines 246-260 Link Here
246
            ],
246
            ],
247
            "x-koha-authorization": {
247
            "x-koha-authorization": {
248
                "permissions": {
248
                "permissions": {
249
                    "catalogue": "manage_volumes"
249
                    "catalogue": "manage_item_groups"
250
                }
250
                }
251
            }
251
            }
252
        },
252
        },
253
        "put": {
253
        "put": {
254
            "x-mojo-to": "Biblios::Volumes#update",
254
            "x-mojo-to": "Biblios::ItemGroups#update",
255
            "operationId": "updateVolume",
255
            "operationId": "updateItemGroup",
256
            "tags": [ "volumes" ],
256
            "tags": [ "item_groups" ],
257
            "summary": "Update volume",
257
            "summary": "Update item group",
258
            "parameters": [
258
            "parameters": [
259
                {
259
                {
260
                    "name": "biblio_id",
260
                    "name": "biblio_id",
Lines 264-286 Link Here
264
                    "type": "string"
264
                    "type": "string"
265
                },
265
                },
266
                {
266
                {
267
                    "name": "volume_id",
267
                    "name": "item_group_id",
268
                    "in": "path",
268
                    "in": "path",
269
                    "description": "Internal identifier for the volume",
269
                    "description": "Internal identifier for the item group",
270
                    "required": true,
270
                    "required": true,
271
                    "type": "string"
271
                    "type": "string"
272
                },
272
                },
273
                {
273
                {
274
                    "name": "body",
274
                    "name": "body",
275
                    "in": "body",
275
                    "in": "body",
276
                    "description": "A JSON object with the new values for the volume",
276
                    "description": "A JSON object with the new values for the item group",
277
                    "required": true,
277
                    "required": true,
278
                    "schema": {
278
                    "schema": {
279
                        "type": "object",
279
                        "type": "object",
280
                        "properties": {
280
                        "properties": {
281
                            "description": {
281
                            "description": {
282
                                "type": "string",
282
                                "type": "string",
283
                                "description": "Volume description"
283
                                "description": "Item group description"
284
                            },
284
                            },
285
                            "display_order": {
285
                            "display_order": {
286
                                "type": "integer",
286
                                "type": "integer",
Lines 298-306 Link Here
298
            ],
298
            ],
299
            "responses": {
299
            "responses": {
300
                "200": {
300
                "200": {
301
                    "description": "The updated volume",
301
                    "description": "The updated item group",
302
                    "schema": {
302
                    "schema": {
303
                        "$ref": "../definitions.json#/volume"
303
                        "$ref": "../definitions.json#/item_group"
304
                    }
304
                    }
305
                },
305
                },
306
                "400": {
306
                "400": {
Lines 322-328 Link Here
322
                    }
322
                    }
323
                },
323
                },
324
                "404": {
324
                "404": {
325
                    "description": "Volume not found",
325
                    "description": "Item group not found",
326
                    "schema": {
326
                    "schema": {
327
                        "$ref": "../definitions.json#/error"
327
                        "$ref": "../definitions.json#/error"
328
                    }
328
                    }
Lines 342-348 Link Here
342
            },
342
            },
343
            "x-koha-authorization": {
343
            "x-koha-authorization": {
344
                "permissions": {
344
                "permissions": {
345
                    "catalogue": "manage_volumes"
345
                    "catalogue": "manage_item_groups"
346
                }
346
                }
347
            },
347
            },
348
            "x-koha-embed": [
348
            "x-koha-embed": [
Lines 350-359 Link Here
350
            ]
350
            ]
351
        },
351
        },
352
        "delete": {
352
        "delete": {
353
            "x-mojo-to": "Biblios::Volumes#delete",
353
            "x-mojo-to": "Biblios::ItemGroups#delete",
354
            "operationId": "deleteVolume",
354
            "operationId": "deleteItemGroup",
355
            "tags": ["volumes"],
355
            "tags": ["item_groups"],
356
            "summary": "Delete volume",
356
            "summary": "Delete item group",
357
            "parameters": [
357
            "parameters": [
358
                {
358
                {
359
                    "name": "biblio_id",
359
                    "name": "biblio_id",
Lines 363-371 Link Here
363
                    "type": "string"
363
                    "type": "string"
364
                },
364
                },
365
                {
365
                {
366
                    "name": "volume_id",
366
                    "name": "item_group_id",
367
                    "in": "path",
367
                    "in": "path",
368
                    "description": "Internal identifier for the volume",
368
                    "description": "Internal identifier for the item group",
369
                    "required": true,
369
                    "required": true,
370
                    "type": "string"
370
                    "type": "string"
371
                }
371
                }
Lines 375-381 Link Here
375
            ],
375
            ],
376
            "responses": {
376
            "responses": {
377
                "204": {
377
                "204": {
378
                    "description": "Volume deleted",
378
                    "description": "Item group deleted",
379
                    "schema": {
379
                    "schema": {
380
                        "type": "string"
380
                        "type": "string"
381
                    }
381
                    }
Lines 393-399 Link Here
393
                    }
393
                    }
394
                },
394
                },
395
                "404": {
395
                "404": {
396
                    "description": "Volume not found",
396
                    "description": "Item group not found",
397
                    "schema": {
397
                    "schema": {
398
                        "$ref": "../definitions.json#/error"
398
                        "$ref": "../definitions.json#/error"
399
                    }
399
                    }
Lines 413-429 Link Here
413
            },
413
            },
414
            "x-koha-authorization": {
414
            "x-koha-authorization": {
415
                "permissions": {
415
                "permissions": {
416
                    "catalogue": "manage_volumes"
416
                    "catalogue": "manage_item_groups"
417
                }
417
                }
418
            }
418
            }
419
        }
419
        }
420
    },
420
    },
421
    "/biblios/{biblio_id}/volumes/{volume_id}/items": {
421
    "/biblios/{biblio_id}/item_groups/{item_group_id}/items": {
422
        "post": {
422
        "post": {
423
            "x-mojo-to": "Biblios::Volumes::Items#add",
423
            "x-mojo-to": "Biblios::ItemGroups::Items#add",
424
            "operationId": "addVolumeItem",
424
            "operationId": "addItemGroupItem",
425
            "tags": [ "volumes" ],
425
            "tags": [ "item_groups" ],
426
            "summary": "Add item to volume",
426
            "summary": "Add item to item group",
427
            "parameters": [
427
            "parameters": [
428
                {
428
                {
429
                    "name": "biblio_id",
429
                    "name": "biblio_id",
Lines 433-441 Link Here
433
                    "type": "string"
433
                    "type": "string"
434
                },
434
                },
435
                {
435
                {
436
                    "name": "volume_id",
436
                    "name": "item_group_id",
437
                    "in": "path",
437
                    "in": "path",
438
                    "description": "Internal identifier for the volume",
438
                    "description": "Internal identifier for the item group",
439
                    "required": true,
439
                    "required": true,
440
                    "type": "string"
440
                    "type": "string"
441
                },
441
                },
Lines 463-469 Link Here
463
            ],
463
            ],
464
            "responses": {
464
            "responses": {
465
                "201": {
465
                "201": {
466
                    "description": "Item linked to volume"
466
                    "description": "Item linked to item group"
467
                },
467
                },
468
                "400": {
468
                "400": {
469
                    "description": "Bad request",
469
                    "description": "Bad request",
Lines 504-510 Link Here
504
            },
504
            },
505
            "x-koha-authorization": {
505
            "x-koha-authorization": {
506
                "permissions": {
506
                "permissions": {
507
                    "catalogue": "manage_volumes"
507
                    "catalogue": "manage_item_groups"
508
                }
508
                }
509
            },
509
            },
510
            "x-koha-embed": [
510
            "x-koha-embed": [
Lines 512-523 Link Here
512
            ]
512
            ]
513
        }
513
        }
514
    },
514
    },
515
    "/biblios/{biblio_id}/volumes/{volume_id}/items/{item_id}": {
515
    "/biblios/{biblio_id}/item_groups/{item_group_id}/items/{item_id}": {
516
        "delete": {
516
        "delete": {
517
            "x-mojo-to": "Biblios::Volumes::Items#delete",
517
            "x-mojo-to": "Biblios::ItemGroups::Items#delete",
518
            "operationId": "deleteVolumeItems",
518
            "operationId": "deleteItemGroupItems",
519
            "tags": ["volumes"],
519
            "tags": ["item_groups"],
520
            "summary": "Delete item from volume",
520
            "summary": "Delete item from item group",
521
            "parameters": [
521
            "parameters": [
522
                {
522
                {
523
                    "name": "biblio_id",
523
                    "name": "biblio_id",
Lines 527-535 Link Here
527
                    "type": "string"
527
                    "type": "string"
528
                },
528
                },
529
                {
529
                {
530
                    "name": "volume_id",
530
                    "name": "item_group_id",
531
                    "in": "path",
531
                    "in": "path",
532
                    "description": "Internal identifier for the volume",
532
                    "description": "Internal identifier for the item group",
533
                    "required": true,
533
                    "required": true,
534
                    "type": "string"
534
                    "type": "string"
535
                },
535
                },
Lines 549-555 Link Here
549
            ],
549
            ],
550
            "responses": {
550
            "responses": {
551
                "204": {
551
                "204": {
552
                    "description": "Item unlinked from volume",
552
                    "description": "Item unlinked from item group",
553
                    "schema": {
553
                    "schema": {
554
                        "type": "string"
554
                        "type": "string"
555
                    }
555
                    }
Lines 567-573 Link Here
567
                    }
567
                    }
568
                },
568
                },
569
                "404": {
569
                "404": {
570
                    "description": "Item not linked to volume",
570
                    "description": "Item not linked to item group",
571
                    "schema": {
571
                    "schema": {
572
                        "$ref": "../definitions.json#/error"
572
                        "$ref": "../definitions.json#/error"
573
                    }
573
                    }
Lines 587-593 Link Here
587
            },
587
            },
588
            "x-koha-authorization": {
588
            "x-koha-authorization": {
589
                "permissions": {
589
                "permissions": {
590
                    "catalogue": "manage_volumes"
590
                    "catalogue": "manage_item_groups"
591
                }
591
                }
592
            }
592
            }
593
        }
593
        }
(-)a/catalogue/detail.pl (-19 / +18 lines)
Lines 46-53 use Koha::DateUtils qw( format_sqldatetime ); Link Here
46
use C4::HTML5Media;
46
use C4::HTML5Media;
47
use C4::CourseReserves qw( GetItemCourseReservesInfo );
47
use C4::CourseReserves qw( GetItemCourseReservesInfo );
48
use Koha::AuthorisedValues;
48
use Koha::AuthorisedValues;
49
use Koha::Biblio::Volume::Items;
50
use Koha::Biblios;
49
use Koha::Biblios;
50
use Koha::Biblio::ItemGroup::Items;
51
use Koha::Biblio::ItemGroups;
51
use Koha::CoverImages;
52
use Koha::CoverImages;
52
use Koha::DateUtils;
53
use Koha::DateUtils;
53
use Koha::Illrequests;
54
use Koha::Illrequests;
Lines 100-136 eval { $biblio->metadata->record }; Link Here
100
$template->param( decoding_error => $@ );
101
$template->param( decoding_error => $@ );
101
102
102
my $op = $query->param('op') || q{};
103
my $op = $query->param('op') || q{};
103
if ( $op eq 'set_volume' ) {
104
if ( $op eq 'set_item_group' ) {
104
    my $volume_id   = $query->param('volume_id');
105
    my $item_group_id = $query->param('item_group_id');
105
    my @itemnumbers = $query->multi_param('itemnumber');
106
    my @itemnumbers   = $query->multi_param('itemnumber');
106
107
107
    foreach my $itemnumber (@itemnumbers) {
108
    foreach my $item_id (@itemnumbers) {
108
        my $volume_item =
109
        my $item_group_item = Koha::Biblio::ItemGroup::Items->find( { item_id => $item_id } );
109
          Koha::Biblio::Volume::Items->find( { itemnumber => $itemnumber } );
110
110
111
        if ($volume_item) {
111
        if ($item_group_item) {
112
            $volume_item->volume_id($volume_id);
112
            $item_group_item->item_group_id($item_group_id);
113
        }
113
        }
114
        else {
114
        else {
115
            $volume_item = Koha::Biblio::Volume::Item->new(
115
            $item_group_item = Koha::Biblio::ItemGroup::Item->new(
116
                {
116
                {
117
                    itemnumber => $itemnumber,
117
                    item_id        => $item_id,
118
                    volume_id  => $volume_id,
118
                    item_group_id  => $item_group_id,
119
                }
119
                }
120
            );
120
            );
121
        }
121
        }
122
122
123
        $volume_item->store();
123
        $item_group_item->store();
124
    }
124
    }
125
}
125
}
126
elsif ( $op eq 'unset_volume' ) {
126
elsif ( $op eq 'unset_item_group' ) {
127
    my $volume_id   = $query->param('volume_id');
127
    my $item_group_id   = $query->param('item_group_id');
128
    my @itemnumbers = $query->multi_param('itemnumber');
128
    my @itemnumbers = $query->multi_param('itemnumber');
129
129
130
    foreach my $itemnumber (@itemnumbers) {
130
    foreach my $item_id (@itemnumbers) {
131
        my $volume_item =
131
        my $item_group_item = Koha::Biblio::ItemGroup::Items->find( { item_id => $item_id } );
132
          Koha::Biblio::Volume::Items->find( { itemnumber => $itemnumber } );
132
        $item_group_item->delete() if $item_group_item;
133
        $volume_item->delete() if $volume_item;
134
    }
133
    }
135
}
134
}
136
135
(-)a/cataloguing/additem.pl (-37 / +29 lines)
Lines 40-45 use C4::Context; Link Here
40
use C4::Circulation qw( LostItem );
40
use C4::Circulation qw( LostItem );
41
use C4::Koha qw( GetAuthorisedValues );
41
use C4::Koha qw( GetAuthorisedValues );
42
use C4::ClassSource qw( GetClassSources GetClassSource );
42
use C4::ClassSource qw( GetClassSources GetClassSource );
43
use Koha::Biblio::ItemGroups;
43
use Koha::DateUtils qw( dt_from_string );
44
use Koha::DateUtils qw( dt_from_string );
44
use Koha::Items;
45
use Koha::Items;
45
use Koha::ItemTypes;
46
use Koha::ItemTypes;
Lines 85-116 sub get_item_from_barcode { Link Here
85
    return($result);
86
    return($result);
86
}
87
}
87
88
88
sub add_item_to_volume {
89
sub add_item_to_item_group {
89
    my ( $biblionumber, $itemnumber, $volume, $volume_description ) = @_;
90
    my ( $biblio_id, $item_id, $item_group_param, $description ) = @_;
90
91
91
    return unless $volume;
92
    if ( $item_group_param ) {
92
93
        # item group passed, deal with it
93
    my $volume_id;
94
        my $item_group;
94
    if ( $volume eq 'create' ) {
95
        if ( $item_group_param eq 'create' ) {
95
        my $volume = Koha::Biblio::Volume->new(
96
            $item_group = Koha::Biblio::ItemGroup->new(
96
            {
97
                {
97
                biblionumber => $biblionumber,
98
                    biblio_id   => $biblio_id,
98
                description  => $volume_description,
99
                    description => $description,
99
            }
100
                }
100
        )->store();
101
            );
101
102
        }
102
        $volume_id = $volume->id;
103
        else {
103
    }
104
            $item_group = Koha::Biblio::ItemGroups->find( $item_group_param );
104
    else {
105
        $volume_id = $volume;
106
    }
107
108
    my $volume_item = Koha::Biblio::Volume::Item->new(
109
        {
110
            itemnumber => $itemnumber,
111
            volume_id  => $volume_id,
112
        }
105
        }
113
    )->store();
106
        $item_group->add_item({ item_id => $item_id });
107
    }
114
}
108
}
115
109
116
# NOTE: This code is subject to change in the future with the implemenation of ajax based autobarcode code
110
# NOTE: This code is subject to change in the future with the implemenation of ajax based autobarcode code
Lines 449-461 my $hostitemnumber = $input->param('hostitemnumber'); Link Here
449
my $marcflavour  = C4::Context->preference("marcflavour");
443
my $marcflavour  = C4::Context->preference("marcflavour");
450
my $searchid     = $input->param('searchid');
444
my $searchid     = $input->param('searchid');
451
# fast cataloguing datas
445
# fast cataloguing datas
452
my $fa_circborrowernumber = $input->param('circborrowernumber');
446
my $fa_circborrowernumber  = $input->param('circborrowernumber');
453
my $fa_barcode            = $input->param('barcode');
447
my $fa_barcode             = $input->param('barcode');
454
my $fa_branch             = $input->param('branch');
448
my $fa_branch              = $input->param('branch');
455
my $fa_stickyduedate      = $input->param('stickyduedate');
449
my $fa_stickyduedate       = $input->param('stickyduedate');
456
my $fa_duedatespec        = $input->param('duedatespec');
450
my $fa_duedatespec         = $input->param('duedatespec');
457
my $volume                = $input->param('volume');
451
my $item_group_param       = $input->param('item_group_id');
458
my $volume_description    = $input->param('volume_description');
452
my $item_group_description = $input->param('item_group_description');
459
453
460
my $frameworkcode = &GetFrameworkCode($biblionumber);
454
my $frameworkcode = &GetFrameworkCode($biblionumber);
461
455
Lines 562-569 if ($op eq "additem") { Link Here
562
        # if barcode exists, don't create, but report The problem.
556
        # if barcode exists, don't create, but report The problem.
563
        unless ($exist_itemnumber) {
557
        unless ($exist_itemnumber) {
564
            my ( $oldbiblionumber, $oldbibnum, $oldbibitemnum ) = AddItemFromMarc( $record, $biblionumber );
558
            my ( $oldbiblionumber, $oldbibnum, $oldbibitemnum ) = AddItemFromMarc( $record, $biblionumber );
565
            add_item_to_volume( $oldbiblionumber, $oldbibitemnum, $volume, $volume_description );
559
            add_item_to_item_group( $oldbiblionumber, $oldbibitemnum, $item_group_param, $item_group_description );
566
567
            # Pushing the last created item cookie back
560
            # Pushing the last created item cookie back
568
            if ($prefillitem && defined $record) {
561
            if ($prefillitem && defined $record) {
569
                my $itemcookie = $input->cookie(
562
                my $itemcookie = $input->cookie(
Lines 662-669 if ($op eq "additem") { Link Here
662
        if (!$exist_itemnumber) {
655
        if (!$exist_itemnumber) {
663
            my ( $oldbiblionumber, $oldbibnum, $oldbibitemnum ) =
656
            my ( $oldbiblionumber, $oldbibnum, $oldbibitemnum ) =
664
                AddItemFromMarc( $record, $biblionumber, { skip_record_index => 1 } );
657
                AddItemFromMarc( $record, $biblionumber, { skip_record_index => 1 } );
665
                add_item_to_volume( $oldbiblionumber, $oldbibitemnum, $volume, $volume_description );
658
                add_item_to_item_group( $oldbiblionumber, $oldbibitemnum, $item_group_param, $item_group_description );
666
667
            # We count the item only if it was really added
659
            # We count the item only if it was really added
668
            # That way, all items are added, even if there was some already existing barcodes
660
            # That way, all items are added, even if there was some already existing barcodes
669
            # FIXME : Please note that there is a risk of infinite loop here if we never find a suitable barcode
661
            # FIXME : Please note that there is a risk of infinite loop here if we never find a suitable barcode
Lines 1033-1039 foreach my $tag ( keys %{$tagslib}){ Link Here
1033
1025
1034
# what's the next op ? it's what we are not in : an add if we're editing, otherwise, and edit.
1026
# what's the next op ? it's what we are not in : an add if we're editing, otherwise, and edit.
1035
$template->param(
1027
$template->param(
1036
    volumes      => scalar Koha::Biblio::Volumes->search({ biblionumber => $biblionumber }),
1028
    item_groups  => scalar Koha::Biblio::ItemGroups->search({ biblio_id => $biblionumber }),
1037
    biblionumber => $biblionumber,
1029
    biblionumber => $biblionumber,
1038
    title        => $oldrecord->{title},
1030
    title        => $oldrecord->{title},
1039
    author       => $oldrecord->{author},
1031
    author       => $oldrecord->{author},
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc (-3 / +3 lines)
Lines 330-338 Link Here
330
            Fast cataloging
330
            Fast cataloging
331
        </span>
331
        </span>
332
        <span class="permissioncode">([% name | html %])</span>
332
        <span class="permissioncode">([% name | html %])</span>
333
    [%- CASE 'manage_volumes' -%]
333
    [%- CASE 'manage_item_groups' -%]
334
        <span class="sub_permission manage_volumes_subpermission">
334
        <span class="sub_permission manage_item_groups_subpermission">
335
            Create, update and delete volumes, add or remove items from a volume
335
            Create, update and delete item groups, add or remove items from an item group
336
        </span>
336
        </span>
337
        <span class="permissioncode">([% name | html %])</span>
337
        <span class="permissioncode">([% name | html %])</span>
338
    [%- CASE 'remaining_permissions' -%]
338
    [%- CASE 'remaining_permissions' -%]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (-2 / +2 lines)
Lines 660-670 Circulation: Link Here
660
            - 'the last patron to return an item. This setting is independent of the <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=opacreadinghistory">opacreadinghistory</a> and <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=AnonymousPatron">AnonymousPatron</a> system preferences.'
660
            - 'the last patron to return an item. This setting is independent of the <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=opacreadinghistory">opacreadinghistory</a> and <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=AnonymousPatron">AnonymousPatron</a> system preferences.'
661
    Holds policy:
661
    Holds policy:
662
        -
662
        -
663
            - pref: EnableVolumes
663
            - pref: EnableItemGroups
664
              choices:
664
              choices:
665
                  yes: Enable
665
                  yes: Enable
666
                  no: "Don't enable"
666
                  no: "Don't enable"
667
            - volumes feature to allow collecting groups of items on a record into volumes.
667
            - the item groups feature to allow collecting groups of items on a record together.
668
        -
668
        -
669
            - In the staff client, split the holds queue into separate tables by
669
            - In the staff client, split the holds queue into separate tables by
670
            - pref: HoldsSplitQueue
670
            - pref: HoldsSplitQueue
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt (-128 / +128 lines)
Lines 273-279 Link Here
273
            <a href="#holdings">Holdings ([% itemloop.size() || 0 | html %])</a>
273
            <a href="#holdings">Holdings ([% itemloop.size() || 0 | html %])</a>
274
        </li>
274
        </li>
275
    [% END %]
275
    [% END %]
276
[% IF Koha.Preference('EnableVolumes') %]<li><a href="#volumes">Volumes</a></li>[% END %]
276
[% IF Koha.Preference('EnableItemGroups') %]<li><a href="#item_groups">Item groups</a></li>[% END %]
277
[% IF ( MARCNOTES || notes ) %]<li><a href="#description">Descriptions ([% ( MARCNOTES.size || 1 ) | html %])</a></li>[% END %]
277
[% IF ( MARCNOTES || notes ) %]<li><a href="#description">Descriptions ([% ( MARCNOTES.size || 1 ) | html %])</a></li>[% END %]
278
[% IF ( subscriptionsnumber ) %]<li><a href="#subscriptions">Subscriptions</a></li>[% END %]
278
[% IF ( subscriptionsnumber ) %]<li><a href="#subscriptions">Subscriptions</a></li>[% END %]
279
[% IF Koha.Preference('AcquisitionDetails') %]<li><a href="#acq_details">Acquisition details</a></li>[% END %]
279
[% IF Koha.Preference('AcquisitionDetails') %]<li><a href="#acq_details">Acquisition details</a></li>[% END %]
Lines 310-318 Link Here
310
              [% IF CAN_user_tools_items_batchmod %]
310
              [% IF CAN_user_tools_items_batchmod %]
311
                <a class="itemselection_action_modify"><i class="fa fa-pencil"></i> Modify selected items</a>
311
                <a class="itemselection_action_modify"><i class="fa fa-pencil"></i> Modify selected items</a>
312
              [% END %]
312
              [% END %]
313
              [% IF CAN_user_editcatalogue_manage_volumes && biblio.volumes.count %]
313
              [% IF CAN_user_editcatalogue_manage_item_groups && biblio.item_groups.count %]
314
                <a class="itemselection_action_volume_set" href="#"><i class="fa fa-book"></i> Add/move to volume</a>
314
                <a class="itemselection_action_item_group_set" href="#"><i class="fa fa-book"></i> Add/move to item group</a>
315
                <a class="itemselection_action_volume_unset" href="#"><i class="fa fa-unlink"></i> Remove from volume</a>
315
                <a class="itemselection_action_item_group_unset" href="#"><i class="fa fa-unlink"></i> Remove from item group</a>
316
              [% END %]
316
              [% END %]
317
            </span>
317
            </span>
318
        [% END %]
318
        [% END %]
Lines 328-335 Link Here
328
                <th id="[% tab | html %]_holdingbranch" data-colname="[% tab | html %]_holdingbranch">Current library</th>
328
                <th id="[% tab | html %]_holdingbranch" data-colname="[% tab | html %]_holdingbranch">Current library</th>
329
                <th id="[% tab | html %]_homebranch" data-colname="[% tab | html %]_homebranch">Home library</th>
329
                <th id="[% tab | html %]_homebranch" data-colname="[% tab | html %]_homebranch">Home library</th>
330
                [% IF ( itemdata_ccode ) %]<th id="[% tab | html %]_ccode" data-colname="[% tab | html %]_ccode">Collection</th>[% END %]
330
                [% IF ( itemdata_ccode ) %]<th id="[% tab | html %]_ccode" data-colname="[% tab | html %]_ccode">Collection</th>[% END %]
331
                [% IF Koha.Preference('EnableVolumes') %]
331
                [% IF Koha.Preference('EnableItemGroups') %]
332
                    <th id="[% tab | html %]_volume" data-colname="[% tab | html %]_volume">Volume</th>
332
                    <th id="[% tab | html %]_item_group" data-colname="[% tab | html %]_item_group">Item group</th>
333
                [% END %]
333
                [% END %]
334
                <th id="[% tab | html %]_itemcallnumber" data-colname="[% tab | html %]_itemcallnumber">Call number</th>
334
                <th id="[% tab | html %]_itemcallnumber" data-colname="[% tab | html %]_itemcallnumber">Call number</th>
335
                [% IF volinfo %]
335
                [% IF volinfo %]
Lines 405-411 Note that permanent location is a code, and location may be an authval. Link Here
405
                        </span>
405
                        </span>
406
                    </td>
406
                    </td>
407
                    [% IF ( itemdata_ccode ) %]<td>[% item.ccode | html %]</td>[% END %]
407
                    [% IF ( itemdata_ccode ) %]<td>[% item.ccode | html %]</td>[% END %]
408
                    [% IF Koha.Preference('EnableVolumes') %]<td class="volume">[% item.object.volume.description | html %]</td>[% END %]
408
                    [% IF Koha.Preference('EnableItemGroups') %]<td class="item_group">[% item.object.item_group.description | html %]</td>[% END %]
409
                    <td class="itemcallnumber">[% IF ( item.itemcallnumber ) %] [% item.itemcallnumber | html %][% END %]</td>
409
                    <td class="itemcallnumber">[% IF ( item.itemcallnumber ) %] [% item.itemcallnumber | html %][% END %]</td>
410
                    [% IF ( volinfo ) %]
410
                    [% IF ( volinfo ) %]
411
                        [% IF itemdata_publisheddate #If there is at least one published date, use it for sorting %]
411
                        [% IF itemdata_publisheddate #If there is at least one published date, use it for sorting %]
Lines 611-624 Note that permanent location is a code, and location may be an authval. Link Here
611
611
612
[% END %][%# end of block items_table %]
612
[% END %][%# end of block items_table %]
613
613
614
[% IF Koha.Preference('EnableVolumes') %]
614
[% IF Koha.Preference('EnableItemGroups') %]
615
    <div id="volumes">
615
    <div id="item_groups">
616
        [% IF CAN_user_editcatalogue_manage_volumes %]
616
        [% IF CAN_user_editcatalogue_manage_item_groups %]
617
            <div class="volumes_table_table_controls">
617
            <div class="item_groups_table_table_controls">
618
                <a href="#" class="volume-create btn btn-default btn-xs"><i class="fa fa-plus"></i> New volume</a>
618
                <a href="#" class="item-group-create btn btn-default btn-xs"><i class="fa fa-plus"></i> New item group</a>
619
            </div>
619
            </div>
620
        [% END %]
620
        [% END %]
621
        <table class="volumes-table" id="volumes-table">
621
        <table class="items-group-table" id="items-group-table">
622
            <thead>
622
            <thead>
623
                <tr>
623
                <tr>
624
                    <td>Display Order</td>
624
                    <td>Display Order</td>
Lines 1048-1079 Note that permanent location is a code, and location may be an authval. Link Here
1048
1048
1049
[% END %]
1049
[% END %]
1050
1050
1051
<div class="modal fade" id="modal-volume-create" tabindex="-1" role="dialog" aria-labelledby="modal-volume-create-label">
1051
<div class="modal fade" id="modal-item-group-create" tabindex="-1" role="dialog" aria-labelledby="modal-item-group-create-label">
1052
    <div class="modal-dialog">
1052
    <div class="modal-dialog">
1053
        <div class="modal-content">
1053
        <div class="modal-content">
1054
            <div class="modal-header">
1054
            <div class="modal-header">
1055
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1055
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1056
                <h3 id="modal-volume-create-label"><i class="fa fa-plus"></i> Create a new volume</h3>
1056
                <h3 id="modal-item-group-create-label"><i class="fa fa-plus"></i> Create a new item group</h3>
1057
            </div>
1057
            </div>
1058
            <form id="modal-volume-create-form" class="validated">
1058
            <form id="modal-item-group-create-form" class="validated">
1059
                <div class="modal-body">
1059
                <div class="modal-body">
1060
                    <fieldset>
1060
                    <fieldset>
1061
                        <p>
1061
                        <p>
1062
                            <label for="volume_description" class="required">Name: </label>
1062
                            <label for="item_group_description" class="required">Name: </label>
1063
                            <input name="description" id="modal-volume-create-form-description" type="text" size="30" required="required" class="required" />
1063
                            <input name="description" id="modal-item-group-create-form-description" type="text" size="30" required="required" class="required" />
1064
                            <span class="required">Required</span>
1064
                            <span class="required">Required</span>
1065
                        </p>
1065
                        </p>
1066
                        <p>
1066
                        <p>
1067
                            <label for="volume_display_order" class="required">Display order: </label>
1067
                            <label for="item_group_display_order" class="required">Display order: </label>
1068
                            <input name="display_order" id="modal-volume-create-form-display_order" value="0" size="5" required="required" class="required" />
1068
                            <input name="display_order" id="modal-item-group-create-form-display_order" value="0" size="5" required="required" class="required" />
1069
                            <span class="required">Required</span>
1069
                            <span class="required">Required</span>
1070
                            <br/>
1070
                            <br/>
1071
                            <span class="hint">Numbers only, volumes will be displayed in counting order</span>
1071
                            <span class="hint">Numbers only, item groups will be displayed in counting order</span>
1072
                        </p>
1072
                        </p>
1073
                    </fieldset>
1073
                    </fieldset>
1074
                </div>
1074
                </div>
1075
                <div class="modal-footer">
1075
                <div class="modal-footer">
1076
                    <button id="modal-volume-create-submit" class="btn btn-default"><i class="fa fa-plus"></i> Submit</button>
1076
                    <button id="modal-item-group-create-submit" class="btn btn-default"><i class="fa fa-plus"></i> Submit</button>
1077
                    <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1077
                    <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1078
                </div>
1078
                </div>
1079
            </form>
1079
            </form>
Lines 1081-1110 Note that permanent location is a code, and location may be an authval. Link Here
1081
    </div>
1081
    </div>
1082
</div>
1082
</div>
1083
1083
1084
<div class="modal fade" id="modal-volume-edit" tabindex="-1" role="dialog" aria-labelledby="modal-volume-edit-label">
1084
<div class="modal fade" id="modal-item-group-edit" tabindex="-1" role="dialog" aria-labelledby="modal-item-group-edit-label">
1085
    <div class="modal-dialog">
1085
    <div class="modal-dialog">
1086
        <div class="modal-content">
1086
        <div class="modal-content">
1087
            <div class="modal-header">
1087
            <div class="modal-header">
1088
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1088
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1089
                <h3 id="modal-volume-edit-label"><i class='fa fa-edit'></i> Edit volume</h3>
1089
                <h3 id="modal-item-group-edit-label"><i class='fa fa-edit'></i> Edit item group</h3>
1090
            </div>
1090
            </div>
1091
            <form id="modal-volume-edit-form" class="validated">
1091
            <form id="modal-item-group-edit-form" class="validated">
1092
                <div class="modal-body">
1092
                <div class="modal-body">
1093
                    <fieldset>
1093
                    <fieldset>
1094
                        <p>
1094
                        <p>
1095
                            <label for="volume_description" class="required">Name: </label>
1095
                            <label for="item_group_description" class="required">Name: </label>
1096
                            <input name="description" id="modal-volume-edit-form-description" type="text" size="30" required="required" class="required" />
1096
                            <input name="description" id="modal-item-group-edit-form-description" type="text" size="30" required="required" class="required" />
1097
                            <span class="required">Required</span>
1097
                            <span class="required">Required</span>
1098
                        </p>
1098
                        </p>
1099
                        <p>
1099
                        <p>
1100
                            <label for="volume_display_order" class="required">Sort order: </label>
1100
                            <label for="item_group_display_order" class="required">Sort order: </label>
1101
                            <input name="display_order" id="modal-volume-edit-form-display_order" size="5" />
1101
                            <input name="display_order" id="modal-item-group-edit-form-display_order" size="5" />
1102
                            <span class="hint">Numbers only, volumes will be displayed in counting order</span>
1102
                            <span class="hint">Numbers only, item groups will be displayed in counting order</span>
1103
                        </p>
1103
                        </p>
1104
                    </fieldset>
1104
                    </fieldset>
1105
                </div>
1105
                </div>
1106
                <div class="modal-footer">
1106
                <div class="modal-footer">
1107
                    <button id="modal-volume-edit-submit" class="btn btn-default"><i class='fa fa-edit'></i> Submit</button>
1107
                    <button id="modal-item-group-edit-submit" class="btn btn-default"><i class='fa fa-edit'></i> Submit</button>
1108
                    <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1108
                    <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1109
                </div>
1109
                </div>
1110
            </form>
1110
            </form>
Lines 1112-1150 Note that permanent location is a code, and location may be an authval. Link Here
1112
    </div>
1112
    </div>
1113
</div>
1113
</div>
1114
1114
1115
<div class="modal fade" id="modal-volume-delete" tabindex="-1" role="dialog" aria-labelledby="modal-volume-delete-label">
1115
<div class="modal fade" id="modal-item-group-delete" tabindex="-1" role="dialog" aria-labelledby="modal-item-group-delete-label">
1116
    <div class="modal-dialog">
1116
    <div class="modal-dialog">
1117
        <div class="modal-content">
1117
        <div class="modal-content">
1118
            <div class="modal-header">
1118
            <div class="modal-header">
1119
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1119
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1120
                <h3 id="modal-volume-delete-label"><i class='fa fa-trash'></i> Delete volume</h3>
1120
                <h3 id="modal-item-group-delete-label"><i class='fa fa-trash'></i> Delete item group</h3>
1121
            </div>
1121
            </div>
1122
            <div class="modal-body">
1122
            <div class="modal-body">
1123
                Are you sure you want to delete this volume?
1123
                Are you sure you want to delete this item group?
1124
            </div>
1124
            </div>
1125
            <div class="modal-footer">
1125
            <div class="modal-footer">
1126
                <button id="modal-volume-delete-submit" class="btn btn-danger"><i class='fa fa-trash'></i> Delete</button>
1126
                <button id="modal-item-group-delete-submit" class="btn btn-danger"><i class='fa fa-trash'></i> Delete</button>
1127
                <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1127
                <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1128
            </div>
1128
            </div>
1129
        </div>
1129
        </div>
1130
    </div>
1130
    </div>
1131
</div>
1131
</div>
1132
1132
1133
<div class="modal fade" id="modal-volume-set" tabindex="-1" role="dialog" aria-labelledby="modal-volume-set-label">
1133
<div class="modal fade" id="modal-item-group-set" tabindex="-1" role="dialog" aria-labelledby="modal-item-group-set-label">
1134
    <div class="modal-dialog">
1134
    <div class="modal-dialog">
1135
        <div class="modal-content">
1135
        <div class="modal-content">
1136
            <div class="modal-header">
1136
            <div class="modal-header">
1137
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1137
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1138
                <h3 id="modal-volume-set-label"><i class='fa fa-book'></i> Set volume for items</h3>
1138
                <h3 id="modal-item-group-set-label"><i class='fa fa-book'></i> Set item group for items</h3>
1139
            </div>
1139
            </div>
1140
            <form id="modal-volume-set-form" class="validated">
1140
            <form id="modal-item-group-set-form" class="validated">
1141
                <div class="modal-body">
1141
                <div class="modal-body">
1142
                    <fieldset>
1142
                    <fieldset>
1143
                        <p>
1143
                        <p>
1144
                            <label for="volume" class="required">Volume: </label>
1144
                            <label for="item_group" class="required">Item group: </label>
1145
                            <select name="volume" id="volume-add-form-select">
1145
                            <select name="item_group" id="item-group-add-form-select">
1146
                                [% FOREACH v IN biblio.volumes %]
1146
                                [% FOREACH ig IN biblio.item_groups %]
1147
                                    <option value="[% v.id | html %]">[% v.description | html %]</option>
1147
                                    <option value="[% ig.id | html %]">[% ig.description | html %]</option>
1148
                                [% END %]
1148
                                [% END %]
1149
                            </select>
1149
                            </select>
1150
                            <span class="required">Required</span>
1150
                            <span class="required">Required</span>
Lines 1152-1158 Note that permanent location is a code, and location may be an authval. Link Here
1152
                    </fieldset>
1152
                    </fieldset>
1153
                </div>
1153
                </div>
1154
                <div class="modal-footer">
1154
                <div class="modal-footer">
1155
                    <button id="modal-volume-set-submit" class="btn btn-default"><i class='fa fa-book'></i> Set volume</button>
1155
                    <button id="modal-item-group-set-submit" class="btn btn-default"><i class='fa fa-book'></i> Set item group</button>
1156
                    <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1156
                    <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1157
                </div>
1157
                </div>
1158
            </form>
1158
            </form>
Lines 1160-1177 Note that permanent location is a code, and location may be an authval. Link Here
1160
    </div>
1160
    </div>
1161
</div>
1161
</div>
1162
1162
1163
<div class="modal fade" id="modal-volume-unset" tabindex="-1" role="dialog" aria-labelledby="modal-volume-unset-label">
1163
<div class="modal fade" id="modal-item-group-unset" tabindex="-1" role="dialog" aria-labelledby="modal-item-group-unset-label">
1164
    <div class="modal-dialog">
1164
    <div class="modal-dialog">
1165
        <div class="modal-content">
1165
        <div class="modal-content">
1166
            <div class="modal-header">
1166
            <div class="modal-header">
1167
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1167
                <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1168
                <h3 id="modal-volume-unset-label"><i class='fa fa-unlink'></i> Remove item from volume</h3>
1168
                <h3 id="modal-item-group-unset-label"><i class='fa fa-unlink'></i> Remove item from item group</h3>
1169
            </div>
1169
            </div>
1170
            <div class="modal-body">
1170
            <div class="modal-body">
1171
                Are you sure you want to remove these item(s) from their volume(s)?
1171
                Are you sure you want to remove these item(s) from their item group(s)?
1172
            </div>
1172
            </div>
1173
            <div class="modal-footer">
1173
            <div class="modal-footer">
1174
                <button id="modal-volume-unset-submit" class="btn btn-danger"><i class='fa fa-unlink'></i> Remove</button>
1174
                <button id="modal-item-group-unset-submit" class="btn btn-danger"><i class='fa fa-unlink'></i> Remove</button>
1175
                <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1175
                <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button>
1176
            </div>
1176
            </div>
1177
        </div>
1177
        </div>
Lines 1527-1535 Note that permanent location is a code, and location may be an authval. Link Here
1527
            if ( search_value ){ $('#cat-search-block #search-form').val(search_value)};
1527
            if ( search_value ){ $('#cat-search-block #search-form').val(search_value)};
1528
        });
1528
        });
1529
1529
1530
        [% IF Koha.Preference('EnableVolumes') %]
1530
        [% IF Koha.Preference('EnableItemGroups') %]
1531
            // Load volumes table
1531
            // Load item groups table
1532
            var volumesTable = KohaTable("volumes-table", {
1532
            var itemGroupsTable = KohaTable("items-group-table", {
1533
                "bAutoWidth": false,
1533
                "bAutoWidth": false,
1534
                'sDom': '<"top pager"ilp>t<"bottom pager"ip>r',
1534
                'sDom': '<"top pager"ilp>t<"bottom pager"ip>r',
1535
                "aoColumns": [
1535
                "aoColumns": [
Lines 1545-1556 Note that permanent location is a code, and location may be an authval. Link Here
1545
                    },
1545
                    },
1546
                    {
1546
                    {
1547
                        "mDataProp": function( oObj ) {
1547
                        "mDataProp": function( oObj ) {
1548
                            [% IF CAN_user_editcatalogue_manage_volumes %]
1548
                            [% IF CAN_user_editcatalogue_manage_item_groups %]
1549
                                return `<button class='volume-edit btn btn-default btn-xs' data-volume-id='${oObj.volume_id}'>
1549
                                return `<button class='item-group-edit btn btn-default btn-xs' data-item-group-id='${oObj.item_group_id}'>
1550
                                    <i class='fa fa-edit'></i> ${_("Edit")}
1550
                                    <i class='fa fa-edit'></i> ${_("Edit")}
1551
                                </button>`
1551
                                </button>`
1552
                                + '&nbsp'
1552
                                + '&nbsp'
1553
                                + `<button class='volume-delete btn btn-default btn-xs' data-volume-id='${oObj.volume_id}'>
1553
                                + `<button class='item-group-delete btn btn-default btn-xs' data-item-group-id='${oObj.item_group_id}'>
1554
                                    <i class='fa fa-trash'></i> ${('Delete')}
1554
                                    <i class='fa fa-trash'></i> ${('Delete')}
1555
                                </button>`;
1555
                                </button>`;
1556
                            [% ELSE %]
1556
                            [% ELSE %]
Lines 1562-1568 Note that permanent location is a code, and location may be an authval. Link Here
1562
                "bPaginate": false,
1562
                "bPaginate": false,
1563
                "bProcessing": true,
1563
                "bProcessing": true,
1564
                "bServerSide": false,
1564
                "bServerSide": false,
1565
                "sAjaxSource": `/api/v1/biblios/${biblionumber}/volumes?_per_page=-1`,
1565
                "sAjaxSource": `/api/v1/biblios/${biblionumber}/item_groups?_per_page=-1`,
1566
                "sAjaxDataProp": "",
1566
                "sAjaxDataProp": "",
1567
                "fnServerData": function ( sSource, aoData, fnCallback ) {
1567
                "fnServerData": function ( sSource, aoData, fnCallback ) {
1568
                    $.getJSON( sSource, aoData, function (json) {
1568
                    $.getJSON( sSource, aoData, function (json) {
Lines 1571-1619 Note that permanent location is a code, and location may be an authval. Link Here
1571
                },
1571
                },
1572
            });
1572
            });
1573
1573
1574
            // Create new volumes
1574
            // Create new item groups
1575
            $('.volume-create').on('click', function(){
1575
            $('.item-group-create').on('click', function(){
1576
                $('#modal-volume-create-form-description').val("");
1576
                $('#modal-item-group-create-form-description').val("");
1577
                $('#modal-volume-create-submit').removeAttr('disabled');
1577
                $('#modal-item-group-create-submit').removeAttr('disabled');
1578
                $('#modal-volume-create').modal('show');
1578
                $('#modal-item-group-create').modal('show');
1579
            });
1579
            });
1580
1580
1581
            $("#modal-volume-create-form").validate({
1581
            $("#modal-item-group-create-form").validate({
1582
                submitHandler: function(form) {
1582
                submitHandler: function(form) {
1583
                    $.ajax({
1583
                    $.ajax({
1584
                        url: `/api/v1/biblios/${biblionumber}/volumes`,
1584
                        url: `/api/v1/biblios/${biblionumber}/item_groups`,
1585
                        headers: { "x-koha-embed": "items" },
1585
                        headers: { "x-koha-embed": "items" },
1586
                        success: function(volumes){
1586
                        success: function(item_groups){
1587
                            $('#modal-volume-create-submit').attr('disabled', 'disabled');
1587
                            $('#modal-item-group-create-submit').attr('disabled', 'disabled');
1588
1588
1589
                            var settings = {
1589
                            var settings = {
1590
                              "url": `/api/v1/biblios/${biblionumber}/volumes`,
1590
                              "url": `/api/v1/biblios/${biblionumber}/item_groups`,
1591
                              "method": "POST",
1591
                              "method": "POST",
1592
                              "headers": {
1592
                              "headers": {
1593
                                "Content-Type": "application/json"
1593
                                "Content-Type": "application/json"
1594
                              },
1594
                              },
1595
                              "data": JSON.stringify(
1595
                              "data": JSON.stringify(
1596
                                  {
1596
                                  {
1597
                                      "description": $("#modal-volume-create-form-description").val(),
1597
                                      "description": $("#modal-item-group-create-form-description").val(),
1598
                                      "display_order": $("#modal-volume-create-form-display_order").val(),
1598
                                      "display_order": $("#modal-item-group-create-form-display_order").val(),
1599
                                  }
1599
                                  }
1600
                              ),
1600
                              ),
1601
                            };
1601
                            };
1602
1602
1603
                            $.ajax(settings)
1603
                            $.ajax(settings)
1604
                            .done(function (response) {
1604
                            .done(function (response) {
1605
                                $('#volume-add-form-select').append($('<option>', {
1605
                                $('#item-group-add-form-select').append($('<option>', {
1606
                                    value: response.volume_id,
1606
                                    value: response.item_group_id,
1607
                                    text: response.description
1607
                                    text: response.description
1608
                                }));
1608
                                }));
1609
1609
1610
                                $('#modal-volume-create').modal('hide');
1610
                                $('#modal-item-group-create').modal('hide');
1611
                                if ( volumes.length == 0 ) {
1611
                                if ( item_groups.length == 0 ) {
1612
                                    // This bib has no previous volumes, reload the page
1612
                                    // This bib has no previous item groups, reload the page
1613
                                    window.location.replace(`/cgi-bin/koha/catalogue/detail.pl?biblionumber=${biblionumber}`);
1613
                                    window.location.replace(`/cgi-bin/koha/catalogue/detail.pl?biblionumber=${biblionumber}`);
1614
                                } else {
1614
                                } else {
1615
                                    // Has other volumes, just reload the table
1615
                                    // Has other item groups, just reload the table
1616
                                    volumesTable.api().ajax.reload();
1616
                                    itemGroupsTable.api().ajax.reload();
1617
                                }
1617
                                }
1618
                            })
1618
                            })
1619
                            .fail(function(err) {
1619
                            .fail(function(err) {
Lines 1625-1653 Note that permanent location is a code, and location may be an authval. Link Here
1625
                }
1625
                }
1626
            });
1626
            });
1627
1627
1628
            $('#modal-volume-create').on('shown.bs.modal', function () {
1628
            $('#modal-item-group-create').on('shown.bs.modal', function () {
1629
                $('#modal-volume-create-form-description').focus();
1629
                $('#modal-item-group-create-form-description').focus();
1630
            })
1630
            })
1631
1631
1632
            // Edit existing volumes
1632
            // Edit existing item groups
1633
            $('body').on( 'click', '.volume-edit', function(){
1633
            $('body').on( 'click', '.item-group-edit', function(){
1634
                const volume_id = $(this).data('volume-id');
1634
                const item_group_id = $(this).data('item-group-id');
1635
                const url = `/api/v1/biblios/${biblionumber}/volumes/${volume_id}`;
1635
                const url = `/api/v1/biblios/${biblionumber}/item_groups/${item_group_id}`;
1636
                $.get( url, function( data ) {
1636
                $.get( url, function( data ) {
1637
                    $('#modal-volume-edit-form-description').val( data.description );
1637
                    $('#modal-item-group-edit-form-description').val( data.description );
1638
                    $('#modal-volume-edit-form-display_order').val( data.display_order );
1638
                    $('#modal-item-group-edit-form-display_order').val( data.display_order );
1639
                    $('#modal-volume-edit-submit').data('volume-id', volume_id );
1639
                    $('#modal-item-group-edit-submit').data('item-group-id', item_group_id );
1640
                    $('#modal-volume-edit-submit').removeAttr('disabled');
1640
                    $('#modal-item-group-edit-submit').removeAttr('disabled');
1641
                    $('#modal-volume-edit').modal('show');
1641
                    $('#modal-item-group-edit').modal('show');
1642
                });
1642
                });
1643
            });
1643
            });
1644
1644
1645
            $("#modal-volume-edit-form").validate({
1645
            $("#modal-item-group-edit-form").validate({
1646
                submitHandler: function(form) {
1646
                submitHandler: function(form) {
1647
                    $('#modal-volume-edit-submit').attr('disabled', 'disabled');
1647
                    $('#modal-item-group-edit-submit').attr('disabled', 'disabled');
1648
1648
1649
                    const volume_id = $('#modal-volume-edit-submit').data('volume-id');
1649
                    const item_group_id = $('#modal-item-group-edit-submit').data('item-group-id');
1650
                    const url = `/api/v1/biblios/${biblionumber}/volumes/${volume_id}`;
1650
                    const url = `/api/v1/biblios/${biblionumber}/item_groups/${item_group_id}`;
1651
1651
1652
                    var settings = {
1652
                    var settings = {
1653
                      "url": url,
1653
                      "url": url,
Lines 1657-1672 Note that permanent location is a code, and location may be an authval. Link Here
1657
                      },
1657
                      },
1658
                      "data": JSON.stringify(
1658
                      "data": JSON.stringify(
1659
                          {
1659
                          {
1660
                              "description": $("#modal-volume-edit-form-description").val(),
1660
                              "description": $("#modal-item-group-edit-form-description").val(),
1661
                              "display_order": $("#modal-volume-edit-form-display_order").val(),
1661
                              "display_order": $("#modal-item-group-edit-form-display_order").val(),
1662
                          }
1662
                          }
1663
                      ),
1663
                      ),
1664
                    };
1664
                    };
1665
1665
1666
                    $.ajax(settings)
1666
                    $.ajax(settings)
1667
                    .done(function (response) {
1667
                    .done(function (response) {
1668
                        $('#modal-volume-edit').modal('hide');
1668
                        $('#modal-item-group-edit').modal('hide');
1669
                        volumesTable.api().ajax.reload();
1669
                        itemGroupsTable.api().ajax.reload();
1670
                    })
1670
                    })
1671
                    .fail(function(err) {
1671
                    .fail(function(err) {
1672
                        var message = err.responseJSON.error;
1672
                        var message = err.responseJSON.error;
Lines 1675-1711 Note that permanent location is a code, and location may be an authval. Link Here
1675
                }
1675
                }
1676
            });
1676
            });
1677
1677
1678
            $('#modal-volume-edit').on('shown.bs.modal', function () {
1678
            $('#modal-item-group-edit').on('shown.bs.modal', function () {
1679
                $('#modal-volume-edit-form-description').focus();
1679
                $('#modal-item-group-edit-form-description').focus();
1680
            })
1680
            })
1681
1681
1682
            // Delete existing volumes
1682
            // Delete existing item groups
1683
            $('body').on( 'click', '.volume-delete', function(){
1683
            $('body').on( 'click', '.item-group-delete', function(){
1684
                const volume_id = $(this).data('volume-id');
1684
                const item_group_id = $(this).data('item-group-id');
1685
                $('#modal-volume-delete-submit').data('volume-id', volume_id );
1685
                $('#modal-item-group-delete-submit').data('item-group-id', item_group_id );
1686
                $('#modal-volume-delete-submit').removeAttr('disabled');
1686
                $('#modal-item-group-delete-submit').removeAttr('disabled');
1687
                $('#modal-volume-delete').modal('show');
1687
                $('#modal-item-group-delete').modal('show');
1688
            });
1688
            });
1689
            $("#modal-volume-delete-submit").on('click', function(){
1689
            $("#modal-item-group-delete-submit").on('click', function(){
1690
                $('#modal-volume-delete-submit').attr('disabled', 'disabled');
1690
                $('#modal-item-group-delete-submit').attr('disabled', 'disabled');
1691
                const volume_id = $("#modal-volume-delete-submit").data('volume-id');
1691
                const item_group_id = $("#modal-item-group-delete-submit").data('item-group-id');
1692
1692
1693
                $.ajax({
1693
                $.ajax({
1694
                    url: `/api/v1/biblios/${biblionumber}/volumes/${volume_id}`,
1694
                    url: `/api/v1/biblios/${biblionumber}/item_groups/${item_group_id}`,
1695
                    headers: { "x-koha-embed": "items" },
1695
                    headers: { "x-koha-embed": "items" },
1696
                    success: function(volume_data){
1696
                    success: function(item_group_data){
1697
                        $.ajax({
1697
                        $.ajax({
1698
                          "url": `/api/v1/biblios/${biblionumber}/volumes/${volume_id}`,
1698
                          "url": `/api/v1/biblios/${biblionumber}/item_groups/${item_group_id}`,
1699
                          "method": "DELETE",
1699
                          "method": "DELETE",
1700
                        })
1700
                        })
1701
                        .done(function (response) {
1701
                        .done(function (response) {
1702
                            $('#modal-volume-delete').modal('hide');
1702
                            $('#modal-item-group-delete').modal('hide');
1703
                            $(`#volume-add-form-select option[value='${volume_id}']`).remove();
1703
                            $(`#item-group-add-form-select option[value='${item_group_id}']`).remove();
1704
                            if ( volume_data.items === null ) {
1704
                            if ( item_group_data.items === null ) {
1705
                                // No items for this volume, we can just refresh the table
1705
                                // No items for this item group, we can just refresh the table
1706
                                volumesTable.api().ajax.reload();
1706
                                itemGroupsTable.api().ajax.reload();
1707
                            } else {
1707
                            } else {
1708
                                // This volume had items attached to it, we need to reload the page
1708
                                // This item group had items attached to it, we need to reload the page
1709
                                window.location.replace(`/cgi-bin/koha/catalogue/detail.pl?biblionumber=${biblionumber}`);
1709
                                window.location.replace(`/cgi-bin/koha/catalogue/detail.pl?biblionumber=${biblionumber}`);
1710
                            }
1710
                            }
1711
                        })
1711
                        })
Lines 1717-1732 Note that permanent location is a code, and location may be an authval. Link Here
1717
                });
1717
                });
1718
            });
1718
            });
1719
1719
1720
            // Add item(s) to a volume
1720
            // Add item(s) to a item group
1721
            $('.itemselection_action_volume_set').on('click', function(){
1721
            $('.itemselection_action_item_group_set').on('click', function(){
1722
                $('#modal-volume-set').modal('show');
1722
                $('#modal-item-group-set').modal('show');
1723
            });
1723
            });
1724
1724
1725
            $("#modal-volume-set-form").validate({
1725
            $("#modal-item-group-set-form").validate({
1726
                submitHandler: function(form) {
1726
                submitHandler: function(form) {
1727
                    $('#modal-volume-set-submit').attr('disabled', 'disabled');
1727
                    $('#modal-item-group-set-submit').attr('disabled', 'disabled');
1728
1728
1729
                    const volume_id = $('#volume-add-form-select').val();
1729
                    const item_group_id = $('#item-group-add-form-select').val();
1730
1730
1731
                    let itemnumbers = new Array();
1731
                    let itemnumbers = new Array();
1732
                    $("input[name='itemnumber'][type='checkbox']:checked").each(function() {
1732
                    $("input[name='itemnumber'][type='checkbox']:checked").each(function() {
Lines 1734-1758 Note that permanent location is a code, and location may be an authval. Link Here
1734
                        itemnumbers.push( itemnumber );
1734
                        itemnumbers.push( itemnumber );
1735
                    });
1735
                    });
1736
                    if (itemnumbers.length > 0) {
1736
                    if (itemnumbers.length > 0) {
1737
                        let url = '/cgi-bin/koha/catalogue/detail.pl?op=set_volume';
1737
                        let url = '/cgi-bin/koha/catalogue/detail.pl?op=set_item_group';
1738
                        url += '&itemnumber=' + itemnumbers.join('&itemnumber=');
1738
                        url += '&itemnumber=' + itemnumbers.join('&itemnumber=');
1739
                        url += '&biblionumber=[% biblionumber | uri %]';
1739
                        url += '&biblionumber=[% biblionumber | uri %]';
1740
                        url += `&volume_id=${volume_id}`;
1740
                        url += `&item_group_id=${item_group_id}`;
1741
1741
1742
                        window.location.replace(url);
1742
                        window.location.replace(url);
1743
                    }
1743
                    }
1744
1744
1745
                    $('#modal-volume-set').modal('hide');
1745
                    $('#modal-item-group-set').modal('hide');
1746
                }
1746
                }
1747
            });
1747
            });
1748
1748
1749
            // Remove item(s) from a volume
1749
            // Remove item(s) from an item group
1750
            $('.itemselection_action_volume_unset').on('click', function(){
1750
            $('.itemselection_action_item_group_unset').on('click', function(){
1751
                $('#modal-volume-unset').modal('show');
1751
                $('#modal-item-group-unset').modal('show');
1752
            });
1752
            });
1753
1753
1754
            $("#modal-volume-unset-submit").on('click', function(){
1754
            $("#modal-item-group-unset-submit").on('click', function(){
1755
                $('#modal-volume-unset-submit').attr('disabled', 'disabled');
1755
                $('#modal-item-group-unset-submit').attr('disabled', 'disabled');
1756
1756
1757
                let itemnumbers = new Array();
1757
                let itemnumbers = new Array();
1758
                $("input[name='itemnumber'][type='checkbox']:checked").each(function() {
1758
                $("input[name='itemnumber'][type='checkbox']:checked").each(function() {
Lines 1760-1773 Note that permanent location is a code, and location may be an authval. Link Here
1760
                    itemnumbers.push( itemnumber );
1760
                    itemnumbers.push( itemnumber );
1761
                });
1761
                });
1762
                if (itemnumbers.length > 0) {
1762
                if (itemnumbers.length > 0) {
1763
                    let url = '/cgi-bin/koha/catalogue/detail.pl?op=unset_volume';
1763
                    let url = '/cgi-bin/koha/catalogue/detail.pl?op=unset_item_group';
1764
                    url += '&itemnumber=' + itemnumbers.join('&itemnumber=');
1764
                    url += '&itemnumber=' + itemnumbers.join('&itemnumber=');
1765
                    url += '&biblionumber=[% biblionumber | uri %]';
1765
                    url += '&biblionumber=[% biblionumber | uri %]';
1766
1766
1767
                    window.location.replace(url);
1767
                    window.location.replace(url);
1768
                }
1768
                }
1769
1769
1770
                $('#modal-volume-unset').modal('hide');
1770
                $('#modal-item-group-unset').modal('hide');
1771
            });
1771
            });
1772
        [% END %]
1772
        [% END %]
1773
    </script>
1773
    </script>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt (-15 / +15 lines)
Lines 239-268 Link Here
239
    <input type="hidden" name="indicator" value=" " />
239
    <input type="hidden" name="indicator" value=" " />
240
    <input type="hidden" name="itemnumber" value="[% itemnumber | html %]" />
240
    <input type="hidden" name="itemnumber" value="[% itemnumber | html %]" />
241
241
242
[% IF volumes.count && op != 'saveitem' && CAN_user_editcatalogue_manage_volumes %]
242
[% IF item_groups.count && op != 'saveitem' && CAN_user_editcatalogue_manage_item_groups %]
243
    <fieldset class="rows">
243
    <fieldset class="rows">
244
        <legend><i class="fa fa-plus"></i> Add to volume</legend>
244
        <legend><i class="fa fa-plus"></i> Add to item group</legend>
245
        [% FOREACH v IN volumes %]
245
        [% FOREACH ig IN item_groups %]
246
            <input type="hidden" id="volume-[% v.id | html %]" value="[% v.description | html %]" />
246
            <input type="hidden" id="item-group-[% ig.id | html %]" value="[% ig.description | html %]" />
247
        [% END %]
247
        [% END %]
248
        <p>
248
        <p>
249
            <label for="select_volume">Options: </label>
249
            <label for="select_item_group">Options: </label>
250
            <select name="volume" id="volume-add-or-create-form-select">
250
            <select name="item_group" id="item-group-add-or-create-form-select">
251
                <optgroup label="Use existing volume">
251
                <optgroup label="Use existing item group">
252
                    [% FOREACH v IN volumes %]
252
                    [% FOREACH ig IN item_groups %]
253
                        <option value="[% v.id | html %]">Use: [% v.description | html %]</option>
253
                        <option value="[% ig.id | html %]">Use: [% ig.description | html %]</option>
254
                    [% END %]
254
                    [% END %]
255
                </optgroup>
255
                </optgroup>
256
                <optgroup label="Other options">
256
                <optgroup label="Other options">
257
                    <option id="volume-add-or-create-form-no-add" value="">Do not add to volume</option>
257
                    <option id="item-group-add-or-create-form-no-add" value="">Do not add to item group</option>
258
                    <option value="create">Create new volume</option>
258
                    <option value="create">Create new item group</option>
259
                </optgroup>
259
                </optgroup>
260
            </select>
260
            </select>
261
        </p>
261
        </p>
262
262
263
        <p id="volume-add-or-create-form-description-block">
263
        <p id="item-group-add-or-create-form-description-block">
264
            <label for="volume_description" class="required">Name: </label>
264
            <label for="item_group_description" class="required">Name: </label>
265
            <input name="volume_description" id="volume-add-or-create-form-description" type="text" size="30" class="required" />
265
            <input name="item_group_description" id="item-group-add-or-create-form-description" type="text" size="30" class="required" />
266
            <span class="required">Required</span>
266
            <span class="required">Required</span>
267
        </p>
267
        </p>
268
    </fieldset>
268
    </fieldset>
Lines 319-325 Link Here
319
319
320
[% MACRO jsinclude BLOCK %]
320
[% MACRO jsinclude BLOCK %]
321
    <script>
321
    <script>
322
      var has_volumes = [% volumes.count | html %];
322
      var has_item_groups = [% item_groups.count | html %];
323
    </script>
323
    </script>
324
[% END %]
324
[% END %]
325
325
(-)a/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js (-12 / +12 lines)
Lines 69-98 $(document).ready(function(){ Link Here
69
        multiCopyControl.toggle();
69
        multiCopyControl.toggle();
70
    });
70
    });
71
71
72
    // Add new item to volume
72
    // Add new item to an item group
73
    if ( has_volumes ) {
73
    if ( has_item_groups ) {
74
        $('#volume-add-or-create-form-description-block').hide();
74
        $('#item-add-or-create-form-description-block').hide();
75
        $('#volume-add-or-create-form-no-add').attr('selected', 'selected' );
75
        $('#item-group-add-or-create-form-no-add').attr('selected', 'selected' );
76
76
77
        $('#volume-add-or-create-form-select').on('change', function(){
77
        $('#item-group-add-or-create-form-select').on('change', function(){
78
            if ( $(this).val() == 'create' ) {
78
            if ( $(this).val() == 'create' ) {
79
                $('#volume-add-or-create-form-description')
79
                $('#item-group-add-or-create-form-description')
80
                    .addClass('required')
80
                    .addClass('required')
81
                    .attr( 'required', 'required' );
81
                    .attr( 'required', 'required' );
82
                $('#volume-add-or-create-form-description-block').show();
82
                $('#item-group-add-or-create-form-description-block').show();
83
            } else {
83
            } else {
84
                $('#volume-add-or-create-form-description')
84
                $('#item-group-add-or-create-form-description')
85
                    .removeClass('required')
85
                    .removeClass('required')
86
                    .removeAttr('required');
86
                    .removeAttr('required');
87
                $('#volume-add-or-create-form-description-block').hide();
87
                $('#item-group-add-or-create-form-description-block').hide();
88
            }
88
            }
89
        });
89
        });
90
    }
90
    }
91
91
92
    $('#volume-add-or-create-form-select').on('change', function() {
92
    $('#item-group-add-or-create-form-select').on('change', function() {
93
        if ( ! $('input.items-enumchron').val() ) {
93
        if ( ! $('input.items-enumchron').val() ) {
94
            let volume_selector = '#volume-' + $(this).val();
94
            let item_group_selector = '#item-group-' + $(this).val();
95
            let enumchron = $(volume_selector).val();
95
            let enumchron = $(item_group_selector).val();
96
            $('input.items-enumchron').val( enumchron );
96
            $('input.items-enumchron').val( enumchron );
97
        }
97
        }
98
    });
98
    });
(-)a/t/db_dependent/Koha/Biblio.t (-1 / +28 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 14;
20
use Test::More tests => 15;
21
21
22
use C4::Biblio qw( AddBiblio ModBiblio );
22
use C4::Biblio qw( AddBiblio ModBiblio );
23
use Koha::Database;
23
use Koha::Database;
Lines 637-639 subtest 'get_marc_notes() UNIMARC tests' => sub { Link Here
637
637
638
    $schema->storage->txn_rollback;
638
    $schema->storage->txn_rollback;
639
};
639
};
640
641
subtest 'item_groups() tests' => sub {
642
643
    plan tests => 6;
644
645
    $schema->storage->txn_begin;
646
647
    my $biblio = $builder->build_sample_biblio();
648
649
    my @item_groups = $biblio->item_groups;
650
    is( scalar(@item_groups), 0, 'Got zero item groups');
651
652
    my $item_group_1 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
653
654
    @item_groups = $biblio->item_groups;
655
    is( scalar(@item_groups), 1, 'Got one item group');
656
    is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group');
657
658
    my $item_group_2 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
659
660
    @item_groups = $biblio->item_groups;
661
    is( scalar(@item_groups), 2, 'Got two item groups');
662
    is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group 1');
663
    is( $item_groups[1]->id, $item_group_2->id, 'Got correct item group 2');
664
665
    $schema->storage->txn_rollback;
666
};
(-)a/t/db_dependent/Koha/Biblio/ItemGroups.t (+70 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 3;
21
22
use Koha::Database;
23
24
use t::lib::TestBuilder;
25
use t::lib::Mocks;
26
27
BEGIN {
28
    use_ok('Koha::Biblio::ItemGroup');
29
    use_ok('Koha::Biblio::ItemGroups');
30
}
31
32
my $schema  = Koha::Database->new->schema;
33
my $builder = t::lib::TestBuilder->new;
34
35
t::lib::Mocks::mock_preference('EnableItemGroups', 1);
36
37
subtest 'add_item() and items() tests' => sub {
38
39
    plan tests => 8;
40
41
    $schema->storage->txn_begin;
42
43
    my $biblio = $builder->build_sample_biblio();
44
    my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
45
    my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
46
47
    my $item_group = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
48
49
    my $items = $item_group->items;
50
    is( $items->count, 0, 'Item group has no items');
51
52
    $item_group->add_item({ item_id => $item_1->id });
53
    my @items = $item_group->items;
54
    is( scalar(@items), 1, 'Item group has one item');
55
    is( $items[0]->id, $item_1->id, 'Item 1 is correct' );
56
57
    $item_group->add_item({ item_id => $item_2->id });
58
    @items = $item_group->items;
59
    is( scalar(@items), 2, 'Item group has two items');
60
    is( $items[0]->id, $item_1->id, 'Item 1 is correct' );
61
    is( $items[1]->id, $item_2->id, 'Item 2 is correct' );
62
63
    # Remove an item
64
    $item_1->delete;
65
    @items = $item_group->items;
66
    is( scalar(@items), 1, 'Item group now has only one item');
67
    is( $items[0]->id, $item_2->id, 'Item 2 is correct' );
68
69
    $schema->storage->txn_rollback;
70
};
(-)a/t/db_dependent/Koha/Item.t (-1 / +26 lines)
Lines 19-25 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 9;
22
use Test::More tests => 10;
23
use Test::Exception;
23
use Test::Exception;
24
24
25
use C4::Biblio qw( GetMarcSubfieldStructure );
25
use C4::Biblio qw( GetMarcSubfieldStructure );
Lines 816-818 subtest 'get_transfers' => sub { Link Here
816
816
817
    $schema->storage->txn_rollback;
817
    $schema->storage->txn_rollback;
818
};
818
};
819
820
subtest 'item_group() tests' => sub {
821
822
    plan tests => 4;
823
824
    $schema->storage->txn_begin;
825
826
    my $biblio = $builder->build_sample_biblio();
827
    my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
828
    my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
829
830
    is( $item_1->item_group, undef, 'Item 1 has no item group');
831
    is( $item_2->item_group, undef, 'Item 2 has no item group');
832
833
    my $item_group_1 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
834
    my $item_group_2 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
835
836
    $item_group_1->add_item({ item_id => $item_1->id });
837
    $item_group_2->add_item({ item_id => $item_2->id });
838
839
    is( $item_1->item_group->id, $item_group_1->id, 'Got item group 1 correctly' );
840
    is( $item_2->item_group->id, $item_group_2->id, 'Got item group 2 correctly' );
841
842
    $schema->storage->txn_rollback;
843
};
(-)a/t/db_dependent/Koha/Volumes.t (-156 lines)
Lines 1-156 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 6;
21
22
use Koha::Database;
23
24
use t::lib::TestBuilder;
25
use t::lib::Mocks;
26
27
BEGIN {
28
    use_ok('Koha::Biblio::Volume');
29
    use_ok('Koha::Biblio::Volumes');
30
}
31
32
my $schema  = Koha::Database->new->schema;
33
my $builder = t::lib::TestBuilder->new;
34
35
t::lib::Mocks::mock_preference('EnableVolumes', 1);
36
37
subtest 'Test Koha::Biblio::volumes' => sub {
38
39
    plan tests => 6;
40
41
    $schema->storage->txn_begin;
42
43
    my $biblio = $builder->build_sample_biblio();
44
45
    my @volumes = $biblio->volumes->as_list;
46
    is( scalar(@volumes), 0, 'Got zero volumes');
47
48
    my $volume_1 = Koha::Biblio::Volume->new( { biblionumber => $biblio->id } )->store();
49
50
    @volumes = $biblio->volumes->as_list;
51
    is( scalar(@volumes), 1, 'Got one volume');
52
    is( $volumes[0]->id, $volume_1->id, 'Got correct volume');
53
54
    my $volume_2 = Koha::Biblio::Volume->new( { biblionumber => $biblio->id } )->store();
55
56
    @volumes = $biblio->volumes->as_list;
57
    is( scalar(@volumes), 2, 'Got two volumes');
58
    is( $volumes[0]->id, $volume_1->id, 'Got correct volume 1');
59
    is( $volumes[1]->id, $volume_2->id, 'Got correct volume 2');
60
61
    $schema->storage->txn_rollback;
62
};
63
64
subtest 'Test Koha::Biblio::Volume::add_item & Koha::Biblio::Volume::items' => sub {
65
66
    plan tests => 6;
67
68
    $schema->storage->txn_begin;
69
70
    my $biblio = $builder->build_sample_biblio();
71
    my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
72
    my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
73
74
    my $volume = Koha::Biblio::Volume->new( { biblionumber => $biblio->id } )->store();
75
76
    my $items = $volume->items;
77
    is( $items->count, 0, 'Volume has no items');
78
79
    $volume->add_item({ item_id => $item_1->id });
80
    my @items = $volume->items->as_list;
81
    is( scalar(@items), 1, 'Volume has one item');
82
    is( $items[0]->id, $item_1->id, 'Item 1 is correct' );
83
84
    $volume->add_item({ item_id => $item_2->id });
85
    @items = $volume->items->as_list;
86
    is( scalar(@items), 2, 'Volume has two items');
87
    is( $items[0]->id, $item_1->id, 'Item 1 is correct' );
88
    is( $items[1]->id, $item_2->id, 'Item 2 is correct' );
89
90
    $schema->storage->txn_rollback;
91
};
92
93
subtest 'Test Koha::Item::volume' => sub {
94
95
    plan tests => 4;
96
97
    $schema->storage->txn_begin;
98
99
    my $biblio = $builder->build_sample_biblio();
100
    my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
101
    my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
102
103
    is( $item_1->volume, undef, 'Item 1 has no volume');
104
    is( $item_2->volume, undef, 'Item 2 has no volume');
105
106
    my $volume_1 = Koha::Biblio::Volume->new( { biblionumber => $biblio->id } )->store();
107
    my $volume_2 = Koha::Biblio::Volume->new( { biblionumber => $biblio->id } )->store();
108
109
    $volume_1->add_item({ item_id => $item_1->id });
110
    $volume_2->add_item({ item_id => $item_2->id });
111
112
    is( $item_1->volume->id, $volume_1->id, 'Got volume 1 correctly' );
113
    is( $item_2->volume->id, $volume_2->id, 'Got volume 2 correctly' );
114
115
    $schema->storage->txn_rollback;
116
};
117
118
subtest 'Koha::Item::delete should delete volume if no other items are using the volume' => sub {
119
120
    plan tests => 11;
121
122
    $schema->storage->txn_begin;
123
124
    my $biblio = $builder->build_sample_biblio();
125
    my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
126
    my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
127
    is( $biblio->items->count, 2, 'Bib has 2 items');
128
129
    is( $item_1->volume, undef, 'Item 1 has no volume');
130
    is( $item_2->volume, undef, 'Item 2 has no volume');
131
132
    my $volume_1 = Koha::Biblio::Volume->new( { biblionumber => $biblio->id } )->store();
133
134
    $volume_1->add_item({ item_id => $item_1->id });
135
    $volume_1->add_item({ item_id => $item_2->id });
136
137
    my $volume = Koha::Biblio::Volumes->find( $volume_1->id );
138
    is( $volume->id, $volume_1->id, 'Found the correct volume');
139
140
    is( $item_1->volume->id, $volume_1->id, 'Item 1 has correct volume');
141
    is( $item_2->volume->id, $volume_1->id, 'Item 2 has correct volume');
142
143
    $item_1->delete();
144
    is( $biblio->items->count, 1, 'Bib has 2 item');
145
    $volume = Koha::Biblio::Volumes->find( $volume_1->id );
146
    is( $volume->id, $volume_1->id, 'Volume still exists after deleting and item, but other items remain');
147
148
    is( $item_2->volume->id, $volume_1->id, 'Item 2 still has correct volume');
149
150
    $item_2->delete();
151
    is( $biblio->items->count, 0, 'Bib has 0 items');
152
    $volume = Koha::Biblio::Volumes->find( $volume_1->id );
153
    is( $volume, undef, 'Volume was deleted when last item was deleted');
154
155
    $schema->storage->txn_rollback;
156
};
(-)a/t/db_dependent/api/v1/volumes.t (-44 / +44 lines)
Lines 26-31 use t::lib::Mocks; Link Here
26
26
27
use List::Util qw(min);
27
use List::Util qw(min);
28
28
29
use Koha::Biblio::ItemGroups;
29
use Koha::Libraries;
30
use Koha::Libraries;
30
use Koha::Database;
31
use Koha::Database;
31
32
Lines 33-43 my $schema = Koha::Database->new->schema; Link Here
33
my $builder = t::lib::TestBuilder->new;
34
my $builder = t::lib::TestBuilder->new;
34
35
35
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
36
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
36
t::lib::Mocks::mock_preference( 'EnableVolumes', 1 );
37
t::lib::Mocks::mock_preference( 'EnableItemGroups', 1 );
37
38
38
my $t = Test::Mojo->new('Koha::REST::V1');
39
my $t = Test::Mojo->new('Koha::REST::V1');
39
40
40
subtest 'volumes list() tests' => sub {
41
subtest 'list() tests' => sub {
41
    plan tests => 9;
42
    plan tests => 9;
42
43
43
    $schema->storage->txn_begin;
44
    $schema->storage->txn_begin;
Lines 53-73 subtest 'volumes list() tests' => sub { Link Here
53
    my $biblio = $builder->build_sample_biblio();
54
    my $biblio = $builder->build_sample_biblio();
54
    my $biblio_id = $biblio->id;
55
    my $biblio_id = $biblio->id;
55
56
56
    $t->get_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/volumes" )
57
    $t->get_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/item_groups" )
57
        ->status_is( 200, 'SWAGGER3.2.2' );
58
        ->status_is( 200, 'SWAGGER3.2.2' );
58
    my $response_count = scalar @{ $t->tx->res->json };
59
    my $response_count = scalar @{ $t->tx->res->json };
59
    is( $response_count, 0, 'Results count is 2');
60
    is( $response_count, 0, 'Results count is 2');
60
61
61
    my $volume_1 = Koha::Biblio::Volume->new( { biblionumber => $biblio->id, display_order => 1, description => "Vol 1" } )->store();
62
    my $item_group_1 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id, display_order => 1, description => "Vol 1" } )->store();
62
63
63
    $t->get_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/volumes" )
64
    $t->get_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/item_groups" )
64
        ->status_is( 200, 'SWAGGER3.2.2' );
65
        ->status_is( 200, 'SWAGGER3.2.2' );
65
    $response_count = scalar @{ $t->tx->res->json };
66
    $response_count = scalar @{ $t->tx->res->json };
66
    is( $response_count, 1, 'Results count is 2');
67
    is( $response_count, 1, 'Results count is 2');
67
68
68
    my $volume_2 = Koha::Biblio::Volume->new( { biblionumber => $biblio->id, display_order => 2, description => "Vol 2" } )->store();
69
    my $item_group_2 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id, display_order => 2, description => "Vol 2" } )->store();
69
70
70
    $t->get_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/volumes" )
71
    $t->get_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/item_groups" )
71
      ->status_is( 200, 'SWAGGER3.2.2' );
72
      ->status_is( 200, 'SWAGGER3.2.2' );
72
73
73
    $response_count = scalar @{ $t->tx->res->json };
74
    $response_count = scalar @{ $t->tx->res->json };
Lines 76-82 subtest 'volumes list() tests' => sub { Link Here
76
    $schema->storage->txn_rollback;
77
    $schema->storage->txn_rollback;
77
};
78
};
78
79
79
subtest 'volumes add() tests' => sub {
80
subtest 'add() tests' => sub {
80
81
81
    plan tests => 6;
82
    plan tests => 6;
82
83
Lines 98-119 subtest 'volumes add() tests' => sub { Link Here
98
    my $unauth_userid = $unauthorized_patron->userid;
99
    my $unauth_userid = $unauthorized_patron->userid;
99
100
100
    my $biblio = $builder->build_sample_biblio();
101
    my $biblio = $builder->build_sample_biblio();
101
    my $biblio_id = $biblio->id;
102
    my $biblio_id  = $biblio->id;
102
    my $volume = { description => 'Vol 1', display_order => 1 };
103
    my $item_group = { description => 'Vol 1', display_order => 1 };
103
104
104
    # Unauthorized attempt
105
    # Unauthorized attempt
105
    $t->post_ok( "//$unauth_userid:$password@/api/v1/biblios/$biblio_id/volumes" => json => $volume )
106
    $t->post_ok( "//$unauth_userid:$password@/api/v1/biblios/$biblio_id/item_groups" => json => $item_group )
106
      ->status_is(403);
107
      ->status_is(403);
107
108
108
    # Authorized attempt
109
    # Authorized attempt
109
    $t->post_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/volumes" => json => $volume )
110
    $t->post_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/item_groups" => json => $item_group )
110
      ->status_is( 201, 'SWAGGER3.2.1' );
111
      ->status_is( 201, 'SWAGGER3.2.1' );
111
112
112
    # Invalid biblio id
113
    # Invalid biblio id
113
    {   # hide useless warnings
114
    {   # hide useless warnings
114
        local *STDERR;
115
        local *STDERR;
115
        open STDERR, '>', '/dev/null';
116
        open STDERR, '>', '/dev/null';
116
        $t->post_ok( "//$auth_userid:$password@/api/v1/biblios/XXX/volumes" => json => $volume )
117
        $t->post_ok( "//$auth_userid:$password@/api/v1/biblios/XXX/item_groups" => json => $item_group )
117
            ->status_is( 404 );
118
            ->status_is( 404 );
118
        close STDERR;
119
        close STDERR;
119
    }
120
    }
Lines 121-127 subtest 'volumes add() tests' => sub { Link Here
121
    $schema->storage->txn_rollback;
122
    $schema->storage->txn_rollback;
122
};
123
};
123
124
124
subtest 'volumes update() tests' => sub {
125
subtest 'update() tests' => sub {
125
    plan tests => 9;
126
    plan tests => 9;
126
127
127
    $schema->storage->txn_begin;
128
    $schema->storage->txn_begin;
Lines 143-173 subtest 'volumes update() tests' => sub { Link Here
143
144
144
    my $biblio = $builder->build_sample_biblio();
145
    my $biblio = $builder->build_sample_biblio();
145
    my $biblio_id = $biblio->id;
146
    my $biblio_id = $biblio->id;
146
    my $volume = Koha::Biblio::Volume->new( { biblionumber => $biblio->id, display_order => 1, description => "Vol 1" } )->store();
147
    my $item_group = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id, display_order => 1, description => "Vol 1" } )->store();
147
    my $volume_id = $volume->id;
148
    my $item_group_id = $item_group->id;
148
149
149
    # Unauthorized attempt
150
    # Unauthorized attempt
150
    $t->put_ok( "//$unauth_userid:$password@/api/v1/biblios/$biblio_id/volumes/$volume_id"
151
    $t->put_ok( "//$unauth_userid:$password@/api/v1/biblios/$biblio_id/item_groups/$item_group_id"
151
                    => json => { description => 'New unauthorized desc change' } )
152
                    => json => { description => 'New unauthorized desc change' } )
152
      ->status_is(403);
153
      ->status_is(403);
153
154
154
    # Authorized attempt
155
    # Authorized attempt
155
    $t->put_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/volumes/$volume_id" => json => { description => "Vol A" } )
156
    $t->put_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/item_groups/$item_group_id" => json => { description => "Vol A" } )
156
      ->status_is(200, 'SWAGGER3.2.1')
157
      ->status_is(200, 'SWAGGER3.2.1')
157
      ->json_has( '/description' => "Vol A", 'SWAGGER3.3.3' );
158
      ->json_has( '/description' => "Vol A", 'SWAGGER3.3.3' );
158
159
159
    # Invalid biblio id
160
    # Invalid biblio id
160
    $t->put_ok( "//$auth_userid:$password@/api/v1/biblios/XXX/volumes/$volume_id" => json => { description => "Vol A" } )
161
    $t->put_ok( "//$auth_userid:$password@/api/v1/biblios/XXX/item_groups/$item_group_id" => json => { description => "Vol A" } )
161
        ->status_is(404);
162
        ->status_is(404);
162
163
163
    # Invalid volume id
164
    # Invalid item group id
164
    $t->put_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/volumes/XXX" => json => { description => "Vol A" } )
165
    $t->put_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/item_groups/XXX" => json => { description => "Vol A" } )
165
        ->status_is(404);
166
        ->status_is(404);
166
167
167
    $schema->storage->txn_rollback;
168
    $schema->storage->txn_rollback;
168
};
169
};
169
170
170
subtest 'volumes delete() tests' => sub {
171
subtest 'delete() tests' => sub {
171
    plan tests => 9;
172
    plan tests => 9;
172
173
173
    $schema->storage->txn_begin;
174
    $schema->storage->txn_begin;
Lines 189-209 subtest 'volumes delete() tests' => sub { Link Here
189
190
190
    my $biblio = $builder->build_sample_biblio();
191
    my $biblio = $builder->build_sample_biblio();
191
    my $biblio_id = $biblio->id;
192
    my $biblio_id = $biblio->id;
192
    my $volume = Koha::Biblio::Volume->new( { biblionumber => $biblio->id, display_order => 1, description => "Vol 1" } )->store();
193
    my $item_group = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id, display_order => 1, description => "Vol 1" } )->store();
193
    my $volume_id = $volume->id;
194
    my $item_groupid = $item_group->id;
194
195
195
    $t->delete_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/volumes/$volume_id" )
196
    $t->delete_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/item_groups/$item_groupid" )
196
        ->status_is(204, 'SWAGGER3.2.4')
197
        ->status_is(204, 'SWAGGER3.2.4')
197
        ->content_is('', 'SWAGGER3.3.4');
198
        ->content_is('', 'SWAGGER3.3.4');
198
199
199
    # Unauthorized attempt to delete
200
    # Unauthorized attempt to delete
200
    $t->delete_ok( "//$unauth_userid:$password@/api/v1/biblios/$biblio_id/volumes/$volume_id" )
201
    $t->delete_ok( "//$unauth_userid:$password@/api/v1/biblios/$biblio_id/item_groups/$item_groupid" )
201
      ->status_is(403);
202
      ->status_is(403);
202
203
203
    $t->delete_ok( "//$auth_userid:$password@/api/v1/biblios/XXX/volumes/$volume_id" )
204
    $t->delete_ok( "//$auth_userid:$password@/api/v1/biblios/XXX/item_groups/$item_groupid" )
204
      ->status_is(404);
205
      ->status_is(404);
205
206
206
    $t->delete_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/volumes/XXX" )
207
    $t->delete_ok( "//$auth_userid:$password@/api/v1/biblios/$biblio_id/item_groups/XXX" )
207
      ->status_is(404);
208
      ->status_is(404);
208
209
209
    $schema->storage->txn_rollback;
210
    $schema->storage->txn_rollback;
Lines 225-264 subtest 'volume items add() + delete() tests' => sub { Link Here
225
    my $biblio = $builder->build_sample_biblio();
226
    my $biblio = $builder->build_sample_biblio();
226
    my $biblio_id = $biblio->id;
227
    my $biblio_id = $biblio->id;
227
228
228
    my $volume = Koha::Biblio::Volume->new( { biblionumber => $biblio->id, display_order => 1, description => "Vol 1" } )->store();
229
    my $item_group = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id, display_order => 1, description => "Vol 1" } )->store();
229
    my $volume_id = $volume->id;
230
    my $item_groupid = $item_group->id;
230
231
231
    my @items = $volume->items;
232
    my @items = $item_group->items;
232
    is( scalar(@items), 0, 'Volume has no items');
233
    is( scalar(@items), 0, 'Item group has no items');
233
234
234
    my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
235
    my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
235
    my $item_1_id = $item_1->id;
236
    my $item_1_id = $item_1->id;
236
237
237
    $t->post_ok( "//$userid:$password@/api/v1/biblios/XXX/volumes/$volume_id/items" => json => { item_id => $item_1->id } )
238
    $t->post_ok( "//$userid:$password@/api/v1/biblios/XXX/item_groups/$item_groupid/items" => json => { item_id => $item_1->id } )
238
      ->status_is( 409 )
239
      ->status_is( 409 )
239
      ->json_is( { error => 'Volume does not belong to passed biblio_id' } );
240
      ->json_is( { error => 'Item group does not belong to passed biblio_id' } );
240
241
241
    $t->post_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/volumes/$volume_id/items" => json => { item_id => $item_1->id } )
242
    $t->post_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/item_groups/$item_groupid/items" => json => { item_id => $item_1->id } )
242
      ->status_is( 201, 'SWAGGER3.2.1' );
243
      ->status_is( 201, 'SWAGGER3.2.1' );
243
244
244
    @items = $volume->items;
245
    @items = $item_group->items;
245
    is( scalar(@items), 1, 'Volume now has one item');
246
    is( scalar(@items), 1, 'Item group now has one item');
246
247
247
    my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
248
    my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
248
    my $item_2_id = $item_2->id;
249
    my $item_2_id = $item_2->id;
249
250
250
    $t->post_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/volumes/$volume_id/items" => json => { item_id => $item_2->id } )
251
    $t->post_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/item_groups/$item_groupid/items" => json => { item_id => $item_2->id } )
251
      ->status_is( 201, 'SWAGGER3.2.1' );
252
      ->status_is( 201, 'SWAGGER3.2.1' );
252
253
253
    @items = $volume->items;
254
    @items = $item_group->items;
254
    is( scalar(@items), 2, 'Volume now has two items');
255
    is( scalar(@items), 2, 'Item group now has two items');
255
256
256
    $t->delete_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/volumes/$volume_id/items/$item_1_id" )
257
    $t->delete_ok( "//$userid:$password@/api/v1/biblios/$biblio_id/item_groups/$item_groupid/items/$item_1_id" )
257
        ->status_is(204, 'SWAGGER3.2.4')
258
        ->status_is(204, 'SWAGGER3.2.4')
258
        ->content_is('', 'SWAGGER3.3.4');
259
        ->content_is('', 'SWAGGER3.3.4');
259
260
260
    @items = $volume->items;
261
    @items = $item_group->items;
261
    is( scalar(@items), 1, 'Volume now has one item');
262
    is( scalar(@items), 1, 'Item group now has one item');
262
263
263
    $schema->storage->txn_rollback;
264
    $schema->storage->txn_rollback;
264
};
265
};
265
- 

Return to bug 24857