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

(-)a/Koha/Club/Hold.pm (+142 lines)
Line 0 Link Here
1
package Koha::Club::Hold;
2
3
# Copyright ByWater Solutions 2014
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use Carp;
23
24
use Koha::Database;
25
26
use Koha::Club::Template::Fields;
27
28
use base qw(Koha::Object);
29
use Koha::Exceptions::ClubHold;
30
use Koha::Club::Hold::PatronHold;
31
use Koha::Clubs;
32
33
use List::Util 'shuffle';
34
35
=head1 NAME
36
37
Koha::Club::Hold
38
39
Represents a hold made for every member of club
40
41
=head1 API
42
43
=head2 Class Methods
44
45
=cut
46
47
=head3 add
48
49
Class (static) method that returns a new Koha::Club::Hold instance
50
51
=cut
52
53
sub add {
54
    my ( $params ) = @_;
55
56
    throw Koha::Exceptions::ClubHold unless $params->{club_id} && $params->{biblio_id};
57
    my $club = Koha::Clubs->find($params->{club_id});
58
    my @enrollments = $club->club_enrollments->as_list;
59
    throw Koha::Exceptions::ClubHold::NoPatrons() unless scalar @enrollments;
60
61
    my $biblio = Koha::Biblios->find($params->{biblio_id});
62
63
    my $club_params = {
64
        club_id => $params->{club_id},
65
        biblio_id => $params->{biblio_id},
66
        item_id => $params->{item_id}
67
    };
68
69
    my $club_hold = Koha::Club::Hold->new($club_params)->store();
70
71
    @enrollments = shuffle(@enrollments);
72
73
    foreach my $enrollment (@enrollments) {
74
        my $patron_id = $enrollment->borrowernumber;
75
76
        my $can_place_hold
77
        = $params->{item_id}
78
        ? C4::Reserves::CanItemBeReserved( $patron_id, $params->{club_id} )
79
        : C4::Reserves::CanBookBeReserved( $patron_id, $params->{biblio_id} );
80
81
        unless ( $can_place_hold->{status} eq 'OK' ) {
82
            warn "Patron(".$patron_id.") Hold cannot be placed. Reason: " . $can_place_hold->{status};
83
            Koha::Club::Hold::PatronHold->new({
84
                patron_id => $patron_id,
85
                club_hold_id => $club_hold->id,
86
                error_code => $can_place_hold->{status}
87
            })->store();
88
            next;
89
        }
90
91
        my $priority = C4::Reserves::CalculatePriority($params->{biblio_id});
92
93
        my $hold_id = C4::Reserves::AddReserve(
94
            $params->{pickup_library_id},
95
            $patron_id,
96
            $params->{biblio_id},
97
            undef,    # $bibitems param is unused
98
            $priority,
99
            undef,    # hold date, we don't allow it currently
100
            $params->{expiration_date},
101
            $params->{notes},
102
            $biblio->title,
103
            $params->{item_id},
104
            undef,    # TODO: Why not?
105
            $params->{item_type}
106
        );
107
        if ($hold_id) {
108
            Koha::Club::Hold::PatronHold->new({
109
                patron_id => $patron_id,
110
                club_hold_id => $club_hold->id,
111
                hold_id => $hold_id
112
            })->store();
113
        } else {
114
            warn "Could not create hold for Patron(".$patron_id.")";
115
            Koha::Club::Hold::PatronHold->new({
116
                patron_id => $patron_id,
117
                club_hold_id => $club_hold->id,
118
                error_message => "Could not create hold for Patron(".$patron_id.")"
119
            })->store();
120
        }
121
122
    }
123
124
    return $club_hold;
125
126
}
127
128
=head3 type
129
130
=cut
131
132
sub _type {
133
    return 'ClubHold';
134
}
135
136
=head1 AUTHOR
137
138
Agustin Moyano <agustinmoyano@theke.io>
139
140
=cut
141
142
1;
(-)a/Koha/Club/Hold/PatronHold.pm (+56 lines)
Line 0 Link Here
1
package Koha::Club::Hold::PatronHold;
2
3
# Copyright ByWater Solutions 2014
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use Carp;
23
24
use Koha::Database;
25
26
use Koha::Club::Template::Fields;
27
28
use base qw(Koha::Object);
29
30
=head1 NAME
31
32
Koha::Club::Hold::PatronHold
33
34
Represents an effective hold made for a patron, because of a club hold
35
36
=head1 API
37
38
=head2 Class Methods
39
40
=cut
41
42
=head3 type
43
44
=cut
45
46
sub _type {
47
    return 'ClubHoldsToPatronHold';
48
}
49
50
=head1 AUTHOR
51
52
Agustin Moyano <agustinmoyano@theke.io>
53
54
=cut
55
56
1;
(-)a/Koha/Club/Hold/PatronHolds.pm (+64 lines)
Line 0 Link Here
1
package Koha::Club::Hold::PatronHolds;
2
3
# Copyright ByWater Solutions 2014
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use Carp;
23
24
use Koha::Database;
25
26
use Koha::Club::Field;
27
28
use base qw(Koha::Objects);
29
30
=head1 NAME
31
32
Koha::Club::Hold::PatronHolds
33
34
Represents a collection of effective holds made by a club hold.
35
36
=head1 API
37
38
=head2 Class Methods
39
40
=cut
41
42
=head3 type
43
44
=cut
45
46
sub _type {
47
    return 'ClubHoldsToPatronHold';
48
}
49
50
=head3 object_class
51
52
=cut
53
54
sub object_class {
55
    return 'Koha::Club::Hold::PatronHold';
56
}
57
58
=head1 AUTHOR
59
60
Agustin Moyano <agustinmoyano@theke.io>
61
62
=cut
63
64
1;
(-)a/Koha/Club/Holds.pm (+64 lines)
Line 0 Link Here
1
package Koha::Club::Holds;
2
3
# Copyright ByWater Solutions 2014
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use Carp;
23
24
use Koha::Database;
25
26
use Koha::Club::Field;
27
28
use base qw(Koha::Objects);
29
30
=head1 NAME
31
32
Koha::Club::Holds
33
34
Represents a collection of club holds.
35
36
=head1 API
37
38
=head2 Class Methods
39
40
=cut
41
42
=head3 type
43
44
=cut
45
46
sub _type {
47
    return 'ClubHold';
48
}
49
50
=head3 object_class
51
52
=cut
53
54
sub object_class {
55
    return 'Koha::Club::Hold';
56
}
57
58
=head1 AUTHOR
59
60
Agustin Moyano <agustinmoyano@theke.io>
61
62
=cut
63
64
1;
(-)a/Koha/Exceptions/ClubHold.pm (+15 lines)
Line 0 Link Here
1
package Koha::Exceptions::ClubHold;
2
3
use Modern::Perl;
4
5
use Exception::Class (
6
    'Koha::Exceptions::ClubHold' => {
7
        description => "Something went wrong!",
8
    },
9
    'Koha::Exceptions::ClubHold::NoPatrons' => {
10
        isa => 'Koha::Exceptions::ClubHold',
11
        description => "Cannot place a hold on a club without patrons.",
12
    },
13
);
14
15
1;
(-)a/Koha/REST/V1/Clubs/Holds.pm (+209 lines)
Line 0 Link Here
1
package Koha::REST::V1::Clubs::Holds;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use C4::Biblio;
23
use C4::Reserves;
24
25
use Koha::Items;
26
use Koha::Patrons;
27
use Koha::Holds;
28
use Koha::Clubs;
29
use Koha::Club::Hold;
30
use Koha::DateUtils;
31
32
use Scalar::Util qw(blessed);
33
use Try::Tiny;
34
use List::Util 'shuffle';
35
36
=head1 API
37
38
=head2 Class methods
39
40
=head3 add
41
42
Method that handles adding a new Koha::Hold object
43
44
=cut
45
46
sub add {
47
    my $c = shift->openapi->valid_input or return;
48
49
    return try {
50
        my $body    = $c->validation->param('body');
51
        my $club_id = $c->validation->param('club_id');
52
53
        my $biblio;
54
55
        my $biblio_id         = $body->{biblio_id};
56
        my $pickup_library_id = $body->{pickup_library_id};
57
        my $item_id           = $body->{item_id};
58
        my $item_type         = $body->{item_type};
59
        my $expiration_date   = $body->{expiration_date};
60
        my $notes             = $body->{notes};
61
62
        if ( $item_id and $biblio_id ) {
63
64
            # check they are consistent
65
            unless ( Koha::Items->search( { itemnumber => $item_id, biblionumber => $biblio_id } )
66
                ->count > 0 )
67
            {
68
                return $c->render(
69
                    status  => 400,
70
                    openapi => { error => "Item $item_id doesn't belong to biblio $biblio_id" }
71
                );
72
            }
73
            else {
74
                $biblio = Koha::Biblios->find($biblio_id);
75
            }
76
        }
77
        elsif ($item_id) {
78
            my $item = Koha::Items->find($item_id);
79
80
            unless ($item) {
81
                return $c->render(
82
                    status  => 404,
83
                    openapi => { error => "item_id not found." }
84
                );
85
            }
86
            else {
87
                $biblio = $item->biblio;
88
            }
89
        }
90
        elsif ($biblio_id) {
91
            $biblio = Koha::Biblios->find($biblio_id);
92
        }
93
        else {
94
            return $c->render(
95
                status  => 400,
96
                openapi => { error => "At least one of biblio_id, item_id should be given" }
97
            );
98
        }
99
100
        unless ($biblio) {
101
            return $c->render(
102
                status  => 400,
103
                openapi => "Biblio not found."
104
            );
105
        }
106
107
        # AddReserve expects date to be in syspref format
108
        if ($expiration_date) {
109
            $expiration_date = output_pref( dt_from_string( $expiration_date, 'rfc3339' ) );
110
        }
111
112
        my $club_hold = Koha::Club::Hold::add({
113
            club_id => $club_id,
114
            biblio_id => $biblio->biblionumber,
115
            item_id => $item_id,
116
            pickup_library_id => $pickup_library_id,
117
            expiration_date => $expiration_date,
118
            notes => $notes,
119
            item_type => $item_type
120
        });
121
122
        my $mapping = _to_api($club_hold->unblessed);
123
124
        return $c->render( status => 201, openapi => $mapping );
125
    }
126
    catch {
127
        if ( blessed $_ and $_->isa('Koha::Exceptions::Object') ) {
128
            if ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
129
                my $broken_fk = $_->broken_fk;
130
131
                if ( grep { $_ eq $broken_fk } keys %{$Koha::REST::V1::Clubs::Holds::to_api_mapping} ) {
132
                    $c->render(
133
                        status  => 404,
134
                        openapi => $Koha::REST::V1::Clubs::Holds::to_api_mapping->{$broken_fk} . ' not found.'
135
                    );
136
                }
137
                else {
138
                    return $c->render(
139
                        status  => 500,
140
                        openapi => { error => "Uncaught exception: $_" }
141
                    );
142
                }
143
            }
144
            else {
145
                return $c->render(
146
                    status  => 500,
147
                    openapi => { error => "$_" }
148
                );
149
            }
150
        }
151
        elsif (blessed $_ and $_->isa('Koha::Exceptions::ClubHold')) {
152
            return $c->render(
153
                status  => 500,
154
                openapi => { error => $_->description }
155
            );
156
        }
157
        else {
158
            return $c->render(
159
                status  => 500,
160
                openapi => { error => "Something went wrong. check the logs." }
161
            );
162
        }
163
    };
