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

(-)a/Koha/CirculationRules.pm (-5 / +187 lines)
Lines 39-161 Koha::CirculationRules - Koha CirculationRule Object set class Link Here
39
39
40
=head3 rule_kinds
40
=head3 rule_kinds
41
41
42
This structure describes the possible rules that may be set, and what scopes they can be set at.
42
This structure describes the possible rules that may be set, what scopes they can be set at and any
43
default values.
43
44
44
Any attempt to set a rule with a nonsensical scope (for instance, setting the C<patron_maxissueqty> for a branchcode and itemtype), is an error.
45
Any attempt to set a rule with a nonsensical scope (for instance, setting the C<patron_maxissueqty>
46
for a branchcode and itemtype), is an error.
47
48
These default values correspond to what would be stored in the database if nothing was changed on
49
the old circulation rules editor; this is a combination of the default values in the template and
50
the issuingrules table.
45
51
46
=cut
52
=cut
47
53
48
our $RULE_KINDS = {
54
our $RULE_KINDS = {
49
    refund => {
55
    refund => {
50
        scope => [ 'branchcode' ],
56
        scope => [ 'branchcode' ],
57
        default_value => 1,
58
        group => 'fines',
59
        description => "Refund lost item fee",
60
        choices => [
61
            [ 0, "No" ],
62
            [ 1, "Yes" ],
63
        ],
51
    },
64
    },
52
65
53
    patron_maxissueqty => {
66
    patron_maxissueqty => {
54
        scope => [ 'branchcode', 'categorycode' ],
67
        scope => [ 'branchcode', 'categorycode' ],
68
        default_value => "",
69
        group => 'circulation',
70
        description => "Total current checkouts allowed",
55
    },
71
    },
56
    patron_maxonsiteissueqty => {
72
    patron_maxonsiteissueqty => {
57
        scope => [ 'branchcode', 'categorycode' ],
73
        scope => [ 'branchcode', 'categorycode' ],
74
        default_value => "",
75
        group => 'circulation',
76
        description => "Total current on-site checkouts allowed",
58
    },
77
    },
59
    max_holds => {
78
    max_holds => {
60
        scope => [ 'branchcode', 'categorycode' ],
79
        scope => [ 'branchcode', 'categorycode' ],
80
        default_value => "",
81
        group => 'holds',
82
        description => "Maximum total holds allowed",
61
    },
83
    },
62
84
63
    holdallowed => {
85
    holdallowed => {
64
        scope => [ 'branchcode', 'itemtype' ],
86
        scope => [ 'branchcode', 'itemtype' ],
65
        can_be_blank => 0,
87
        can_be_blank => 0,
88
        default_value => 2,
89
        group => 'holds',
90
        description => "Hold policy",
91
        choices => [
92
            [ 2, "From any library" ],
93
            [ 1, "From home library" ],
94
            [ 0, "No holds allowed" ],
95
        ],
66
    },
96
    },
67
    hold_fulfillment_policy => {
97
    hold_fulfillment_policy => {
68
        scope => [ 'branchcode', 'itemtype' ],
98
        scope => [ 'branchcode', 'itemtype' ],
69
        can_be_blank => 0,
99
        can_be_blank => 0,
100
        default_value => 'any',
101
        group => 'holds',
102
        description => "Hold pickup library match",
103
        choices => [
104
            [ "any", "any library" ],
105
            [ "homebranch", "item's home library" ],
106
            [ "holdingbranch", "item's holding library" ],
107
        ],
70
    },
108
    },
71
    returnbranch => {
109
    returnbranch => {
72
        scope => [ 'branchcode', 'itemtype' ],
110
        scope => [ 'branchcode', 'itemtype' ],
73
        can_be_blank => 0,
111
        can_be_blank => 0,
112
        default_value => 'homebranch',
113
        group => 'circulation',
114
        description => "Return policy",
115
        choices => [
116
            [ "homebranch", "Item returns home" ],
117
            [ "holdingbranch", "Item returns to issuing library" ],
118
            [ "noreturn", "Item floats" ],
119
        ],
74
    },
120
    },
75
121
76
    article_requests => {
122
    article_requests => {
77
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
123
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
124
        default_value => 'no',
125
        group => 'circulation',
126
        description => "Article requests",
127
        choices => [
128
            [ "no", "No" ],
129
            [ "yes", "Yes" ],
130
            [ "bib_only", "Record only" ],
131
            [ "item_only", "Item only" ],
132
        ],
78
    },
133
    },
79
    auto_renew => {
134
    auto_renew => {
80
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
135
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
136
        default_value => 0,
137
        group => 'circulation',
138
        description => "Automatic renewal",
139
        choices => [
140
            [ 0, "No" ],
141
            [ 1, "Yes" ],
142
        ],
81
    },
143
    },
82
    cap_fine_to_replacement_price => {
144
    cap_fine_to_replacement_price => {
83
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
145
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
146
        default_value => 0,
147
        group => 'fines',
148
        description => "Cap fine at replacement price",
149
        choices => [
150
            [ 0, "No" ],
151
            [ 1, "Yes" ],
152
        ],
84
    },
153
    },
85
    chargeperiod => {
154
    chargeperiod => {
86
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
155
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
156
        default_value => 0,
157
        group => 'fines',
158
        description => "Fine charging interval",
87
    },
159
    },
88
    chargeperiod_charge_at => {
160
    chargeperiod_charge_at => {
89
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
161
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
162
        default_value => 0,
163
        group => 'circulation',
164
        description => "When to charge",
165
        choices => [
166
            [ 0, "End of interval" ],
167
            [ 1, "Start of interval" ],
168
        ],
90
    },
169
    },
91
    fine => {
170
    fine => {
92
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
171
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
172
        default_value => 0,
173
        group => 'fines',
174
        description => "Fine amount",
93
    },
175
    },
94
    finedays => {
176
    finedays => {
95
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
177
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
178
        default_value => 0,
179
        group => 'fines',
180
        description => "Suspension in days",
96
    },
181
    },
97
    firstremind => {
182
    firstremind => {
98
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
183
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
184
        default_value => 0,
185
        group => 'fines',
186
        description => "Fine grace period",
99
    },
187
    },
100
    hardduedate => {
188
    hardduedate => {
101
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
189
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
190
        default_value => "",
191
        group => 'circulation',
192
        description => "Hard due date",
193
        type => "date",
102
    },
194
    },
103
    hardduedatecompare => {
195
    hardduedatecompare => {
104
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
196
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
197
        default_value => -1,
198
        group => 'circulation',
199
        description => "Hard due date type",
200
        choices => [
201
            [ -1, "Before" ],
202
            [ 0, "Exactly on" ],
203
            [ 1, "After" ],
204
        ],
105
    },
205
    },
106
    holds_per_day => {
206
    holds_per_day => {
107
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
207
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
208
        group => 'holds',
108
    },
209
    },
109
    holds_per_record => {
210
    holds_per_record => {
110
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
211
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
212
        default_value => 1,
213
        group => 'holds',
214
        description => "Holds per record (count)",
111
    },
215
    },
112
    issuelength => {
216
    issuelength => {
113
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
217
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
218
        default_value => 0,
219
        group => 'circulation',
220
        description => "Loan period",
114
    },
221
    },
115
    daysmode => {
222
    daysmode => {
116
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
223
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
117
    },
224
    },
118
    lengthunit => {
225
    lengthunit => {
119
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
226
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
227
        default_value => 'days',
228
        group => 'circulation',
229
        description => "Loan period unit",
120
    },
230
    },
121
    maxissueqty => {
231
    patron_maxissueqty => {
122
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
232
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
233
        default_value => "",
234
        group => 'circulation',
235
        description => "Current checkouts allowed",
123
    },
236
    },
124
    maxonsiteissueqty => {
237
    patron_maxonsiteissueqty => {
125
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
238
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
239
        default_value => "",
240
        group => 'circulation',
241
        description => "Current on-site checkouts allowed",
126
    },
242
    },
127
    maxsuspensiondays => {
243
    maxsuspensiondays => {
128
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
244
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
245
        default_value => "",
246
        group => 'fines',
247
        description => "Max. suspension duration (days)",
129
    },
248
    },
130
    no_auto_renewal_after => {
249
    no_auto_renewal_after => {
131
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
250
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
251
        default_value => "",
252
        group => 'circulation',
253
        description => "No automatic renewal after",
132
    },
254
    },
133
    no_auto_renewal_after_hard_limit => {
255
    no_auto_renewal_after_hard_limit => {
134
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
256
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
257
        default_value => "",
258
        group => 'circulation',
259
        description => "No automatic renewal after (hard limit)",
260
        type => "date",
135
    },
261
    },
136
    norenewalbefore => {
262
    norenewalbefore => {
137
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
263
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
264
        default_value => "",
265
        group => 'circulation',
266
        description => "No renewal before",
138
    },
267
    },
139
    onshelfholds => {
268
    onshelfholds => {
140
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
269
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
270
        default_value => 0,
271
        group => 'holds',
272
        description => "On shelf holds allowed",
273
        choices => [
274
            [ 1, "Yes" ],
275
            [ 0, "If any unavailable" ],
276
            [ 2, "If all unavailable" ],
277
        ],
141
    },
278
    },
142
    opacitemholds => {
279
    opacitemholds => {
143
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
280
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
281
        default_value => "N",
282
        group => 'holds',
283
        description => "Item-level holds",
284
        choices => [
285
            [ "N", "Don't allow" ],
286
            [ "Y", "Allow" ],
287
            [ "F", "Force" ],
288
        ],
144
    },
289
    },
145
    overduefinescap => {
290
    overduefinescap => {
146
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
291
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
292
        default_value => "",
293
        group => 'fines',
294
        description => "Overdue fines cap (amount)",
147
    },
295
    },
148
    renewalperiod => {
296
    renewalperiod => {
149
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
297
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
298
        default_value => 0,
299
        group => 'circulation',
300
        description => "Renewal period",
150
    },
301
    },
151
    renewalsallowed => {
302
    renewalsallowed => {
152
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
303
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
304
        default_value => 0,
305
        group => 'circulation',
306
        description => "Renewals allowed (count)",
153
    },
307
    },
154
    rentaldiscount => {
308
    rentaldiscount => {
155
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
309
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
310
        default_value => 0,
311
        group => 'circulation',
312
        description => "Rental discount (%)",
156
    },
313
    },
157
    reservesallowed => {
314
    reservesallowed => {
158
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
315
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
316
        default_value => 0,
317
        group => 'holds',
318
        description => "Holds allowed (count)",
159
    },
319
    },
160
    suspension_chargeperiod => {
320
    suspension_chargeperiod => {
161
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
321
        scope => [ 'branchcode', 'categorycode', 'itemtype' ],
Lines 249-254 sub get_effective_rules { Link Here
249
409
250
=head3 set_rule
410
=head3 set_rule
251
411
412
=over 4
413
Koha::CirculationRules->set_rule( {
414
    branchcode => $branchcode,
415
    [ categorycode => $categorycode, ]
416
    [ itemtype => $itemtype, ]
417
    rule_name => $rule_name,
418
    rule_value => $rule_value,
419
    [ allow_null_out_of_scope => 1, ]
420
} )
421
=back
422
423
Sets a single circulation rule.
424
425
It is an error to specify a C<categorycode> or C<itemtype> unless the given C<rule_name> can be set
426
at that scope; i.e., you cannot set a C<patron_maxissueqty> for an itemtype.
427
428
If C<allow_null_out_of_scope> is passed, and any excess C<categorycode> or C<itemtype> is set to C<undef>,
429
they are ignored.
430
252
=cut
431
=cut
253
432
254
sub set_rule {
433
sub set_rule {
Lines 272-278 sub set_rule { Link Here
272
                unless exists $params->{$scope_level};
451
                unless exists $params->{$scope_level};
273
        } else {
452
        } else {
274
            croak "set_rule cannot set '$params->{rule_name}' for a '$scope_level'!"
453
            croak "set_rule cannot set '$params->{rule_name}' for a '$scope_level'!"
275
                if exists $params->{$scope_level};
454
                unless !exists $params->{$scope_level} || (
455
                    $params->{allow_null_out_of_scope} &&
456
                    !defined $params->{$scope_level}
457
                );
276
        }
458
        }
277
    }
459
    }
278
460
(-)a/Koha/Exceptions/Authorization.pm (+3 lines)
Lines 12-17 use Exception::Class ( Link Here
12
        description => 'Unauthorized',
12
        description => 'Unauthorized',
13
        fields => ['required_permissions']
13
        fields => ['required_permissions']
14
    },
14
    },
15
    'Koha::Exceptions::Authorization::Restricted' => {
16
        description => 'User can only modify settings for their branch'
17
    },
15
18
16
);
19
);
17
20
(-)a/Koha/REST/V1/CirculationRules.pm (+53 lines)
Lines 19-25 use Modern::Perl; Link Here
19
19
20
use Mojo::Base 'Mojolicious::Controller';
20
use Mojo::Base 'Mojolicious::Controller';
21
21
22
use C4::Auth;
23
use C4::Context;
24
22
use Koha::CirculationRules;
25
use Koha::CirculationRules;
26
use Koha::Database;
27
use Koha::Exceptions::Authorization;
23
28
24
use Try::Tiny;
29
use Try::Tiny;
25
30
Lines 42-45 sub get_kinds { Link Here
42
    );
47
    );
