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

(-)a/Koha/Exceptions/Item.pm (+18 lines)
Line 0 Link Here
1
package Koha::Exceptions::Item;
2
3
use Modern::Perl;
4
5
use Exception::Class (
6
7
    'Koha::Exceptions::Item' => {
8
        description => 'Something went wrong!',
9
    },
10
    'Koha::Exceptions::Item::NotFound' => {
11
        isa => 'Koha::Exceptions::Item',
12
        description => 'Item not found',
13
        fields => ['itemnumber']
14
    },
15
16
);
17
18
1;
(-)a/Koha/Exceptions/ItemType.pm (+18 lines)
Line 0 Link Here
1
package Koha::Exceptions::ItemType;
2
3
use Modern::Perl;
4
5
use Exception::Class (
6
7
    'Koha::Exceptions::ItemType' => {
8
        description => 'Something went wrong!',
9
    },
10
    'Koha::Exceptions::ItemType::NotFound' => {
11
        isa => 'Koha::Exceptions::ItemType',
12
        description => 'ItemType not found',
13
        fields => ['itemtype']
14
    },
15
16
);
17
18
1;
(-)a/Koha/Exceptions/Library.pm (+18 lines)
Line 0 Link Here
1
package Koha::Exceptions::Library;
2
3
use Modern::Perl;
4
5
use Exception::Class (
6
7
    'Koha::Exceptions::Library' => {
8
        description => 'Something went wrong!',
9
    },
10
    'Koha::Exceptions::Library::NotFound' => {
11
        isa => 'Koha::Exceptions::Library',
12
        description => 'Library not found',
13
        fields => ['branchcode']
14
    },
15
16
);
17
18
1;
(-)a/Koha/Exceptions/Patron.pm (+18 lines)
Line 0 Link Here
1
package Koha::Exceptions::Patron;
2
3
use Modern::Perl;
4
5
use Exception::Class (
6
7
    'Koha::Exceptions::Patron' => {
8
        description => 'Something went wrong!',
9
    },
10
    'Koha::Exceptions::Patron::NotFound' => {
11
        isa => 'Koha::Exceptions::Patron',
12
        description => 'Patron not found',
13
        fields => ['borrowernumber']
14
    },
15
16
);
17
18
1;
(-)a/Koha/Exceptions/Patron/Category.pm (+18 lines)
Line 0 Link Here
1
package Koha::Exceptions::Patron::Category;
2
3
use Modern::Perl;
4
5
use Exception::Class (
6
7
    'Koha::Exceptions::Patron::Category' => {
8
        description => 'Something went wrong!',
9
    },
10
    'Koha::Exceptions::Patron::Category::NotFound' => {
11
        isa => 'Koha::Exceptions::Patron::Category',
12
        description => 'Patron::Category not found',
13
        fields => ['categorycode']
14
    },
15
16
);
17
18
1;
(-)a/Koha/REST/V1/IssuingRule.pm (+237 lines)
Line 0 Link Here
1
package Koha::REST::V1::IssuingRule;
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::Circulation;
23
24
use Koha::IssuingRules;
25
use Koha::Items;
26
use Koha::ItemTypes;
27
use Koha::Libraries;
28
use Koha::Patron::Categories;
29
use Koha::Patrons;
30
31
use Koha::Exceptions;
32
use Koha::Exceptions::ItemType;
33
use Koha::Exceptions::Library;
34
use Koha::Exceptions::Patron;
35
use Koha::Exceptions::Patron::Category;
36
37
use Try::Tiny;
38
39
sub get_effective {
40
    my $c = shift->openapi->valid_input or return;
41
42
    return try {
43
        my $params = $c->req->query_params->to_hash;
44
45
        my ($categorycode, $itemtype, $branchcode);
46
        my $user      = $c->stash('koha.user');
47
        my $patron    = _find_patron($params);
48
        my $item      = _find_item($params);
49
        $categorycode = _find_categorycode($params, $patron);
50
        $itemtype     = _find_itemtype($params, $item);
51
        $branchcode   = _find_branchcode($params, $item, $patron, $user);
52
53
        my $rule = Koha::IssuingRules->get_effective_issuing_rule({
54
            categorycode => $categorycode,
55
            itemtype     => $itemtype,
56
            branchcode   => $branchcode,
57
        });
58
59
        return $c->render(status => 200, openapi => $rule);
60
    }
61
    catch {
62
        if ($_->isa('Koha::Exceptions::BadParameter')) {
63
            return $c->render(status => 400, openapi => { error => $_->error });
64
        }
65
        elsif ($_->isa('Koha::Exceptions::Item::NotFound')) {
66
            return $c->render(status => 404, openapi => { error => $_->error });
67
        }
68
        elsif ($_->isa('Koha::Exceptions::ItemType::NotFound')) {
69
            return $c->render(status => 404, openapi => { error => $_->error });
70
        }
71
        elsif ($_->isa('Koha::Exceptions::Library::NotFound')) {
72
            return $c->render(status => 404, openapi => { error => $_->error });
73
        }
74
        elsif ($_->isa('Koha::Exceptions::Patron::NotFound')) {
75
            return $c->render(status => 404, openapi => { error => $_->error });
76
        }
77
        elsif ($_->isa('Koha::Exceptions::Patron::Category::NotFound')) {
78
            return $c->render(status => 404, openapi => { error => $_->error });
79
        }
80
        else {
81
            return $c->render(status => 500, openapi => {
82
                error => 'Something went wrong, check the logs.'
83
            });
84
        }
85
    };
86
}
87
88
sub _find_branchcode {
89
    my ($params, $item, $patron, $loggedin_user) = @_;
90
91
    my $circcontrol = C4::Context->preference('CircControl');
92
    my $branchcode;
93
    if ($circcontrol eq 'PatronLibrary' && defined $patron
94
        && !length $params->{branchcode}) {
95
        $branchcode = C4::Circulation::_GetCircControlBranch(
96
            undef,
97
            $patron->unblessed
98
        );
99
    } elsif ($circcontrol eq 'ItemHomeLibrary' && defined $item
100
             && !length $params->{branchcode}) {
101
        $branchcode = C4::Circulation::_GetCircControlBranch(
102
            $item->unblessed,
103
            undef
104
        );
105
    } elsif ($circcontrol eq 'PickupLibrary' && !exists $params->{branchcode}) {
106
        # If CircControl == PickupLibrary, expect currently logged in branch
107
        # to be the homebranch of logged in user ONLY IF parameter branchcode
108
        # is not provided - if the parameter exists but is not defined, allow
109
        # the possibility to query with null branchcode
110
        $branchcode = $loggedin_user->branchcode;
111
    }
112
113
    return $branchcode if $branchcode;
114
115
    if (length $params->{branchcode}) {
116
        my $library = Koha::Libraries->find($params->{branchcode});
117
        unless ($library) {
118
            Koha::Exceptions::Library::NotFound->throw(
119
                error => 'Branchcode not found'
120
            );
121
        }
122
        $branchcode = $library->branchcode;
123
    }
124
125
    return $branchcode;
126
127
}
128
129
sub _find_categorycode {
130
    my ($params, $patron) = @_;
131
132
    my $categorycode;
133
    if (defined $patron && length $params->{categorycode}) {
134
        unless ($patron->categorycode eq $params->{categorycode}) {
135
            Koha::Exceptions::BadParameter->throw(
136
                error => "Patron's categorycode does not match given categorycode"
137
            );
138
        }
139
    }
140
141
    return $patron->categorycode if $patron;
142
143
    if (length $params->{categorycode}) {
144
        my $category = Koha::Patron::Categories->find($params->{categorycode});
145
        unless ($category) {
146
            Koha::Exceptions::Patron::Category::NotFound->throw(
147
                error => 'Categorycode not found'
148
            );
149
        }
150
        $categorycode = $category->categorycode;
151
    }
152
153
    return $categorycode;
154
}
155
156
sub _find_item {
157
    my ($params) = @_;
158
159
    my $item;
160
    if (length $params->{itemnumber} && length $params->{barcode}) {
161
        $item = Koha::Items->find({
162
            itemnumber  => $params->{itemnumber},
163
            barcode     => $params->{barcode},
164
        });
165
    } elsif (length $params->{itemnumber}) {
166
        $item = Koha::Items->find($params->{itemnumber});
167
    } elsif (length $params->{barcode}) {
168
        $item = Koha::Items->find({
169
            barcode => $params->{barcode}
170
        });
171
    }
172
173
    if ((length $params->{itemnumber} || length $params->{barcode})
174
        && !defined $item) {
175
        Koha::Exceptions::Item::NotFound->throw(
176
            error => 'Item not found'
177
        );
178
    }
179
180
    return $item;
181
}
182
183
sub _find_itemtype {
184
    my ($params, $item) = @_;
185
186
    my $itemtype;
187
    if (defined $item && length $params->{itemtype}) {
188
        unless ($item->effective_itemtype eq $params->{itemtype}) {
189
            Koha::Exceptions::BadParameter->throw(
190
                error => "Item's item type does not match given item type"
191
            );
192
        }
193
    }
194
195
    return $item->effective_itemtype if $item;
196
197
    if (length $params->{itemtype}) {
198
        my $itemtype_o = Koha::ItemTypes->find($params->{itemtype});
199
        unless ($itemtype_o) {
200
            Koha::Exceptions::ItemType::NotFound->throw(
201
                error => 'Item type not found'
202
            );
203
        }
204
        $itemtype = $itemtype_o->itemtype;
205
    }
206
207
    return $itemtype;
208
}
209
210
sub _find_patron {
211
    my ($params) = @_;
212
213
    my $patron;
214
    if (length $params->{borrowernumber} && length $params->{cardnumber}) {
215
        $patron = Koha::Patrons->find({
216
            borrowernumber => $params->{borrowernumber},
217
            cardnumber     => $params->{cardnumber},
218
        });
219
    } elsif (length $params->{borrowernumber}) {
220
        $patron = Koha::Patrons->find($params->{borrowernumber});
221
    } elsif (length $params->{cardnumber}) {
222
        $patron = Koha::Patrons->find({
223
            cardnumber => $params->{cardnumber}
224
        });
225
    }
226
227
    if ((length $params->{borrowernumber} || length $params->{cardnumber})
228
        && !defined $patron) {
229
        Koha::Exceptions::Patron::NotFound->throw(
230
            error => 'Patron not found'
231
        );
232
    }
233
234
    return $patron;
235
}
236
237
1;
(-)a/api/v1/swagger/definitions.json (+3 lines)
Lines 11-16 Link Here
11
  "holds": {
11
  "holds": {
12
    "$ref": "definitions/holds.json"
12
    "$ref": "definitions/holds.json"
13
  },
13
  },