164
}
165
166
=head3 _to_api
167
168
Helper function that maps unblessed Koha::Club::Hold objects into REST api
169
attribute names.
170
171
=cut
172
173
sub _to_api {
174
    my $club_hold    = shift;
175
176
    # Rename attributes
177
    foreach my $column ( keys %{ $Koha::REST::V1::Clubs::Holds::to_api_mapping } ) {
178
        my $mapped_column = $Koha::REST::V1::Clubs::Holds::to_api_mapping->{$column};
179
        if (    exists $club_hold->{ $column }
180
             && defined $mapped_column )
181
        {
182
            # key != undef
183
            $club_hold->{ $mapped_column } = delete $club_hold->{ $column };
184
        }
185
        elsif (    exists $club_hold->{ $column }
186
                && !defined $mapped_column )
187
        {
188
            # key == undef
189
            delete $club_hold->{ $column };
190
        }
191
    }
192
193
    # Calculate the 'restricted' field
194
    return $club_hold;
195
}
196
197
=head3 $to_api_mapping
198
199
=cut
200
201
our $to_api_mapping = {
202
    id => 'club_hold_id',
203
    club_id => 'club_id',
204
    biblio_id => 'biblio_id',
205
    item_id => 'item_id'
206
};
207
208
209
1;
(-)a/api/v1/swagger/definitions/club_hold.json (+21 lines)
Line 0 Link Here
1
{
2
    "type": "object",
3
    "properties": {
4
      "club_hold_id": {
5
        "type": "integer",
6
        "description": "Internal club hold identifier"
7
      },
8
      "club_id": {
9
        "type": "integer",
10
        "description": "Internal club identifier"
11
      },
12
      "biblio_id": {
13
        "type": "integer",
14
        "description": "Internal biblio identifier"
15
      },
16
      "item_id": {
17
        "type": ["string", "null"],
18
        "description": "Internal item identifier"
19
      }
20
    }
21
  }