43
}
48
}
44
49
50
sub get_rules {
51
    my $c = shift->openapi->valid_input or return;
52
53
    return $c->render(
54
        status => 200,
55
        openapi => [ Koha::CirculationRules->search ],
56
    );
57
}
58
59
sub save_rules {
60
    my $c = shift->openapi->valid_input or return;
61
62
    my $schema = Koha::Database->new->schema;
63
64
    my $uid = $c->stash( 'koha.user' )->userid;
65
    my $restricted_to_library = $uid && haspermission( $uid, { parameters => 'manage_circ_rules_from_any_libraries' } ) ? "" : $c->stash( 'koha.user' )->branchcode;
66
67
    return try {
68
        my $rules = $c->req->json;
69
70
        $schema->storage->txn_do( sub {
71
            foreach my $rule ( @$rules ) {
72
                if ( $restricted_to_library && ( !$rule->{branchcode} || $rule->{branchcode} ne $restricted_to_library ) ) {
73
                    Koha::Exceptions::Authorization::Restricted->throw(
74
                        error => 'User can only modify settings for their branch.'
75
                    );
76
                }
77
78
                Koha::CirculationRules->set_rule( { %$rule, allow_null_out_of_scope => 1 } );
79
            }
80
        } );
81
82
        return $c->render( status => 200, openapi => "" );
83
    }
84
    catch {
85
        if ( $_->isa('Koha::Exceptions::Authorization::Restricted') ) {
86
            return $c->render( status  => 403,
87
                               openapi => { error => $_->message } );
88
        } else {
89
            warn $_;
90
91
            return $c->render( status => 500,
92
                openapi => { error => "Something went wrong, check the logs."} );
93
        }
94
    };
95
96
}
97
45
1;
98
1;
(-)a/api/v1/swagger/definitions.json (+3 lines)
Lines 5-10 Link Here
5
  "basket": {
5
  "basket": {
6
    "$ref": "definitions/basket.json"
6
    "$ref": "definitions/basket.json"
7
  },
7
  },