14
  "issuingrule": {
15
    "$ref": "definitions/issuingrule.json"
16
  },
14
  "patron": {
17
  "patron": {
15
    "$ref": "definitions/patron.json"
18
    "$ref": "definitions/patron.json"
16
  }
19
  }
(-)a/api/v1/swagger/definitions/issuingrule.json (+144 lines)
Line 0 Link Here
1
{
2
  "type": "object",
3
  "properties": {
4
    "issuingrules_id": {
5
      "description": "internal issuing rule id",
6
      "type": "integer"
7
    },
8
    "categorycode": {
9
      "description": "patron category this rule is for (categories.categorycode)",
10
      "type": "string"
11
    },
12
    "itemtype": {
13
      "description": "item type this rule is for (itemtypes.itemtype)",
14
      "type": "string"
15
    },
16
    "restrictedtype": {
17
      "description": "not used? always NULL",
18
      "type": ["integer", "null"]
19
    },
20
    "rentaldiscount": {
21
      "description": "percent discount on the rental charge for this item",
22
      "type": ["number", "null"]
23
    },
24
    "reservecharge": {
25
      "description": "",
26
      "type": ["number", "null"]
27
    },
28
    "fine": {
29
      "description": "fine amount",
30
      "type": ["number", "null"]
31
    },
32
    "finedays": {
33
      "description": "suspension in days",
34
      "type": ["integer", "null"]
35
    },
36
    "maxsuspensiondays": {
37
      "description": "max suspension days",
38
      "type": ["integer", "null"]
39
    },
40
    "firstremind": {
41
      "description": "fine grace period",
42
      "type": ["integer", "null"]
43
    },
44
    "chargeperiod": {
45
      "description": "how often the fine amount is charged",
46
      "type": ["integer", "null"]
47
    },
48
    "chargeperiod_charge_at": {
49
      "description": "Should fine be given at the start ( 1 ) or the end ( 0 ) of the period",
50
      "type": "integer"
51
    },
52
    "accountsent": {
53
      "description": "not used? always NULL",
54
      "type": ["integer", "null"]
55
    },
56
    "chargename": {
57
      "description": "not used? always NULL",
58
      "type": ["string", "null"]
59
    },
60
    "maxissueqty": {
61
      "description": "total number of checkouts allowed",
62
      "type": ["integer", "null"]
63
    },
64
    "maxonsiteissueqty": {
65
      "description": "total number of on-site checkouts allowed",
66
      "type": ["integer", "null"]
67
    },
68
    "issuelength": {
69
      "description": "length of checkout in the unit set in issuingrules.lengthunit",
70
      "type": ["integer", "null"]
71
    },
72
    "lengthunit": {
73
      "description": "unit of checkout length (days, hours)",
74
      "type": ["string", "null"]
75
    },
76
    "hardduedate": {
77
      "description": "hard due date",
78
      "type": ["string", "null"],
79
      "format": "date"
80
    },
81
    "hardduedatecompare": {
82
      "description": "type of hard due date (1 = after, 0 = on, -1 = before)",
83
      "type": "integer"
84
    },
85
    "renewalsallowed": {
86
      "description": "how many renewals are allowed",
87
      "type": "integer"
88
    },
89
    "renewalperiod": {
90
      "description": "renewal period in the unit set in issuingrules.lengthunit",
91
      "type": ["integer", "null"]
92
    },
93
    "norenewalbefore": {
94
      "description": "no renewal allowed until X days or hours before due date.",
95
      "type": ["integer", "null"]
96
    },
97
    "auto_renew": {
98
      "description": "automatic renewal",
99
      "type": ["integer", "null"]
100
    },
101
    "no_auto_renewal_after": {
102
      "description": "no auto renewal allowed after X days or hours after the issue date",
103
      "type": ["integer", "null"]
104
    },
105
    "no_auto_renewal_after_hard_limit": {
106
      "description": "no auto renewal allowed after a given date",
107
      "type": ["string", "null"],
108
      "format": "date"
109
    },
110
    "reservesallowed": {
111
      "description": "how many holds are allowed",
112
      "type": "integer"
113
    },
114
    "holds_per_record": {
115
      "description": "How many holds a patron can have on a given bib",
116
      "type": "integer"
117
    },
118
    "branchcode": {
119
      "description": "the branch this rule is for (branches.branchcode)",
120
      "type": "string"
121
    },
122
    "overduefinescap": {
123
      "description": "the maximum amount of an overdue fine",
124
      "type": ["number", "null"]
125
    },
126
    "cap_fine_to_replacement_price": {
127
      "description": "cap the fine based on item's replacement price",
128
      "type": "integer"
129
    },
130
    "onshelfholds": {
131
      "description": "allow holds for items that are on shelf",
132
      "type": "integer"
133
    },
134
    "opacitemholds": {
135
      "description": "allow opac users to place specific items on hold",
136
      "type": "string"
137
    },
138
    "article_requests": {
139
      "description": "allow article requests to be placed,",
140
      "type": "string"
141
    }
142
  },
143
  "additionalProperties": false
144
}
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 11-16 Link Here
11
  "/holds/{reserve_id}": {
11
  "/holds/{reserve_id}": {
12
    "$ref": "paths/holds.json#/~1holds~1{reserve_id}"
12
    "$ref": "paths/holds.json#/~1holds~1{reserve_id}"
13
  },
13
  },