(-)a/api/v1/swagger/definitions/club_hold_patron_hold.json (+30 lines)
Line 0 Link Here
1
{
2
    "type": "object",
3
    "properties": {
4
      "club_hold_patron_hold_id": {
5
        "type": "integer",
6
        "description": "Internal club hold to patron hold identifier"
7
      },
8
      "club_hold_id": {
9
        "type": "integer",
10
        "description": "Internal club hold identifier"
11
      },
12
      "hold_id": {
13
        "type": ["integer", "null"],
14
        "description": "Internal hold identifier"
15
      },
16
      "patron_id": {
17
        "type": "integer",
18
        "description": "Internal patron identifier"
19
      },
20
      "error_code": {
21
        "type": ["string", "null"],
22
        "format": "date",
23
        "description": "Code returned by CanItemBeReserved"
24
      },
25
      "error_message": {
26
        "type": ["string", "null"],
27
        "description": "Generic error message"
28
      }
29
    }
30
  }
(-)a/api/v1/swagger/definitions/club_hold_patron_holds.json (+6 lines)
Line 0 Link Here
1
{
2
    "type": "array",
3
    "items": {
4
      "$ref": "club_hold_patron_hold.json"
5
    }
6
}
(-)a/api/v1/swagger/definitions/club_holds.json (+6 lines)
Line 0 Link Here
1
{
2
    "type": "array",
3
    "items": {
4
      "$ref": "club_hold.json"
5
    }
6
}
(-)a/api/v1/swagger/parameters.json (+3 lines)
Lines 14-19 Link Here
14
  "hold_id_pp": {
14
  "hold_id_pp": {
15
    "$ref": "parameters/hold.json#/hold_id_pp"
15
    "$ref": "parameters/hold.json#/hold_id_pp"
16
  },
16
  },