8
  "circ-rule": {
9
    "$ref": "definitions/circ-rule.json"
10
  },
8
  "circ-rule-kind": {
11
  "circ-rule-kind": {
9
    "$ref": "definitions/circ-rule-kind.json"
12
    "$ref": "definitions/circ-rule-kind.json"
10
  },
13
  },
(-)a/api/v1/swagger/definitions/circ-rule-kind.json (+24 lines)
Lines 8-13 Link Here
8
        "type": "string",
8
        "type": "string",
9
        "enum": [ "branchcode", "categorycode", "itemtype" ]
9
        "enum": [ "branchcode", "categorycode", "itemtype" ]
10
      }
10
      }
11
    },
12
    "default_value": {
13
      "description": "effective default if no rules are set",
14
      "type": [ "string", "null" ]
15
    },
16
    "can_be_blank": {
17
      "description": "if rule can be blank",
18
      "type": [ "boolean", "null"]
19
    },
20
    "group": {
21
      "description": "which group this rule belongs to",
22
      "type": "string"
23
    },
24
    "description": {
25
      "description": "text describing this rule",
26
      "type": "string"
27
    },
28
    "choices": {
29
      "description": "predefined set of values this rule can be",
30
      "type": "array"
31
    },
32
    "type": {
33
      "description": "type of rule data",
34
      "type": "string"
11
    }