14
  "/issuingrules/effective": {
15
    "$ref": "paths/issuingrules.json#/~1issuingrules~1effective"
16
  },
14
  "/patrons": {
17
  "/patrons": {
15
    "$ref": "paths/patrons.json#/~1patrons"
18
    "$ref": "paths/patrons.json#/~1patrons"
16
  },
19
  },
(-)a/api/v1/swagger/paths/issuingrules.json (+98 lines)
Line 0 Link Here
1
{
2
  "/issuingrules/effective": {
3
    "get": {
4
      "x-mojo-to": "IssuingRule#get_effective",
5
      "operationId": "getEffectiveIssuingRule",
6
      "tags": ["issuingrules"],
7
      "parameters": [{
8
        "name": "branchcode",
9
        "in": "query",
10
        "description": "Target branchcode (internal library identifier). If parameter not given, assume logged in user's homebranch. Give empty value (e.g. issuingrules/effective?branchcode=&categorycode=ASD) to query issuing rules targetting all libraries.",
11
        "required": false,
12
        "type": "string"
13
      }, {
14
        "name": "itemtype",
15
        "in": "query",
16
        "description": "Target itemtype (internal item type identifier)",
17
        "required": false,
18
        "type": "string"
19
      }, {
20
        "name": "categorycode",
21
        "in": "query",
22
        "description": "Target patron categorycode (internal category identifier)",
23
        "required": false,
24
        "type": "string"
25
      }, {
26
        "name": "barcode",
27
        "in": "query",
28
        "description": "Target item (barcode)",
29
        "required": false,
30
        "type": "string"
31
      }, {
32
        "name": "itemnumber",
33
        "in": "query",
34
        "description": "Target item (internal item identifier)",
35
        "required": false,
36
        "type": "integer"
37
      }, {
38
        "name": "borrowernumber",
39
        "in": "query",
40
        "description": "Target patron (internal patron identifier)",
41
        "required": false,
42
        "type": "integer"
43
      }, {
44
        "name": "cardnumber",
45
        "in": "query",
46
        "description": "Target patron card number",
47
        "required": false,
48
        "type": "string"
49
      }],
50
      "produces": ["application/json"],
51
      "responses": {
52
        "200": {
53
          "description": "Effective issuing rule",
54
          "schema": { "$ref": "../definitions.json#/issuingrule" }
55
        },
56
        "400": {
57
          "description": "Bad parameter",
58
          "schema": { "$ref": "../definitions.json#/error" }
59
        },
60
        "401": {
61
          "description": "Authentication required",
62
          "schema": {
63
            "$ref": "../definitions.json#/error"
64
          }
65
        },
66
        "403": {
67
          "description": "Access forbidden",
68
          "schema": {
69
            "$ref": "../definitions.json#/error"
70
          }
71
        },
72
        "404": {
73
          "description": "One or more of the given parameters not found",
74
          "schema": {
75
            "$ref": "../definitions.json#/error"
76
          }
77
        },
78
        "500": {
79
          "description": "Internal server error",
80
          "schema": {
81
            "$ref": "../definitions.json#/error"
82
          }
83
        },
84
        "503": {
85
          "description": "Under maintenance",
86
          "schema": {
87
            "$ref": "../definitions.json#/error"
88
          }
89
        }
90
      },
91
      "x-koha-authorization": {
92
        "permissions": {
93
          "parameters": "manage_circ_rules"
94
        }
95
      }
96
    }
97
  }
98
}
(-)a/t/db_dependent/api/v1/cities.t (-1 / +1 lines)
Lines 33-39 my $builder = t::lib::TestBuilder->new; Link Here
33
33
34
# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
34
# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
35
# this affects the other REST api tests
35
# this affects the other REST api tests
36
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
36
#t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
37
37
38
my $remote_address = '127.0.0.1';
38
my $remote_address = '127.0.0.1';
39
my $t              = Test::Mojo->new('Koha::REST::V1');
39
my $t              = Test::Mojo->new('Koha::REST::V1');
(-)a/t/db_dependent/api/v1/issuingrules.t (-1 / +385 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
20
use Test::More tests => 1;
21
use Test::Mojo;
22
use Test::Warn;
23
24
use t::lib::TestBuilder;
25
use t::lib::Mocks;
26
27
use C4::Auth;
28
29
use Koha::Database;
30
use Koha::IssuingRules;
31
32
my $schema  = Koha::Database->new->schema;
33
my $builder = t::lib::TestBuilder->new;
34
35
# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
36
# this affects the other REST api tests
37
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
38
39
my $remote_address = '127.0.0.1';
40
my $t              = Test::Mojo->new('Koha::REST::V1');
41
42
subtest 'get_effective() tests' => sub {
43
44
    plan tests => 27;
45
46
    $schema->storage->txn_begin;
47
48
    # Empty current issuign rules and create two random issuing rules
49
    Koha::IssuingRules->search->delete;
50
51
    my $context1 = create_test_context();
52
    my $context2 = create_test_context();
53
    my $context3 = create_test_context();
54
55
    create_issuing_rule({
56
        categorycode => '*',
57
        itemtype => '*',
58
        branchcode => '*'
59
    });
60
    create_issuing_rule();
61
    create_issuing_rule();
62
    create_issuing_rule({
63
        categorycode => $context1->{patron}->{categorycode},
64
        itemtype => $context1->{item}->{itype},
65
        branchcode => '*'
66
    });
67
    create_issuing_rule({
68
        categorycode => $context2->{patron}->{categorycode},
69
        itemtype => $context2->{item}->{itype},
70
        branchcode => $context2->{patron}->{branchcode}
71
    });
72
    create_issuing_rule({
73
        categorycode => $context3->{patron}->{categorycode},
74
        itemtype => $context3->{item}->{itype},
75
        branchcode => $context3->{item}->{homebranch}
76
    });
77
    create_issuing_rule({
78
        categorycode => $context3->{patron}->{categorycode},
79
        itemtype => $context3->{item}->{itype},
80
        branchcode => $context3->{item}->{holdingbranch}
81
    });
82
    my ( $borrowernumber, $session_id ) =
83
      create_user_and_session( { authorized => 0 } );
84
85
    my $tx = $t->ua->build_tx( GET => '/api/v1/issuingrules/effective' );
86
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
87
    $t->request_ok($tx)
88
      ->status_is(401);
89
90
    $tx = $t->ua->build_tx( GET => '/api/v1/issuingrules/effective' );
91
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
92
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
93
    $t->request_ok($tx)
94
      ->status_is(403);
95
96
    ( $borrowernumber, $session_id ) =
97
      create_user_and_session( { authorized => 1 } );
98
99
    my $path = '/api/v1/issuingrules/effective';
100
    my $params = {};
101
102
    $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
103
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
104
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
105
    $t->request_ok($tx)
106
      ->status_is(200)
107
      ->json_is('/branchcode' => '*')
108
      ->json_is('/categorycode' => '*')
109
      ->json_is('/itemtype' => '*');
110
111
    $params = {
112
        branchcode => '',
113
        categorycode => '',
114
        itemtype => ''
115
    };
116
    $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
117
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
118
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
119
    $t->request_ok($tx)
120
      ->status_is(200)
121
      ->json_is('/branchcode' => '*')
122
      ->json_is('/categorycode' => '*')
123
      ->json_is('/itemtype' => '*');
124
125
    $params = { itemtype => $context1->{item}->{itype} };
126
    $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
127
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
128
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
129
    $t->request_ok($tx)
130
      ->status_is(200)
131
      ->json_is('/branchcode' => '*')
132
      ->json_is('/categorycode' => '*')
133
      ->json_is('/itemtype' => '*');
134
135
    $params = {
136
        categorycode => $context1->{patron}->{categorycode},
137
        itemtype => $context1->{item}->{itype}
138
    };
139
    $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
140
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
141
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
142
    $t->request_ok($tx)
143
      ->status_is(200)
144
      ->json_is('/branchcode' => '*')
145
      ->json_is('/categorycode' => $context1->{patron}->{categorycode})
146
      ->json_is('/itemtype' => $context1->{item}->{itype});
147
148
    subtest 'CircControl = ItemHomeLibrary' => sub {
149
        plan tests => 15;
150
151
        t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary');
152
        t::lib::Mocks::mock_preference('HomeOrHoldingBranch', 'homebranch');
153
154
        create_issuing_rule({
155
            categorycode => $context3->{patron}->{categorycode},
156
            itemtype => $context3->{item}->{itype},
157
            branchcode => '*'
158
        });
159
160
        $params = {
161
            cardnumber => $context3->{patron}->{cardnumber},
162
            barcode => $context3->{item}->{barcode}
163
        };
164
        # Test exact match, item's homebranch
165
        $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
166
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
167
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
168
        $t->request_ok($tx)
169
          ->status_is(200)
170
          ->json_is('/branchcode' => $context3->{item}->{homebranch})
171
          ->json_is('/categorycode' => $context3->{patron}->{categorycode})
172
          ->json_is('/itemtype' => $context3->{item}->{itype});
173
174
        t::lib::Mocks::mock_preference('HomeOrHoldingBranch', 'holdingbranch');
175
        $params = {
176
            cardnumber => $context3->{patron}->{cardnumber},
177
            barcode => $context3->{item}->{barcode}
178
        };
179
        # Test exact match, holdingbranch
180
        $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
181
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
182
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
183
        $t->request_ok($tx)
184
          ->status_is(200)
185
          ->json_is('/branchcode' => $context3->{item}->{holdingbranch})
186
          ->json_is('/categorycode' => $context3->{patron}->{categorycode})
187
          ->json_is('/itemtype' => $context3->{item}->{itype});
188
189
        # Test custom branch
190
        $params->{'branchcode'} = $context2->{patron}->{branchcode};
191
        $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
192
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
193
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
194
        $t->request_ok($tx)
195
          ->status_is(200)
196
          ->json_is('/branchcode' => '*')
197
          ->json_is('/categorycode' => $context3->{patron}->{categorycode})
198
          ->json_is('/itemtype' => $context3->{item}->{itype});
199
    };
200
201
    subtest 'CircControl = PatronLibrary' => sub {
202
        plan tests => 10;
203
204
        t::lib::Mocks::mock_preference('CircControl', 'PatronLibrary');
205
        create_issuing_rule({
206
            categorycode => $context2->{patron}->{categorycode},
207
            itemtype => $context2->{item}->{itype},
208
            branchcode => '*'
209
        });
210
        $params = {
211
            itemnumber => $context2->{item}->{itemnumber},
212
            borrowernumber => $context2->{patron}->{borrowernumber}
213
        };
214
        # Test exact match
215
        $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
216
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
217
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
218
        $t->request_ok($tx)
219
          ->status_is(200)
220
          ->json_is('/branchcode' => $context2->{patron}->{branchcode})
221
          ->json_is('/categorycode' => $context2->{patron}->{categorycode})
222
          ->json_is('/itemtype' => $context2->{item}->{itype});
223
224
        # Test custom branchcode
225
        $params->{'branchcode'} = $context3->{patron}->{branchcode};
226
        $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
227
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
228
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
229
        $t->request_ok($tx)
230
          ->status_is(200)
231
          ->json_is('/branchcode' => '*')
232
          ->json_is('/categorycode' => $context2->{patron}->{categorycode})
233
          ->json_is('/itemtype' => $context2->{item}->{itype});
234
    };
235
236
    subtest 'CircControl = PickupLibrary' => sub {
237
        plan tests => 15;
238
239
        t::lib::Mocks::mock_preference('CircControl', 'PickupLibrary');
240
        my $patron = Koha::Patrons->find($borrowernumber);
241
        create_issuing_rule({
242
            categorycode => $context2->{patron}->{categorycode},
243
            itemtype => $context2->{item}->{itype},
244
            branchcode => $patron->branchcode
245
        });
246
        $params = {
247
            cardnumber => $context2->{patron}->{cardnumber},
248
            barcode => $context2->{item}->{barcode}
249
        };
250
        $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
251
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
252
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
253
        $t->request_ok($tx)
254
          ->status_is(200)
255
          ->json_is('/branchcode' => $patron->branchcode)
256
          ->json_is('/categorycode' => $context2->{patron}->{categorycode})
257
          ->json_is('/itemtype' => $context2->{item}->{itype});
258
259
        $params = {
260
            cardnumber => $context2->{patron}->{cardnumber},
261
            barcode => $context2->{item}->{barcode},
262
            branchcode => '',
263
        };
264
        # Test all branches
265
        $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
266
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
267
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
268
        $t->request_ok($tx)
269
          ->status_is(200)
270
          ->json_is('/branchcode' => '*')
271
          ->json_is('/categorycode' => $context2->{patron}->{categorycode})
272
          ->json_is('/itemtype' => $context2->{item}->{itype});
273
274
        # Test another branch, should still return rule for all branches because
275
        # there is no exact match to that branchcode
276
        $params->{branchcode} = $context1->{patron}->{branchcode};
277
        $tx = $t->ua->build_tx( GET => _query_params($path, $params) );
278
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
279
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
280
        $t->request_ok($tx)
281
          ->status_is(200)
282
          ->json_is('/branchcode' => '*')
283
          ->json_is('/categorycode' => $context2->{patron}->{categorycode})
284
          ->json_is('/itemtype' => $context2->{item}->{itype});
285
    };
286
287
    $schema->storage->txn_rollback;
288
};
289
290
sub create_test_context {
291
    my $itemtype = $builder->build({ source => 'Itemtype'});
292
    my $library = $builder->build({ source => 'Branch' });
293
    my $library2 = $builder->build({ source => 'Branch' });
294
    my $biblio = $builder->build({ source => 'Biblio' });
295
    my $biblioitem = $builder->build({
296
        source => 'Biblioitem',
297
        value => {
298
            biblionumber => $biblio->{biblionumber},
299
            itemtype => $itemtype->{itemtype},
300
        }
301
    });
302
    my $item = $builder->build({
303
        source => 'Item',
304
        value => {
305
            biblionumber => $biblio->{biblionumber},
306
            biblioitemnumber => $biblioitem->{biblioitemnumber},
307
            itype => $itemtype->{itemtype},
308
            homebranch => $library->{branchcode},
309
            holdingbranch => $library2->{branchcode},
310
        }
311
    });
312
    my $category = $builder->build({ source => 'Category' });
313
    my $patron = $builder->build({
314
        source => 'Borrower',
315
        value => {
316
            categorycode => $category->{categorycode}
317
        }
318
    });
319
320
    return {
321
        biblio => $biblio,
322
        biblioitem => $biblioitem,
323
        category => $category,
324
        item => $item,
325
        itemtype => $itemtype,
326
        library => $library,
327
        library_two => $library2,
328
        patron => $patron,
329
    };
330
}
331
332
sub create_issuing_rule {
333
    my ($params) = @_;
334
335
    my $rule = $builder->build({
336
        source => 'Issuingrule',
337
        value => $params
338
    });
339
340
    return $rule;
341
}
342
sub create_user_and_session {
343
344
    my $args  = shift;
345
    my $flags = ( $args->{authorized} ) ? $args->{authorized} : 0;
346
    my $dbh   = C4::Context->dbh;
347
348
    my $user = $builder->build(
349
        {
350
            source => 'Borrower',
351
            value  => {
352
                flags => $flags,
353
            }
354
        }
355
    );
356
357
    # Create a session for the authorized user
358
    my $session = C4::Auth::get_session('');
359
    $session->param( 'number',   $user->{borrowernumber} );
360
    $session->param( 'id',       $user->{userid} );
361
    $session->param( 'ip',       '127.0.0.1' );
362
    $session->param( 'lasttime', time() );
363
    $session->flush;
364
365
    if ( $args->{authorized} ) {
366
        $dbh->do( "
367
            INSERT INTO user_permissions (borrowernumber,module_bit,code)
368
            VALUES (?,3,'manage_circ_rules')", undef,
369
            $user->{borrowernumber} );
370
    }
371
372
    return ( $user->{borrowernumber}, $session->id );
373
}
374
375
sub _query_params {
376
    my ($path, $params) = @_;
377
378
    $path .= '?';
379
    foreach my $param (keys %$params) {
380
        $path .= $param.'='.$params->{$param}.'&';
381
    }
382
    $path =~ s/\&$//;
383
    return $path;
384
}
385
1;

Return to bug 19037