17
  "club_id_pp": {
18
    "$ref": "parameters/club.json#/club_id_pp"
19
  },
17
  "library_id_pp": {
20
  "library_id_pp": {
18
    "$ref": "parameters/library.json#/library_id_pp"
21
    "$ref": "parameters/library.json#/library_id_pp"
19
  },
22
  },
(-)a/api/v1/swagger/parameters/club.json (+9 lines)
Line 0 Link Here
1
{
2
    "club_id_pp": {
3
        "name": "club_id",
4
        "in": "path",
5
        "description": "Internal club identifier",
6
        "required": true,
7
        "type": "integer"
8
      }
9
}
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 29-34 Link Here
29
  "/biblios/{biblio_id}": {
29
  "/biblios/{biblio_id}": {
30
    "$ref": "paths/biblios.json#/~1biblios~1{biblio_id}"
30
    "$ref": "paths/biblios.json#/~1biblios~1{biblio_id}"
31
  },
31
  },
32
  "/clubs/{club_id}/holds": {
33
    "$ref": "paths/clubs.json#/~1clubs~1{club_id}~1holds"
34
  },
32
  "/holds": {
35
  "/holds": {
33
    "$ref": "paths/holds.json#/~1holds"
36
    "$ref": "paths/holds.json#/~1holds"
34
  },
37
  },