35
    }
12
  },
36
  },
13
  "additionalProperties": false,
37
  "additionalProperties": false,
(-)a/api/v1/swagger/definitions/circ-rule.json (+31 lines)
Line 0 Link Here
1
{
2
  "type": "object",
3
  "properties": {
4
    "id": {
5
      "description": "internal identifier of this rule",
6
      "type": "integer"
7
    },
8
    "branchcode": {
9
      "type": [ "string", "null" ],
10
      "description": "branch that this rule applies to"
11
    },
12
    "categorycode": {
13
      "type": [ "string", "null" ],
14
      "description": "category that this rule applies to"
15
    },
16
    "itemtype": {
17
      "type": [ "string", "null" ],
18
      "description": "itemtype that this rule applies to"
19
    },
20
    "rule_name": {
21
      "type": "string",
22
      "description": "name of the kind of this rule"
23
    },
24
    "rule_value": {
25
      "type": [ "string", "null" ],
26
      "description": "value of the rule"
27
    }
28
  },
29
  "additionalProperties": false,
30
  "required": [ "branchcode", "rule_name", "rule_value" ]
31
}
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 26-31 Link Here
26
  "/checkouts/{checkout_id}/renewal": {
26
  "/checkouts/{checkout_id}/renewal": {
27
    "$ref": "paths/checkouts.json#/~1checkouts~1{checkout_id}~1renewal"
27
    "$ref": "paths/checkouts.json#/~1checkouts~1{checkout_id}~1renewal"
28
  },
28
  },