(-)a/api/v1/swagger/paths/clubs.json (+100 lines)
Line 0 Link Here
1
{
2
    "/clubs/{club_id}/holds": {
3
        "post": {
4
            "x-mojo-to": "Clubs::Holds#add",
5
            "operationId": "addClubHold",
6
            "tags": ["holds", "clubs"],
7
            "parameters": [{
8
                "$ref": "../parameters.json#/club_id_pp"
9
              }, {
10
                "name": "body",
11
                "in": "body",
12
                "description": "A JSON object containing informations about the new hold",
13
                "required": true,
14
                "schema": {
15
                  "type": "object",
16
                  "properties": {
17
                    "biblio_id": {
18
                      "description": "Internal biblio identifier",
19
                      "type": [ "integer", "null" ]
20
                    },
21
                    "item_id": {
22
                      "description": "Internal item identifier",
23
                      "type": [ "integer", "null" ]
24
                    },
25
                    "pickup_library_id": {
26
                      "description": "Internal library identifier for the pickup library",
27
                      "type": "string"
28
                    },
29
                    "expiration_date": {
30
                      "description": "Hold end date",
31
                      "type": ["string", "null"],
32
                      "format": "date"
33
                    },
34
                    "notes": {
35
                      "description": "Notes related to this hold",
36
                      "type": [ "string", "null" ]
37
                    },
38
                    "item_type": {
39
                      "description": "Limit hold on one itemtype (ignored for item-level holds)",
40
                      "type": [ "string", "null" ]
41
                    }
42
                  },
43
                  "required": [ "pickup_library_id" ]
44
                }
45
              }
46
            ],
47
            "consumes": ["application/json"],
48
            "produces": ["application/json"],
49
            "responses": {
50
              "201": {
51
                "description": "Created hold",
52
                "schema": {
53
                    "$ref": "../definitions/club_hold.json"
54
                }
55
              },
56
              "400": {
57
                "description": "Missing or wrong parameters",
58
                "schema": {
59
                  "$ref": "../definitions.json#/error"
60
                }
61
              },
62
              "401": {
63
                "description": "Authentication required",
64
                "schema": {
65
                  "$ref": "../definitions.json#/error"
66
                }
67
              },
68
              "403": {
69
                "description": "Hold not allowed",
70
                "schema": {
71
                  "$ref": "../definitions.json#/error"
72
                }
73
              },
74
              "404": {
75
                "description": "Hold not found",
76
                "schema": {
77
                  "$ref": "../definitions.json#/error"
78
                }
79
              },
80
              "500": {
81
                "description": "Internal server error",
82
                "schema": {
83
                  "$ref": "../definitions.json#/error"
84
                }
85
              },
86
              "503": {
87
                "description": "Under maintenance",
88
                "schema": {
89
                  "$ref": "../definitions.json#/error"
90
                }
91
              }
92
            },
93
            "x-koha-authorization": {
94
              "permissions": {
95
                "reserveforothers": "1"
96
              }
97
            }
98
        }
99
    }
100
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt (-2 / +1 lines)
Lines 88-94 Link Here
88
                    </div>
88
                    </div>
89
                [% END %]
89
                [% END %]
90
                <fieldset class="brief">
90
                <fieldset class="brief">
91
                    <label>Seach Patrons or clubs</label>
91
                    <label>Search Patrons or clubs</label>
92
                    <div id="circ_holds_select">
92
                    <div id="circ_holds_select">
93
                        <ul class="nav nav-tabs" role="tablist">
93
                        <ul class="nav nav-tabs" role="tablist">
94
                            <li role="presentation"><a href="#holds_patronsearch_pane" aria-controls="holds_patronsearch_pane" role="tab" data-toggle="tab">Patrons</a></li>
94
                            <li role="presentation"><a href="#holds_patronsearch_pane" aria-controls="holds_patronsearch_pane" role="tab" data-toggle="tab">Patrons</a></li>
95
- 

Return to bug 19618