29
  "/circulation-rules": {
30
    "$ref": "paths/circulation-rules.json#/~1circulation-rules"
31
  },
29
  "/circulation-rules/kinds": {
32
  "/circulation-rules/kinds": {
30
    "$ref": "paths/circulation-rules.json#/~1circulation-rules~1kinds"
33
    "$ref": "paths/circulation-rules.json#/~1circulation-rules~1kinds"
31
  },
34
  },
(-)a/api/v1/swagger/paths/circulation-rules.json (-1 / +131 lines)
Lines 1-9 Link Here
1
{
1
{
2
  "/circulation-rules": {
3
    "get": {
4
      "x-mojo-to": "CirculationRules#get_rules",
5
      "operationId": "getCirculationRules",
6
      "tags": ["circulation-rules"],
7
      "produces": [
8
        "application/json"
9
      ],
10
      "responses": {
11
        "200": {
12
          "description": "A list of defined rules",
13
          "schema": {
14
            "type": "array",
15
            "items": {
16
              "$ref": "../definitions.json#/circ-rule"
17
            }
18
          }
19
        },
20
        "400": {
21
          "description": "Bad request",
22
          "schema": {
23
            "$ref": "../definitions.json#/error"
24
          }
25
        },
26
        "403": {
27
          "description": "Access forbidden",
28
          "schema": {
29
            "$ref": "../definitions.json#/error"
30
          }
31
        },
32
        "500": {
33
          "description": "Internal error",
34
          "schema": {
35
            "$ref": "../definitions.json#/error"
36
          }
37
        },
38
        "503": {
39
          "description": "Under maintenance",
40
          "schema": {
41
            "$ref": "../definitions.json#/error"
42
          }
43
        }
44
      },
45
      "x-koha-authorization": {
46
        "permissions": {
47
          "manage_circ_rules": "1"
48
        }
49
      }
50
    },
51
    "post": {
52
      "x-mojo-to": "CirculationRules#save_rules",
53
      "operationId": "saveCirculationRules",
54
      "tags": ["circulation-rules"],
55
      "produces": [
56
        "application/json"
57
      ],
58
      "parameters": [{
59
        "name": "body",
60
        "in": "body",
61
        "description": "A JSON object containing rules to set; null for a rule_value means to delete the rule",
62
        "required": true,
63
        "schema": {
64
          "type": "array",
65
          "items": {
66
            "$ref": "../definitions.json#/circ-rule"
67
          }
68
        }
69
      }],
70
      "responses": {
71
        "200": {
72
          "description": "Rules saved",
73
          "schema": {
74
            "type": "string"
75
          }
76
        },
77
        "400": {
78
          "description": "Bad request",
79
          "schema": {
80
            "$ref": "../definitions.json#/error"
81
          }
82
        },
83
        "401": {
84
          "description": "Authentication required",
85
          "schema": {
86
            "$ref": "../definitions.json#/error"
87
          }
88
        },
89
        "403": {
90
          "description": "Access forbidden",
91
          "schema": {
92
            "$ref": "../definitions.json#/error"
93
          }
94
        },
95
        "500": {
96
          "description": "Internal error",
97
          "schema": {
98
            "$ref": "../definitions.json#/error"
99
          }
100
        },
101
        "503": {
102
          "description": "Under maintenance",
103
          "schema": {
104
            "$ref": "../definitions.json#/error"
105
          }
106
        }
107
      },
108
      "x-koha-authorization": {
109
        "permissions": {
110
          "manage_circ_rules": "1"
111
        }
112
      }
113
    }
114
  },
2
  "/circulation-rules/kinds": {
115
  "/circulation-rules/kinds": {
3
    "get": {
116
    "get": {
4
      "x-mojo-to": "CirculationRules#get_kinds",
117
      "x-mojo-to": "CirculationRules#get_kinds",
5
      "operationId": "getCirculationRuleKinds",
118
      "operationId": "getCirculationRuleKinds",
6
      "tags": ["cities"],
119
      "tags": ["circulation-rules"],
7
      "produces": [
120
      "produces": [
8
        "application/json"
121
        "application/json"
9
      ],
122
      ],
Lines 17-22 Link Here
17
            }
130
            }
18
          }
131
          }
19
        },
132
        },
133
        "400": {
134
          "description": "Bad request",
135
          "schema": {
136
            "$ref": "../definitions.json#/error"
137
          }
138
        },
20
        "403": {
139
        "403": {
21
          "description": "Access forbidden",
140
          "description": "Access forbidden",
22
          "schema": {
141
          "schema": {
Lines 28-33 Link Here
28
          "schema": {
147
          "schema": {
29
            "$ref": "../definitions.json#/error"
148
            "$ref": "../definitions.json#/error"
30
          }
149
          }
150
        },
151
        "503": {
152
          "description": "Under maintenance",
153
          "schema": {
154
            "$ref": "../definitions.json#/error"
155
          }
156
        }
157
      },
158
      "x-koha-authorization": {
159
        "permissions": {
160
          "manage_circ_rules": "1"
31
        }
161
        }
32
      }
162
      }
33
    }
163
    }
(-)a/api/v1/swagger/x-primitives.json (+4 lines)
Lines 18-23 Link Here
18
    "maxLength": 10,
18
    "maxLength": 10,
19
    "minLength": 1
19
    "minLength": 1
20
  },
20
  },
21
  "branchcode": {
22
    "type": "string",
23
    "description": "library assigned branch identifier"
24
  },
21
  "cardnumber": {
25
  "cardnumber": {
22
    "type": ["string", "null"],
26
    "type": ["string", "null"],
23
    "description": "library assigned user identifier"
27
    "description": "library assigned user identifier"
(-)a/t/db_dependent/api/v1/circ_rules.t (-1 / +270 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 => 5;
21
use Test::Deep qw( cmp_deeply set superhashof );
22
use Test::Mojo;
23
use Test::Warn;
24
25
use t::lib::TestBuilder;
26
use t::lib::Mocks;
27
28
use C4::Auth;
29
use Koha::Acquisition::Booksellers;
30
use Koha::CirculationRules;
31
use Koha::Database;
32
33
my $schema  = Koha::Database->new->schema;
34
my $builder = t::lib::TestBuilder->new;
35
36
# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
37
# this affects the other REST api tests
38
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
39
40
my $remote_address = '127.0.0.1';
41
my $t              = Test::Mojo->new('Koha::REST::V1');
42
43
sub tx_maker {
44
    my $args = shift;
45
46
    my ( $borrowernumber, $session_id ) = create_user_and_session( $args );
47
48
    return sub {
49
        my $tx = shift->ua->build_tx( @_ );
50
        $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
51
        $tx->req->env( { REMOTE_ADDR => $remote_address } );
52
53
        return $tx;
54
    }
55
}
56
57
subtest 'rule_kinds' => sub {
58
    plan tests => 3;
59
60
    my $tx_maker = tx_maker( { permissions => 'authorized' } );
61
62
    my $tx = $tx_maker->( $t, GET => '/api/v1/circulation-rules/kinds' );
63
    $t->request_ok($tx)
64
      ->status_is(200)
65
      ->json_is( Koha::CirculationRules->rule_kinds );
66
};
67
68
subtest 'get_rules' => sub {
69
    plan tests => 6;
70
71
    $schema->storage->txn_begin;
72
73
    Koha::CirculationRules->search->delete;
74
75
    my $tx_maker = tx_maker( { permissions => 'authorized' } );
76
77
    my $tx = $tx_maker->( $t, GET => '/api/v1/circulation-rules' );
78
    $t->request_ok($tx)
79
      ->status_is(200)
80
      ->json_is( [] );
81
82
    my $branchcode = $builder->build({ source => 'Branch' })->{'branchcode'};
83
    my @rules = (
84
        {
85
            branchcode => undef,
86
            categorycode => undef,
87
            itemtype => undef,
88
            rule_name => 'maxissueqty',
89
            rule_value => 10,
90
        },
91
        {
92
            branchcode => $branchcode,
93
            categorycode => undef,
94
            itemtype => undef,
95
            rule_name => 'maxissueqty',
96
            rule_value => 20,
97
        },
98
    );
99
100
    Koha::CirculationRules->set_rule( $_ ) for ( @rules );
101
102
    $tx = $tx_maker->( $t, GET => '/api/v1/circulation-rules' );
103
    $t->request_ok($tx)
104
      ->status_is(200);
105
    cmp_deeply( $tx->res->json, set( map { superhashof( $_ ) } @rules ), 'rules retrieved correctly' );
106
107
    $schema->storage->txn_rollback;
108
};
109
110
subtest 'set_rules | unauthorized' => sub {
111
    plan tests => 2;
112
113
    $schema->storage->txn_begin;
114
115
    Koha::CirculationRules->search->delete;
116
117
    my $tx_maker = tx_maker( { permissions => 'none' } );
118
119
    my $rule = {
120
        branchcode => undef,
121
        categorycode => undef,
122
        itemtype => undef,
123
        rule_name => 'maxissueqty',
124
        rule_value => 10,
125
    };
126
127
    my $tx = $tx_maker->( $t, POST => '/api/v1/circulation-rules', json => $rule );
128
    $t->request_ok($tx)
129
      ->status_is(403);
130
131
    $schema->storage->txn_rollback;
132
};
133
134
subtest 'set_rules' => sub {
135
    plan tests => 3;
136
137
    $schema->storage->txn_begin;
138
139
    Koha::CirculationRules->search->delete;
140
141
    my $tx_maker = tx_maker( { permissions => 'authorized' } );
142
143
    my @rules = (
144
        {
145
            branchcode => undef,
146
            categorycode => undef,
147
            itemtype => undef,
148
            rule_name => 'maxissueqty',
149
            rule_value => 10,
150
        },
151
    );
152
153
    my $tx = $tx_maker->( $t, POST => '/api/v1/circulation-rules', json => \@rules );
154
    $t->request_ok($tx)
155
      ->status_is(200);
156
157
    my $stored_rule = Koha::CirculationRules->search( {
158
        branchcode => undef,
159
        categorycode => undef,
160
        itemtype => undef,
161
    } )->next;
162
163
    cmp_deeply(
164
        $stored_rule && $stored_rule->unblessed,
165
        superhashof( $rules[0] ),
166
        'rule stored correctly'
167
    );
168
169
    $schema->storage->txn_rollback;
170
};
171
172
subtest 'set_rules | restricted' => sub {
173
    plan tests => 6;
174
175
    $schema->storage->txn_begin;
176
177
    Koha::CirculationRules->search->delete;
178
179
    my $branchcode = $builder->build({ source => 'Branch' })->{'branchcode'};
180
    my $other_branchcode = $builder->build({ source => 'Branch' })->{'branchcode'};
181
    my $tx_maker = tx_maker( { permissions => 'authorized_restricted', branchcode => $branchcode } );
182
183
    my @allowed_rules = (
184
        {
185
            branchcode => $branchcode,
186
            categorycode => undef,
187
            itemtype => undef,
188
            rule_name => 'maxissueqty',
189
            rule_value => 10,
190
        },
191
    );
192
193
    my @forbidden_rules = (
194
        {
195
            branchcode => undef,
196
            categorycode => undef,
197
            itemtype => undef,
198
            rule_name => 'maxissueqty',
199
            rule_value => 10,
200
        },
201
        {
202
            branchcode => $other_branchcode,
203
            categorycode => undef,
204
            itemtype => undef,
205
            rule_name => 'maxissueqty',
206
            rule_value => 20,
207
        },
208
    );
209
210
    for my $rule ( @allowed_rules ) {
211
        my $tx = $tx_maker->( $t, POST => '/api/v1/circulation-rules', json => [ $rule ] );
212
        $t->request_ok($tx)
213
          ->status_is(200);
214
    }
215
216
    for my $rule ( @forbidden_rules ) {
217
        my $tx = $tx_maker->( $t, POST => '/api/v1/circulation-rules', json => [ $rule ] );
218
        $t->request_ok($tx)
219
          ->status_is(403);
220
    }
221
222
    $schema->storage->txn_rollback;
223
};
224
225
sub create_user_and_session {
226
227
    my $args = shift;
228
    my $flags = {
229
        none => 0,
230
        # catalogue and parameters permissions
231
        authorized => (1 << 2) | (1 << 3),
232
        # Just catalogue
233
        authorized_restricted => (1 << 2),
234
    }->{ $args->{permissions} || ( $args->{authorized} ? 'authorized' : 'none' ) };
235
236
    my %branch_flags;
237
    $branch_flags{branchcode} = $args->{branchcode} if ( $args->{branchcode} );
238
239
    # my $flags = ( $args->{authorized} ) ? $args->{authorized} : 0;
240
    my $dbh = C4::Context->dbh;
241
242
    my $user = $builder->build(
243
        {   source => 'Borrower',
244
            value  => { flags => $flags, %branch_flags }
245
        }
246
    );
247
248
    # Create a session for the authorized user
249
    my $session = C4::Auth::get_session('');
250
    $session->param( 'number',   $user->{borrowernumber} );
251
    $session->param( 'id',       $user->{userid} );
252
    $session->param( 'ip',       '127.0.0.1' );
253
    $session->param( 'lasttime', time() );
254
    $session->flush;
255
256
    # Allow circulation rules management for librarian's home library, but not
257
    # other libraries
258
    if ( $args->{permissions} eq 'authorized_restricted' ) {
259
        $dbh->do(
260
            q{
261
            INSERT INTO user_permissions (borrowernumber,module_bit,code)
262
            VALUES (?,3,'manage_circ_rules')},
263
            undef, $user->{borrowernumber}, $user->{borrowernumber}
264
        );
265
    }
266
267
    return ( $user->{borrowernumber}, $session->id );
268
}
269
270
1;

Return to bug 15522