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

(-)a/C4/Circulation.pm (-4 / +7 lines)
Lines 46-51 use C4::RotatingCollections qw(GetCollectionItemBranches); Link Here
46
use Algorithm::CheckDigits;
46
use Algorithm::CheckDigits;
47
47
48
use Data::Dumper;
48
use Data::Dumper;
49
use Koha::FloatingMatrix;
49
use Koha::DateUtils;
50
use Koha::DateUtils;
50
use Koha::Calendar;
51
use Koha::Calendar;
51
use Koha::Borrower::Debarments;
52
use Koha::Borrower::Debarments;
Lines 2023-2037 sub AddReturn { Link Here
2023
        DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' });
2024
        DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' });
2024
    }
2025
    }
2025
2026
2027
    my $floatingType = Koha::FloatingMatrix::CheckFloating($item, $branch, $hbr);
2026
    # FIXME: make this comment intelligible.
2028
    # FIXME: make this comment intelligible.
2027
    #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
2029
    #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
2028
    #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
2030
    #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
2029
2031
2030
    if ( !$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){
2032
    if ((not($floatingType) || $floatingType eq 'POSSIBLE') &&
2031
        if ( C4::Context->preference("AutomaticItemReturn"    ) or
2033
        !$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){
2034
        if ( not($floatingType && $floatingType eq 'POSSIBLE') and #If floatingType is POSSIBLE, we prompt for transfer but not autoinitiate it.
2035
            (C4::Context->preference("AutomaticItemReturn"    ) or
2032
            (C4::Context->preference("UseBranchTransferLimits") and
2036
            (C4::Context->preference("UseBranchTransferLimits") and
2033
             ! IsBranchTransferAllowed($branch, $hbr, $item->{C4::Context->preference("BranchTransferLimitsType")} )
2037
             ! IsBranchTransferAllowed($branch, $hbr, $item->{C4::Context->preference("BranchTransferLimitsType")} )
2034
           )) {
2038
           ))) {
2035
            $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s)", $item->{'itemnumber'},$branch, $hbr;
2039
            $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s)", $item->{'itemnumber'},$branch, $hbr;
2036
            $debug and warn "item: " . Dumper($item);
2040
            $debug and warn "item: " . Dumper($item);
2037
            ModItemTransfer($item->{'itemnumber'}, $branch, $hbr);
2041
            ModItemTransfer($item->{'itemnumber'}, $branch, $hbr);
Lines 2040-2046 sub AddReturn { Link Here
2040
            $messages->{'NeedsTransfer'} = 1;   # TODO: instead of 1, specify branchcode that the transfer SHOULD go to, $item->{homebranch}
2044
            $messages->{'NeedsTransfer'} = 1;   # TODO: instead of 1, specify branchcode that the transfer SHOULD go to, $item->{homebranch}
2041
        }
2045
        }
2042
    }
2046
    }
2043
2044
    return ( $doreturn, $messages, $issue, $borrower );
2047
    return ( $doreturn, $messages, $issue, $borrower );
2045
}
2048
}
2046
2049
(-)a/Koha/FloatingMatrix.pm (+479 lines)
Line 0 Link Here
1
package Koha::FloatingMatrix;
2
3
# Copyright 2015 Vaara-kirjastot
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 2 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
=head1 FloatingMatrix
21
22
Koha::FloatingMatrix - Object to control accessing and modifying floating matrix rules.
23
24
=cut
25
26
use Modern::Perl;
27
use Carp qw(carp croak confess longmess);
28
use Scalar::Util qw(blessed refaddr);
29
30
use C4::Context qw(dbh);
31
use Koha::Cache;
32
use Koha::Database;
33
34
use Koha::FloatingMatrix::BranchRule;
35
36
use Koha::Exception::BadParameter;
37
38
my $cacheExpiryTime = 1200;
39
40
=head new
41
42
    my $fm = Koha::FloatingMatrix->new();
43
44
Finds a FloatingMatrix from Koha::Cache or instantiates a new one.
45
46
=cut
47
48
sub new {
49
    my ($class) = @_;
50
51
    my $cache = Koha::Cache->get_instance();
52
    my $fm = $cache->get_from_cache('floatingMatrix');
53
    unless ($fm) {
54
        $fm = {};
55
        bless $fm, $class;
56
        $fm->_getFloatingMatrix();
57
        $fm->_createModificationBuffer();
58
        $cache->set_in_cache('floatingMatrix', $fm, {expiry => $cacheExpiryTime});
59
    }
60
    return $fm if blessed($fm) && $fm->isa('Koha::FloatingMatrix');
61
    return undef;
62
}
63
64
sub _getFloatingMatrix {
65
    my ($fm) = @_;
66
67
    my $schema = Koha::Database->new()->schema();
68
    my @rules = $schema->resultset('FloatingMatrix')->search({});
69
70
    my %map;
71
    $fm->_setBranchRules( \%map );
72
    foreach my $rule (@rules) {
73
        my $branchRule = Koha::FloatingMatrix::BranchRule->newFromDBIx($rule);
74
        $fm->_linkBranchRule($branchRule);
75
    }
76
}
77
78
=head GetFloatingTypes
79
Static Method
80
81
    my $floatingTypes = Koha::FloatingMatrix::GetFloatingTypes();
82
83
@RETURNS Reference to ARRAY, of koha.floating_matrix.floating enumerations.
84
            These are all the available ways Items can float in Koha.
85
=cut
86
87
sub GetFloatingTypes {
88
    my $schema = Koha::Database->new()->schema();
89
    my $source = $schema->source('FloatingMatrix');
90
    my $info = $source->column_info('floating');
91
    my $floatingTypeEnumerations =  $info->{extra}->{list};
92
    return $floatingTypeEnumerations;
93
}
94
95
=head getFloatingTypes
96
    $fm->getFloatingTypes();
97
See. GetFloatingTypes()
98
=cut
99
sub getFloatingTypes {
100
    my ($fm) = @_;
101
102
    return $fm->{floatingTypes} if $fm->{floatingTypes};
103
104
    $fm->{floatingTypes} = Koha::FloatingMatrix::GetFloatingTypes();
105
    return $fm->{floatingTypes};
106
}
107
108
=head upsertBranchRule
109
110
    my ($branchRule, $error) = $fm->upsertBranchRule( $params );
111
    if ($error) {
112
        #I must have given some bad object properties
113
    }
114
    else {
115
        $fm->store();
116
    }
117
118
Adds or modifies an existing overduerule. Take note that this is only a local modification
119
for this instance of OverdueRulesMap-object.
120
If changes need to persists, call the $orm->store() method to save changes to DB.
121
122
@PARAM1, HASH, See Koha::Overdues::OverdueRule->new() for parameter descriptions.
123
@RETURN, Koha::Overdues::OverdueRule-object if success and
124
         String errorCode, if something bad hapened.
125
126
@THROWS Koha::Exception::BadParameter from Koha::FloatingMatrix::BranchRule->new().
127
=cut
128
129
sub upsertBranchRule {
130
    my ($fm, $params) = @_;
131
132
    my $branchRule = Koha::FloatingMatrix::BranchRule->new($params); #While creating this object we sanitate the parameters.
133
134
    my $existingBranchRule = $fm->_findBranchRule($branchRule);
135
    my $operation;
136
    if ($existingBranchRule) { #We are modifying an existing rule
137
        #Should we somehow tell that we are updating an existing rule?
138
        $existingBranchRule->replace($branchRule); #Replace with the new sanitated values, we preserve the internal data structure position.
139
        #Replacing might be less costly than recursively unlinking a large internal mapping.
140
        $branchRule = $existingBranchRule;
141
        $operation = 'MOD';
142
    }
143
    else { #Just adding more rules
144
        $fm->_linkBranchRule($branchRule);
145
        $operation = 'ADD';
146
    }
147
148
    $fm->_pushToModificationBuffer([$branchRule, $operation]);
149
    return $branchRule;
150
}
151
152
=head getBranchRule
153
154
    my $branchRule = $fm->getBranchRule( $fromBranch, $toBranch );
155
156
@PARAM1 String, koha.floating_matrix.from_branch, must give @PARAM2 as well
157
@PARAM2 String, koha.floating_matrix.to_branch, must give @PARAM1 as well
158
@RETURNS Koha::FloatingMatrix::BranchRule-object matching the params or undef.
159
=cut
160
161
sub getBranchRule {
162
    my ($fm, $fromBranch, $toBranch) = @_;
163
164
    my $branchRule = $fm->_findBranchRule(undef, $fromBranch, $toBranch);
165
166
    return $branchRule;
167
}
168
169
=head getBranchRules
170
171
    my $branchRules = $fm->getBranchRules();
172
173
@RETURNS Reference to a HASH of Koha::FloatingMatrix::BranchRule-objects
174
         Hash keys are <fromBranch>-<toBranch>, eg. FFL-CPL
175
=cut
176
177
sub getBranchRules {
178
    my ($fm) = @_;
179
    return $fm->{branchRules};
180
}
181
182
=head _setBranchRules
183
Needs to be called only from the _getFloatingMatrix() to bind the branchRules-map to this object.
184
@PARAM1, Reference to HASH.
185
=cut
186
187
sub _setBranchRules {
188
    my ($fm, $hash) = @_;
189
    $fm->{branchRules} = $hash;
190
}
191
192
sub checkFloating {
193
    my ($fm, $item, $checkinBranch, $transferTargetBranch) = @_;
194
    unless ($transferTargetBranch) { #If no transfer branch is given, then we try our best to figure it out.
195
        my $branchItemRule = C4::Circulation::GetBranchItemRule($item->{'homebranch'}, $item->{'itype'});
196
        my $returnBranchRule = $branchItemRule->{'returnbranch'} || "homebranch";
197
        # get the proper branch to which to return the item
198
        $transferTargetBranch = $item->{$returnBranchRule} || C4::Context->userenv->{'branch'};
199
    }
200
201
    #If the check-in branch and transfer branch are the same, then no point in transferring.
202
    if ($checkinBranch eq $transferTargetBranch) {
203
        return undef;
204
    }
205
206
    my $branchRule = $fm->getBranchRule($checkinBranch, $transferTargetBranch);
207
    if ($branchRule) {
208
        my $floating = $branchRule->getFloating();
209
        if ($floating eq 'ALWAYS') {
210
            return 'ALWAYS';
211
        }
212
        elsif ($floating eq 'POSSIBLE') {
213
            return 'POSSIBLE';
214
        }
215
        elsif ($floating eq 'CONDITIONAL') {
216
            if(_CheckConditionalFloat($item, $branchRule)) {
217
                return 'ALWAYS';
218
            }
219
        }
220
        else {
221
            warn "FloatingMatrix->checkFloating():> Bad floating type for route from '$checkinBranch' to '$transferTargetBranch'. Not floating.\n";
222
        }
223
    }
224
    return undef;
225
}
226
227
=head _CheckConditionalFloat
228
Static method
229
230
@PARAM1, HASH of koha.items-row
231
@PARAM2, Koha::FloatingMatrix::BranchRule-object
232
@RETURNS 1 if floats, undef if not.
233
=cut
234
235
sub _CheckConditionalFloat {
236
    my ($item, $branchRule) = @_;
237
238
    my $conditionRules = $branchRule->getConditionRules();
239
240
    my $evalCondition = '';
241
    if (my @conds = $conditionRules =~ /(\w+)\s+(ne|eq|gt|lt|<|>|==|!=)\s+(\w+)\s*(and|or|xor|&&|\|\|)?/ig) {
242
        #Iterate the condition quads, with the fourth index being the logical join operator.
243
        for (my $i=0 ; $i<scalar(@conds) ; $i+=4) {
244
            my $column = $conds[$i];
245
            my $operator = $conds[$i+1];
246
            my $value = $conds[$i+2];
247
            my $join = $conds[$i+3] || '';
248
249
            $evalCondition .= join(' ',"\$item->{'$column'}",$operator,"'$value'",$join,'');
250
        }
251
    }
252
    else {
253
        warn "Koha::FloatingMatrix::_CheckConditionalFloat():> Bad condition rules '$conditionRules' couldn't be parsed\n";
254
        return undef;
255
    }
256
    my $ok = eval("return 1 if($evalCondition);");
257
    if ($@) {
258
        warn "Koha::FloatingMatrix::_CheckConditionalFloat():> Something bad hapened when trying to evaluate the dynamic conditional:\n$@\n";
259
        return undef;
260
    }
261
    return $ok;
262
}
263
264
=head CheckFloating
265
Static Subroutine
266
267
A convenience Subroutine to checkFloating without intantiating a new Koha::FloatingMatrix-object
268
=cut
269
270
sub CheckFloating {
271
    my ($item, $checkinBranch, $transferTargetBranch) = @_;
272
    my $fm = Koha::FloatingMatrix->new();
273
    return $fm->checkFloating($item, $checkinBranch, $transferTargetBranch);
274
}
275
276
=head _findBranchRule
277
278
    my $branchRule = $fm->_findBranchRule( undef, $fromBranch, $toBranch );
279
    my $existingBranchRule = $fm->_findBranchRule( $branchRule );
280
281
Finds a branch rule from the internal floating matrix map.
282
283
This abstracts the retrieval of branch rules, so we can later change the internal mapping.
284
@PARAM1, Koha::FloatingMatrix::BranchRule-object, to see if a BranchRule with same targeting rules is present.
285
         or undef, if you are only interested in retrieving.
286
@PARAM2-3, MANDATORY if no @PARAM1 given, Targeting rules to find a BranchRule.
287
@RETURNS Koha::FloatingMatrix::BranchRule-object, of the object occupying the given position.
288
         or undef if nothing is found.
289
=cut
290
291
sub _findBranchRule {
292
    my ($fm, $branchRule, $fromBranch, $toBranch) = @_;
293
    if (blessed($branchRule) && $branchRule->isa('Koha::FloatingMatrix::BranchRule')) {
294
        $fromBranch = $branchRule->getFromBranch();
295
        $toBranch   = $branchRule->getToBranch();
296
    }
297
298
    my $existingBranchRule = $fm->getBranchRules()->{  $fromBranch  }->{  $toBranch  };
299
    return $existingBranchRule;
300
}
301
302
=head _linkBranchRule
303
304
    my $existingBranchRule = $fm->_linkBranchRule( $branchRule );
305
306
Links the new branchrule to the internal floating matrix map, overwriting a possible existing
307
reference to a BranchRule, and losing that into the binary limbo.
308
309
This abstracts the retrieval of floating matrix routes, so we can later change the internal mapping.
310
@PARAM1, Koha::FloatingMatrix::BranchRule-object, to link to the internal mapping
311
=cut
312
313
sub _linkBranchRule {
314
    my ($fm, $branchRule) = @_;
315
316
    $fm->{branchRules}->{  $branchRule->getFromBranch()  }->{  $branchRule->getToBranch()  } = $branchRule;
317
}
318
319
=head _unlinkBranchRule
320
321
    my $existingBranchRule = $fm->_unlinkBranchRule( $branchRule );
322
323
Unlinks the branchRule from the internal floating matrix map and releasing it into the binary limbo.
324
325
This abstracts the internal mapping of branch rules, so we can later change it.
326
@PARAM1, Koha::FloatingMatrix::BranchRule-object
327
328
=cut
329
330
sub _unlinkBranchRule {
331
    my ($fm, $branchRule) = @_;
332
333
    #Delete the BranchRule
334
    my $branchRules = $fm->getBranchRules();
335
    my $fromBranch = $branchRule->getFromBranch();
336
    my $toBranch = $branchRule->getToBranch();
337
338
    eval{ delete( $branchRules->{  $fromBranch  }->{  $toBranch  } ); };
339
    if ($@) {
340
        carp "Unlinking BranchRule failed because of '".$@."'. Something wrong with this BranchRule\n".$branchRule->toString()."\n";
341
        return $@;
342
    }
343
344
    unless (scalar(%{$branchRules->{  $fromBranch  }})) {
345
        #Delete the branchLevel
346
        delete( $branchRules->{  $fromBranch  } );
347
    }
348
}
349
350
=head deleteBranchRule
351
352
    $fm->deleteBranchRule($branchRule);
353
    $fm->deleteBranchRule($fromBranch, $toBranch);
354
355
Deletes the BranchRule from the internal mapping, but not from the DB.
356
Call $fm->store() to persist the removal.
357
358
The given $branchRule must be the same object gained using $fm->getBranchRule(),
359
or you are misusing the FloatingMatrix-object and bad things might happen.
360
361
@PARAM1, Koha::FloatingMatrix::BranchRule-object to delete from DB.
362
or
363
@PARAM1 {String, koha.floating_matrix.from_branch}
364
@PARAM1 {String, koha.floating_matrix.to_branch}
365
366
@THROWS Koha::Exception::BadParameter if no branchRule, with the given parameters, is found,
367
                or the given BranchRule doesn't match the one in the internal mapping.
368
=cut
369
370
sub deleteBranchRule {
371
    my ($fm, $fromBranch, $toBranch) = @_;
372
    my ($branchRule, $branchRuleLocal);
373
    #Process given parameters, see if we use parameter group1 (BranchRule-object) or group2 (branchcodes)?
374
    if (blessed $fromBranch && $fromBranch->isa('Koha::FloatingMatrix::BranchRule')) {
375
        $branchRule = $fromBranch;
376
        $fromBranch = $branchRule->getFromBranch();
377
        $toBranch = $branchRule->getToBranch();
378
    }
379
380
    $branchRuleLocal = $fm->getBranchRule($fromBranch, $toBranch);
381
    Koha::Exception::BadParameter->throw(error => "Koha::FloatingMatrix->deleteBranchRule($fromBranch, $toBranch):> No BranchRule exists for the given fromBranch '$fromBranch' and toBranch '$toBranch'")
382
                unless $branchRuleLocal;
383
384
    $fm->_unlinkBranchRule(  $branchRuleLocal  );
385
    $fm->_pushToModificationBuffer([$branchRuleLocal, 'DEL']);
386
}
387
388
=head $fm->deleteAllFloatingMatrixRules()
389
390
Deletes all Floating matrix rules in the DB, shows no pity.
391
Invalidates $fm in the Koha::Cache
392
=cut
393
394
sub deleteAllFloatingMatrixRules {
395
    my ($fm) = @_;
396
    my $schema = Koha::Database->new()->schema();
397
    $schema->resultset('FloatingMatrix')->search({})->delete_all;
398
    $fm = undef;
399
400
    my $cache = Koha::Cache->get_instance();
401
    $cache->clear_from_cache('floatingMatrix');
402
}
403
404
=head store
405
406
    $fm->store();
407
408
Saves all pending transactions to DB, by calling the Koha::FloatingMatrix::BranchRule->store() || delete();
409
410
=cut
411
412
sub store {
413
    my $fm = shift;
414
    my $schema = Koha::Database->new()->schema();
415
416
    my $pendingModifications = $fm->_consumeModificationBuffer();
417
    foreach my $modRequest (@$pendingModifications) {
418
        my $branchRule = $modRequest->[0];
419
        my $operation = $modRequest->[1];
420
        if ($operation eq 'MOD' || $operation eq 'ADD') {
421
            $branchRule->store();
422
        }
423
        elsif ($operation eq 'DEL') {
424
            $branchRule->delete();
425
        }
426
        else {
427
            carp "Unsupported database access operation '$operation'!";
428
        }
429
    }
430
431
    my $cache = Koha::Cache->get_instance();
432
    $cache->set_in_cache('floatingMatrix', $fm, {expiry => $cacheExpiryTime});
433
}
434
435
=head _pushToModificationBuffer
436
437
    $fm->_pushToModificationBuffer([$branchRule, $operation]);
438
439
To be able to more effectively service DB write operations, especially when using
440
FloatingMatrix with an REST(ful?) API giving lots of write operations, it is useful
441
to be able to know which BranchRules need changing and which do not.
442
Thus we don't need to either
443
DELETE all BranchRules from DB and re-add them (what if the rewrite request dies?)
444
  or
445
check each rule for possible changes.
446
447
The modification buffer tells what information needs changing.
448
449
To write the changes to DB, use $fm->store().
450
451
@PARAM1, Tuple (Two-index ARRAY reference). [0] = $branchRule (see. upsertBranchRule())
452
                                            [1] = The operation, either 'ADD', 'MOD' or 'DEL'
453
@RETURN, always nothing.
454
455
=cut
456
457
sub _pushToModificationBuffer {
458
    my ($fm, $tuple) = @_;
459
    push @{$fm->{modBuffer}}, $tuple;
460
    return undef;
461
}
462
=head _consumeModificationBuffer
463
Detaches the modification buffer from the FloatingMatrix (parent) and returns it.
464
Fm now has an empty modification buffer ready for new modifications.
465
=cut
466
467
sub _consumeModificationBuffer {
468
    my ($fm) = @_;
469
    my $modBuffer = $fm->{modBuffer};
470
    $fm->{modBuffer} = [];
471
    return $modBuffer;
472
}
473
sub _createModificationBuffer {
474
    my ($fm) = @_;
475
    $fm->{modBuffer} = [];
476
    return undef;
477
}
478
479
1; #Satisfy the compiler
(-)a/Koha/FloatingMatrix/BranchRule.pm (+342 lines)
Line 0 Link Here
1
package Koha::FloatingMatrix::BranchRule;
2
3
# Copyright 2015 Vaara-kirjastot
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 2 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
=head1 Rule
21
22
Koha::FloatingMatrix::BranchRule - Object representing floating rules for one branch
23
24
=head1 DESCRIPTION
25
26
27
=cut
28
29
use Modern::Perl;
30
use Carp qw(carp croak confess longmess);
31
use Scalar::Util 'blessed';
32
use Data::Dumper;
33
34
use C4::Context qw(dbh);
35
use Koha::Cache;
36
use Koha::Database;
37
38
use Koha::Exception::BadParameter;
39
40
41
=head new
42
43
    my ($branchRule, $error) = Koha::FloatingMatrix::BranchRule->new({
44
                                    fromBranch => 'CPL',
45
                                    toBranch => 'FFL',
46
                                    floating => ['ALWAYS'|'POSSIBLE'|'CONDITIONAL'],
47
                                    conditionRules => "items->{itype} eq 'BK' && $items->{permanent_location} eq 'CART'"
48
                                });
49
50
BranchRule defines the floating rule for one transfer route.
51
Eg. If you check-in an Item to CFL and based on your library policies tha Item is transferred to IPT,
52
We check if there is a floating rule for that fromBranch-toBranch -combination and apply that if applicable.
53
54
@PARAM1, HASH
55
@RETURN, Koha::FloatingMatrix::BranchRule-object,
56
@THROWS Koha::Exception::BadParameter if given parameters don't validate properly.
57
=cut
58
59
sub new {
60
    my ($class, $params) = @_;
61
62
    ValidateParams($params);
63
64
    bless $params, $class;
65
    return $params;
66
}
67
68
=head newFromDBIx
69
70
    Koha::FloatingMatrix::BranchRule->newFromDBIx(  $Koha::Schema::Result::FloatingMatrix  );
71
72
Creates a BranchRule-object from DBIx.
73
See new() for more info.
74
=cut
75
76
sub newFromDBIx {
77
    my ($class, $dbix) = @_;
78
79
    my $params = {
80
                id => $dbix->id(),
81
                fromBranch => $dbix->get_column('from_branch'),
82
                toBranch => $dbix->get_column('to_branch'),
83
                floating => $dbix->get_column('floating'),
84
                conditionRules => $dbix->get_column('condition_rules'),
85
            };
86
    return $class->new($params);
87
}
88
89
=head ValidateParams, Static subroutine
90
91
    my $error = Koha::Overdues::OverdueRule::ValidateParams($params);
92
93
@PARAM1, HASH, see new() for valid values.
94
@THROWS Koha::Exception::BadParameter if given parameters don't validate properly.
95
=cut
96
97
my $maximumConditionRulesDatabaseLength = 100;
98
sub ValidateParams {
99
    my ($params) = @_;
100
    my $errorMsg;
101
102
    if (not($params->{fromBranch}) || length $params->{fromBranch} < 1) {
103
        $errorMsg = "No 'fromBranch'";
104
    }
105
    elsif (not($params->{toBranch}) || length $params->{toBranch} < 1) {
106
        $errorMsg = "No 'toBranch'";
107
    }
108
    elsif (not($params->{floating}) || length $params->{floating} < 1) {
109
        $errorMsg = "No 'floating'";
110
    }
111
    elsif ($params->{floating} &&  ($params->{floating} ne 'CONDITIONAL' &&
112
                                    $params->{floating} ne 'ALWAYS' &&
113
                                    $params->{floating} ne 'POSSIBLE')
114
                                   ) {
115
        $errorMsg = "Bad enum '".$params->{floating}."' for 'floating'";
116
    }
117
    elsif (not($params->{conditionRules}) && $params->{floating} eq 'CONDITIONAL') {
118
        $errorMsg = "No 'conditionRules' when floating = 'CONDITIONAL'";
119
    }
120
    elsif ($params->{conditionRules} && $params->{conditionRules} =~ /[};{]/gsmi) {
121
        $errorMsg = "Not allowed 'conditionRules' characters '};{' present";
122
    }
123
    elsif ($params->{conditionRules} && length($params->{conditionRules}) > $maximumConditionRulesDatabaseLength) {
124
        $errorMsg = "'conditionRules' text is too long. Maximum length is '$maximumConditionRulesDatabaseLength' characters";
125
    }
126
    elsif ($params->{conditionRules}) {
127
        ParseConditionRules(undef, $params->{conditionRules});
128
    }
129
130
    if ($errorMsg) {
131
        my $fb = $params->{fromBranch} || '';
132
        my $tb = $params->{toBranch} || '';
133
        my $id = $params->{id} || '';
134
        Koha::Exception::BadParameter->throw(error => "Koha::FloatingMatrix::BranchRule::ValidateParams():> $errorMsg. For branch rule id '$id', fromBranch '$fb', toBranch '$tb'.");
135
    }
136
    #Now that we have sanitated the input, we can rest assured that bad input won't crash this Object :)
137
}
138
139
=head parseConditionRules, Static method
140
141
    my $evalCondition = ParseConditionRules($item, $conditionRules);
142
    my $evalCondition = ParseConditionRules(undef, $conditionRules);
143
144
Parses the given Perl boolean expression into an eval()-able expression.
145
If Item is given, uses the Item's columns to create a executable expression to check for
146
conditional floating for this specific Item.
147
148
@PARAM1 {Reference to HASH of koha.items-row} Item to check for conditional floating.
149
@PARAM2 {String koha.floating_matrix.condition_rules} The boolean expression to turn
150
                    into a Perl code to check the floating condition.
151
@THROWS Koha::Exception::BadParameter, if the conditionRules couldn't be parsed.
152
=cut
153
sub ParseConditionRules {
154
    my ($item, $conditionRulesString) = @_;
155
    my $evalCondition = '';
156
    if (my @conds = $conditionRulesString =~ /(\w+)\s+(ne|eq|gt|lt|<|>|==|!=)\s+(\w+)\s*(and|or|xor|&&|\|\|)?/ig) {
157
158
        #If we haven't got no Item, simply stop here to aknowledge that the given condition logic is valid (atleast parseable)
159
        return undef unless $item;
160
161
        #If we have an Item, then prepare and return an eval-expression to test if the Item should float.
162
        #Iterate the condition quads, with the fourth index being the logical join operator.
163
        for (my $i=0 ; $i<scalar(@conds) ; $i+=4) {
164
            my $column = $conds[$i];
165
            my $operator = $conds[$i+1];
166
            my $value = $conds[$i+2];
167
            my $join = $conds[$i+3] || '';
168
169
            $evalCondition .= join(' ',"\$item->{'$column'}",$operator,"'$value'",$join,'');
170
        }
171
172
        return $evalCondition;
173
    }
174
    else {
175
        Koha::Exception::BadParameter->throw(error =>
176
                    "Koha::FloatingMatrix::parseConditionRules():> Bad condition rules '$conditionRulesString' couldn't be parsed\n".
177
                    "See 'Help' for more info");
178
    }
179
}
180
181
=head replace
182
183
    my $fmBranchRule->replace( $replacementBranchRule );
184
185
Replaces the calling branch rule's keys with the given parameters'.
186
=cut
187
188
sub replace {
189
    my ($branchRule, $replacementBranchRule) = @_;
190
191
    foreach my $key (keys %$replacementBranchRule) {
192
        $branchRule->{$key} = $replacementBranchRule->{$key};
193
    }
194
}
195
196
=head clone
197
    $fmBranchCode->clone();
198
Returns a duplicate of self
199
=cut
200
sub clone {
201
    my ($branchRule) = @_;
202
203
    my %newBranchRuleParams;
204
    foreach my $key (keys %$branchRule) {
205
        $newBranchRuleParams{$key} = $branchRule->{$key};
206
    }
207
    return Koha::FloatingMatrix::BranchRule->new(\%newBranchRuleParams);
208
}
209
210
=head store
211
Saves the BranchRule into the floating_matrix-table
212
@THROWS Koha::Exception::BadParameter if id given but no object exists with that id.
213
=cut
214
sub store {
215
    my $branchRule = shift;
216
    my $schema = Koha::Database->new()->schema();
217
218
    my $params = $branchRule->_buildBranchRuleColumns();
219
    my $id = $branchRule->getId();
220
    my $oldBranchRule;
221
    if ($id) {
222
        $oldBranchRule = $schema->resultset( 'FloatingMatrix' )->find( $id );
223
    }
224
    if ($id && not($oldBranchRule)) {
225
        Koha::Exception::BadParameter->throw(error => "Koha::FloatingMatrix::BranchRule->store():> floating_matrix.id given, but no matching row exist in DB");
226
    }
227
228
    if ($oldBranchRule) {
229
        $oldBranchRule->update( $params );
230
    }
231
    else {
232
        my $newBranchRule = $schema->resultset( 'FloatingMatrix' )->create( $params );
233
        $branchRule->setId( $newBranchRule->id() );
234
    }
235
}
236
237
=head _buildBranchRuleColumns
238
Transforms the BranchRule into a DBIx $parameters HASH which can be UPDATED to DB.
239
DBIx is crazy about excess parameters with no mapped DB column, so we cannot just pass the
240
BranchRule-object to the DBIx.
241
=cut
242
sub _buildBranchRuleColumns {
243
    my ($branchRule) = @_;
244
245
    my $columns = {};
246
    $columns->{"from_branch"} = $branchRule->getFromBranch();
247
    $columns->{"to_branch"} = $branchRule->getToBranch();
248
    $columns->{"floating"} = $branchRule->getFloating();
249
    $columns->{"condition_rules"} = $branchRule->getConditionRules();
250
251
    return $columns;
252
}
253
254
sub delete {
255
    my ($branchRule) = @_;
256
    my $schema = Koha::Database->new()->schema();
257
    $schema->resultset('FloatingMatrix')->find($branchRule->getId())->delete();
258
}
259
260
sub setId {
261
    my ($self, $val) = @_;
262
    if ($val) {
263
        $self->{id} = $val;
264
    }
265
    else {
266
        delete $self->{id};
267
    }
268
}
269
sub getId {
270
    my ($self) = @_;
271
    return $self->{id};
272
}
273
sub setFromBranch {
274
    my ($self, $fromBranch) = @_;
275
    $self->{fromBranch} = $fromBranch;
276
}
277
sub getFromBranch {
278
    my ($self) = @_;
279
    return $self->{fromBranch};
280
}
281
sub setToBranch {
282
    my ($self, $toBranch) = @_;
283
    $self->{toBranch} = $toBranch;
284
}
285
sub getToBranch {
286
    my ($self) = @_;
287
    return $self->{toBranch};
288
}
289
sub setFloating {
290
    my ($self, $val) = @_;
291
    $self->{floating} = $val;
292
}
293
sub getFloating {
294
    my ($self) = @_;
295
    return $self->{floating};
296
}
297
=head setConditionRules
298
See parseConditionRules()
299
@THROWS Koha::Exception::BadParameter, if the conditionRules couldn't be parsed.
300
=cut
301
sub setConditionRules {
302
    my ($self, $val) = @_;
303
    #Validate the conditinal rules.
304
    ParseConditionRules(undef, $val);
305
    $self->{conditionRules} = $val;
306
}
307
sub getConditionRules {
308
    my ($self) = @_;
309
    return $self->{conditionRules};
310
}
311
312
=head toString
313
314
    my $stringRepresentationOfThisObject = $branchRule->toString();
315
    print $stringRepresentationOfThisObject."\n";
316
317
=cut
318
319
sub toString {
320
    my ($self) = @_;
321
322
    return Data::Dumper::Dump($self);
323
}
324
=head TO_JSON
325
326
    my $json = JSON::XS->new->utf8->convert_blessed->encode( $branchRule );
327
    or
328
    my $json = $branchRule->TO_JSON();
329
330
Used to serialize this object as JSON.
331
=cut
332
sub TO_JSON {
333
    my ($branchRule) = @_;
334
335
    my $json = {};
336
    while (my ($key, $val) = each(%$branchRule)) {
337
        $json->{$key} = $val;
338
    }
339
    return $json;
340
}
341
342
1; #Satisfy the compiler
(-)a/Koha/Schema/Result/FloatingMatrix.pm (+114 lines)
Line 0 Link Here
1
package Koha::Schema::Result::FloatingMatrix;
2
3
# Created by DBIx::Class::Schema::Loader
4
# DO NOT MODIFY THE FIRST PART OF THIS FILE
5
6
use strict;
7
use warnings;
8
9
use base 'DBIx::Class::Core';
10
11
12
=head1 NAME
13
14
Koha::Schema::Result::FloatingMatrix
15
16
=cut
17
18
__PACKAGE__->table("floating_matrix");
19
20
=head1 ACCESSORS
21
22
=head2 id
23
24
  data_type: 'integer'
25
  is_auto_increment: 1
26
  is_nullable: 0
27
28
=head2 from_branch
29
30
  data_type: 'varchar'
31
  is_foreign_key: 1
32
  is_nullable: 0
33
  size: 10
34
35
=head2 to_branch
36
37
  data_type: 'varchar'
38
  is_foreign_key: 1
39
  is_nullable: 0
40
  size: 10
41
42
=head2 floating
43
44
  data_type: 'enum'
45
  default_value: 'ALWAYS'
46
  extra: {list => ["ALWAYS","POSSIBLE","CONDITIONAL"]}
47
  is_nullable: 0
48
49
=head2 condition_rules
50
51
  data_type: 'varchar'
52
  is_nullable: 1
53
  size: 20
54
55
=cut
56
57
__PACKAGE__->add_columns(
58
  "id",
59
  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
60
  "from_branch",
61
  { data_type => "varchar", is_foreign_key => 1, is_nullable => 0, size => 10 },
62
  "to_branch",
63
  { data_type => "varchar", is_foreign_key => 1, is_nullable => 0, size => 10 },
64
  "floating",
65
  {
66
    data_type => "enum",
67
    default_value => "ALWAYS",
68
    extra => { list => ["ALWAYS", "POSSIBLE", "CONDITIONAL"] },
69
    is_nullable => 0,
70
  },
71
  "condition_rules",
72
  { data_type => "varchar", is_nullable => 1, size => 20 },
73
);
74
__PACKAGE__->set_primary_key("id");
75
76
=head1 RELATIONS
77
78
=head2 from_branch
79
80
Type: belongs_to
81
82
Related object: L<Koha::Schema::Result::Branch>
83
84
=cut
85
86
__PACKAGE__->belongs_to(
87
  "from_branch",
88
  "Koha::Schema::Result::Branch",
89
  { branchcode => "from_branch" },
90
  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
91
);
92
93
=head2 to_branch
94
95
Type: belongs_to
96
97
Related object: L<Koha::Schema::Result::Branch>
98
99
=cut
100
101
__PACKAGE__->belongs_to(
102
  "to_branch",
103
  "Koha::Schema::Result::Branch",
104
  { branchcode => "to_branch" },
105
  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
106
);
107
108
109
# Created by DBIx::Class::Schema::Loader v0.07010 @ 2015-05-08 18:06:26
110
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:v6/Qk4/BTfzIuwD0Z2bXmA
111
112
113
# You can replace this text with custom code or comments, and it will be preserved on regeneration
114
1;
(-)a/admin/floating-matrix-api.pl (+87 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
# Copyright 2015 Vaara-kirjastot
3
#
4
# This file is part of Koha.
5
#
6
# Koha is free software; you can redistribute it and/or modify it under the
7
# terms of the GNU General Public License as published by the Free Software
8
# Foundation; either version 2 of the License, or (at your option) any later
9
# version.
10
#
11
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
12
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License along
16
# with Koha; if not, write to the Free Software Foundation, Inc.,
17
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19
use Modern::Perl;
20
use CGI qw ( -utf8 );
21
use Try::Tiny;
22
use Scalar::Util qw(blessed);
23
use JSON::XS;
24
25
use C4::Context;
26
use C4::Output;
27
use C4::Auth qw(check_cookie_auth);
28
29
use Koha::FloatingMatrix;
30
31
my $input = new CGI;
32
33
#my ( $auth_status, $sessionID ) =
34
#  check_cookie_auth( $input->cookie('CGISESSID'),
35
#    { circulate => 'circulate_remaining_permissions' } );
36
my ( $auth_status, $sessionID ) =
37
  check_cookie_auth( $input->cookie('CGISESSID'),
38
                    {
39
                        parameters => 1,
40
                    } );
41
42
43
binmode STDOUT, ":encoding(UTF-8)";
44
45
my $fm = Koha::FloatingMatrix->new();
46
47
my $data = $input->Vars();
48
if ($data) {
49
    try {
50
        ##If we are getting a DELETE-request, we DELETE (CGI doesn't know what DELETE is :(((
51
        if ($data->{delete}) {
52
            $fm->deleteBranchRule($data->{fromBranch}, $data->{toBranch});
53
            $fm->store();
54
        }
55
        ##If we are getting a POST-request, we UPSERT
56
        else {
57
            $fm->upsertBranchRule($data);
58
            $fm->store();
59
        }
60
    } catch {
61
        if (blessed $_ && $_->isa('Koha::Exception::BadParameter')) {
62
            respondBadParameterException($_);
63
        }
64
        else {
65
            die $_;
66
        }
67
    };
68
69
    print $input->header( -type => 'text/json',
70
                          -charset => 'UTF-8',
71
                          -status => "200 OK");
72
    print JSON::XS->new->utf8->convert_blessed->encode($data);
73
}
74
else {
75
    print $input->header( -type => 'text/plain',
76
                          -charset => 'UTF-8',
77
                          -status => "405 Method Not Allowed");
78
}
79
80
sub respondBadParameterException {
81
    my ($e) = @_;
82
    print $input->header( -type => 'text/json',
83
                          -charset => 'UTF-8',
84
                          -status => "400 Bad Request");
85
    print JSON::XS->new->utf8->encode({error => $e->as_string()});
86
    exit 1;
87
}
(-)a/admin/floating-matrix.pl (+139 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
# Copyright 2015 Vaara-kirjastot
3
#
4
# This file is part of Koha.
5
#
6
# Koha is free software; you can redistribute it and/or modify it under the
7
# terms of the GNU General Public License as published by the Free Software
8
# Foundation; either version 2 of the License, or (at your option) any later
9
# version.
10
#
11
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
12
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License along
16
# with Koha; if not, write to the Free Software Foundation, Inc.,
17
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19
use Modern::Perl;
20
use CGI qw ( -utf8 );
21
use C4::Context;
22
use C4::Output;
23
use C4::Auth;
24
use C4::Branch; # GetBranches
25
26
use Koha::FloatingMatrix;
27
28
my $input = new CGI;
29
30
my ($template, $loggedinuser, $cookie)
31
    = get_template_and_user({template_name => "admin/floating-matrix.tt",
32
                            query => $input,
33
                            type => "intranet",
34
                            authnotrequired => 0,
35
                            flagsrequired => {parameters => 1},
36
                            debug => 1,
37
                            });
38
39
my $operation = 'showMatrix'; #Default op is show.
40
41
my $fm = Koha::FloatingMatrix->new();
42
my $branches = GetBranches();
43
44
$template->param(
45
    fm => $fm,
46
    branches => $branches,
47
);
48
49
50
output_html_with_http_headers $input, $cookie, $template->output;
51
52
exit 0;
53
54
=head
55
my $update = $input->param('op') eq 'set-cost-matrix';
56
57
my ($cost_matrix, $have_matrix);
58
unless ($update) {
59
    $cost_matrix = TransportCostMatrix();
60
    $have_matrix = keys %$cost_matrix if $cost_matrix;
61
}
62
63
my $branches = GetBranches();
64
my @branchloop = map { code => $_,
65
                       name => $branches->{$_}->{'branchname'} },
66
                 sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} }
67
                 keys %$branches;
68
my (@branchfromloop, @cost, @errors);
69
foreach my $branchfrom ( @branchloop ) {
70
    my $fromcode = $branchfrom->{code};
71
72
    my %from_row = ( code => $fromcode, name => $branchfrom->{name} );
73
    foreach my $branchto ( @branchloop ) {
74
        my $tocode = $branchto->{code};
75
76
        my %from_to_input_def = ( code => $tocode, name => $branchto->{name} );
77
        push @{ $from_row{branchtoloop} }, \%from_to_input_def;
78
79
        if ($fromcode eq $tocode) {
80
            $from_to_input_def{skip} = 1;
81
            next;
82
        }
83
84
        (my $from_to = "${fromcode}_${tocode}") =~ s/\W//go;
85
         $from_to_input_def{id} = $from_to;
86
        my $input_name   = "cost_$from_to";
87
        my $disable_name = "disable_$from_to";
88
89
        if ($update) {
90
            my $value = $from_to_input_def{value} = $input->param($input_name);
91
            if ( $input->param($disable_name) ) {
92
                $from_to_input_def{disabled} = 1;
93
            }
94
            else {
95
                push @errors, "$from_row{name} -> $from_to_input_def{name}"
96
                  unless $value =~ /\d/o && $value >= 0.0;
97
            }
98
        }
99
        else {
100
            if ($have_matrix) {
101
                if ( my $cell = $cost_matrix->{$tocode}{$fromcode} ) {
102
                    $from_to_input_def{value} = $cell->{cost};
103
                    $from_to_input_def{disabled} = 1 if $cell->{disable_transfer};
104
                } else {
105
                    # matrix has been previously initialized, but a branch referenced here was created afterward.
106
                    $from_to_input_def{disabled} = 1;
107
                }
108
            } else {
109
                # First time initializing the matrix
110
                $from_to_input_def{disabled} = 1;
111
            }
112
        }
113
    }
114
115
#              die Dumper(\%from_row);
116
    push @branchfromloop, \%from_row;
117
}
118
119
if ($update && !@errors) {
120
    my @update_recs = map {
121
        my $from = $_->{code};
122
        map { frombranch => $from, tobranch => $_->{code}, cost => $_->{value}, disable_transfer => $_->{disabled} || 0 },
123
            grep { $_->{code} ne $from }
124
            @{ $_->{branchtoloop} };
125
    } @branchfromloop;
126
127
    UpdateTransportCostMatrix(\@update_recs);
128
}
129
130
$template->param(
131
    branchloop => \@branchloop,
132
    branchfromloop => \@branchfromloop,
133
    errors => \@errors,
134
);
135
output_html_with_http_headers $input, $cookie, $template->output;
136
137
exit 0;
138
139
=cut
(-)a/installer/data/mysql/kohastructure.sql (+18 lines)
Lines 1886-1891 CREATE TABLE `reviews` ( -- patron opac comments Link Here
1886
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1886
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1887
1887
1888
--
1888
--
1889
-- Table structure for table `floating_matrix`
1890
--
1891
1892
DROP TABLE IF EXISTS floating_matrix;
1893
CREATE TABLE floating_matrix ( -- Controls should we automatically transfer Items checked in to one branch to the Item's configured normal destination
1894
    id int(11) NOT NULL AUTO_INCREMENT, -- unique id
1895
    from_branch varchar(10) NOT NULL, -- branch where the Item has been checked in
1896
    to_branch varchar(10) NOT NULL, -- where the Item would normally be transferred to
1897
    floating enum('ALWAYS','POSSIBLE','CONDITIONAL') NOT NULL DEFAULT 'ALWAYS', -- type of floating; ALWAYS just skips any transports, POSSIBLE prompts if a transport is needed, CONDITIONAL is like ALWAYS if condition is met
1898
    condition_rules varchar(100), -- if floating = CONDITIONAL, then the special condition to trigger floating.
1899
    CHECK ( from_branch <> to_branch ), -- a dud check, mysql does not support that
1900
    PRIMARY KEY (`id`),
1901
    UNIQUE KEY `floating_matrix_uniq_branches` (`from_branch`,`to_branch`),
1902
    CONSTRAINT floating_matrix_ibfk_1 FOREIGN KEY (from_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE,
1903
    CONSTRAINT floating_matrix_ibfk_2 FOREIGN KEY (to_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE
1904
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1905
1906
--
1889
-- Table structure for table `saved_sql`
1907
-- Table structure for table `saved_sql`
1890
--
1908
--
1891
1909
(-)a/installer/data/mysql/updatedatabase.pl (+31 lines)
Lines 5769-5774 if (C4::Context->preference("Version") < TransformToNum($DBversion)) { Link Here
5769
    SetVersion($DBversion);
5769
    SetVersion($DBversion);
5770
}
5770
}
5771
5771
5772
$DBversion = "3.99.00.XXX";
5773
if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
5774
5775
    #Check if the table has already been CREATEd and possibly CREATE it
5776
    my $dbh = C4::Context->dbh();
5777
    my $sth = $dbh->table_info( '', '', 'floating_matrix', 'TABLE' );
5778
    my $table = $sth->fetchrow_hashref();
5779
    unless ($table) {
5780
        $dbh->do("
5781
        CREATE TABLE floating_matrix ( -- Controls should we automatically transfer Items checked in to one branch to the Item's configured normal destination
5782
            id int(11) NOT NULL AUTO_INCREMENT, -- unique id
5783
            from_branch varchar(10) NOT NULL, -- branch where the Item has been checked in
5784
            to_branch varchar(10) NOT NULL, -- where the Item would normally be transferred to
5785
            floating enum('ALWAYS','POSSIBLE','CONDITIONAL') NOT NULL DEFAULT 'ALWAYS', -- type of floating; ALWAYS just skips any transports, POSSIBLE prompts if a transport is needed, CONDITIONAL is like ALWAYS if condition is met
5786
            condition_rules varchar(100), -- if floating = CONDITIONAL, then the special condition to trigger floating.
5787
            CHECK ( from_branch <> to_branch ), -- a dud check, mysql does not support that
5788
            PRIMARY KEY (`id`),
5789
            UNIQUE KEY `floating_matrix_uniq_branches` (`from_branch`,`to_branch`),
5790
            CONSTRAINT floating_matrix_ibfk_1 FOREIGN KEY (from_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE,
5791
            CONSTRAINT floating_matrix_ibfk_2 FOREIGN KEY (to_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE
5792
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
5793
        ");
5794
        print "Upgrade to $DBversion done (Bug 9525 - group floating rules)\n";
5795
    }
5796
    else {
5797
        print "Upgrade to $DBversion already applied (Bug 9525 - group floating rules)\n";
5798
    }
5799
5800
    SetVersion($DBversion);
5801
}
5802
5772
$DBversion ="3.09.00.038";
5803
$DBversion ="3.09.00.038";
5773
if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
5804
if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
5774
    $dbh->do("ALTER TABLE borrower_attributes CHANGE  attribute  attribute VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL");
5805
    $dbh->do("ALTER TABLE borrower_attributes CHANGE  attribute  attribute VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL");
(-)a/koha-tmpl/intranet-tmpl/prog/en/css/admin/floating-matrix.css (+16 lines)
Line 0 Link Here
1
.disabled-transfer {
2
    background-color: #FF8888;
3
}
4
.branchRule select { width: 100%; }
5
.branchRule select {padding: 0px; margin: 5px 0px;}
6
7
.branchRule input {padding: 0px; margin: 0px;}
8
.branchRule input[type="text"]  { width: 100%; display: none; }
9
.branchRule input[type="submit"] {display:none}
10
11
.branchRule.selected {color: #1b93d3; box-shadow:0px 0px 10px;}
12
.branchRule.selected input[type="submit"] {display:initial;}
13
.branchRule.selected input[type="text"] {display:initial;  border: 0px none;}
14
15
.failedAjax {box-shadow:0px 0px 30px 20px #ff0000}
16
.TEMPLATE {display: none; visibility: hidden;}
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/admin/floating-matrix-floatingTypes.inc (+26 lines)
Line 0 Link Here
1
[%# Summon this block to the template by INCLUDEing it like this in the header:
2
        % INCLUDE 'admin/floating-matrix-floatingType.inc' %
3
    You must have the required parameters in the template toolkit parameter-space.
4
%]
5
[%#REQUIRES branchRule (Koha::FloatingMatrix::BranchRule), fm (Koha::FloatingMatrix) %]
6
[% preselectedFloatingType = branchRule.getFloating() %]
7
    <select name="floating">
8
        <option value="DISABLED"[% IF ! preselectedFloatingType %] selected="selected"[% END %]>
9
            Disabled
10
        </option>
11
12
        [% FOR floatingType IN fm.getFloatingTypes() %]
13
            <option value="[% floatingType %]"[% IF floatingType == preselectedFloatingType %] selected="selected"[% END %]>
14
                [% SWITCH floatingType %]
15
                [% CASE 'ALWAYS' %]
16
                    Always
17
                [% CASE 'POSSIBLE' %]
18
                    Possible
19
                [% CASE 'CONDITIONAL' %]
20
                    Conditional
21
                [% CASE %]
22
                    Unknown floating type '[% floatingType %]'
23
                [% END #SWITCH floatingType %]
24
            </option>
25
        [% END #FOR fm.getFloatingTypes() %]
26
    </select>
(-)a/koha-tmpl/intranet-tmpl/prog/en/js/admin/floating-matrix.js (+186 lines)
Line 0 Link Here
1
////Create javascript namespace
2
var FloMax = FloMax || {};
3
4
//// DOCUMENT READY INIT SCRIPTS
5
6
FloMax.branchRules = {}; //Stores all the edited branchRules so we can rollback HTML changes and CRUD over AJAX.
7
8
FloMax.branchRuleDisabledColor = "#e83519";
9
FloMax.branchRuleAlwaysColor = "#219e0e";
10
FloMax.branchRulePossibleColor = "#2626bf";
11
FloMax.branchRuleConditionalColor = "#af21bc";
12
$(document).ready(function() {
13
    //Color cells based on their floatingType
14
    $(".branchRule").each(function(){
15
        FloMax.changeBranchRuleColor(this);
16
    });
17
18
    //Bind action listeners to the floating matrix
19
    $(".branchRule").bind({
20
        click: function() {
21
            //If we are clicking a branchCode element, mark it as selected and remove other selections.
22
            if (! $(this).hasClass('selected')) {
23
                $(".branchRule").removeClass('selected');
24
                $("input[name='conditionRules']").addClass('hidden');
25
26
                $(this).addClass('selected');
27
                FloMax.changeBranchRuleColor(this);
28
                $(this).find("input[name='conditionRules']").removeClass('hidden');
29
            }
30
        },
31
    });
32
    $(".branchRule input[name='cancel']").bind({
33
        click: function(event) {
34
            FloMax.replaceBranchRule( $(this).parents(".branchRule") );
35
            $(this).parents(".branchRule").removeClass('selected');
36
            $("input[name='conditionRules']").addClass('hidden');
37
            event.stopPropagation();
38
        },
39
    });
40
    $(".branchRule input[name='submit']").bind({
41
        click: function(event) {
42
            FloMax.storeBranchRule( $(this).parents(".branchRule") );
43
            $(this).parents(".branchRule").removeClass('selected');
44
            $("input[name='conditionRules']").addClass('hidden');
45
            event.stopPropagation();
46
        },
47
    });
48
    $(".branchRule select").bind({
49
        change: function() {
50
            //When the floatingType changes, so does the color
51
            var branchRule = $(this).parents(".branchRule");
52
            FloMax.changeBranchRuleColor(branchRule);
53
        },
54
    });
55
});
56
////EOF DOCUMENT READY INIT SCRIPTS
57
58
FloMax.changeBranchRuleColor = function (branchRule) {
59
    var floatingType = $(branchRule).find("select[name='floating']").val();
60
    var color = "#000000";
61
    if (floatingType == 'DISABLED') {
62
        color = FloMax.branchRuleDisabledColor;
63
    }
64
    else if (floatingType == 'ALWAYS') {
65
        color = FloMax.branchRuleAlwaysColor;
66
    }
67
    else if (floatingType == 'POSSIBLE') {
68
        color = FloMax.branchRulePossibleColor;
69
    }
70
    else if (floatingType == 'CONDITIONAL') {
71
        color = FloMax.branchRuleConditionalColor;
72
    }
73
    $(branchRule).css('color', color);
74
    $(branchRule).css('background-color', color);
75
}
76
77
FloMax.displayConditionRulesInput = function (branchRule) {
78
    var conditionRulesInput = $(branchRule).find("input[name='conditionRules']");
79
    conditionRulesInput.removeClass('hidden');
80
}
81
82
FloMax.buildBranchRuleFromHTML = function (branchRule) {
83
    //Get fromBranch by looking at the first column at this row.
84
    var siblingCells = $(branchRule).parent().prevAll();
85
    var fromBranch = $(siblingCells).eq(  ($(siblingCells).size())-1  ); //Get the first cell in this row.
86
    fromBranch = $(fromBranch).html();
87
    //Get toBranch by looking at the first row of the current column.
88
    var nthColumn = ($(siblingCells).size()); //Get x-position from all siblings + the column header, into this branchRule
89
    var toBranch = $("#floatingMatrix").find("#fmHeaderRow").children("th").eq( nthColumn ).html();
90
    //Get floating
91
    var floating = $(branchRule).find("select").val();
92
    //Get conditionRules
93
    var conditionRules = $(branchRule).find("input[name='conditionRules']").val();
94
    //Get id
95
    var id;
96
    if ($(branchRule).attr('id')) {
97
        id = $(branchRule).attr('id').substring(3); //Skip characters 'br_'
98
    }
99
    var brJSON = {'fromBranch' : fromBranch,
100
              'toBranch' : toBranch,
101
              'floating' : floating,
102
              'conditionRules' : conditionRules,
103
              'id' : id,
104
    };
105
    return brJSON;
106
}
107
FloMax.storeBranchRule = function (branchRule) {
108
    var brJSON = FloMax.buildBranchRuleFromHTML(branchRule);
109
    FloMax.branchRules[brJSON.fromBranch+'-'+brJSON.toBranch] = brJSON;
110
    FloMax.persistBranchRule(brJSON, branchRule);
111
}
112
FloMax.replaceBranchRule = function (branchRule) {
113
    var brJSON = FloMax.buildBranchRuleFromHTML(branchRule);
114
    var oldBrJSON = FloMax.branchRules[brJSON.fromBranch+'-'+brJSON.toBranch];
115
    var newBranchRule = FloMax.newBranchRuleHTML(oldBrJSON);
116
    branchRule.replaceWith(newBranchRule);
117
}
118
FloMax.resetBranchRule = function (branchRule) {
119
    branchRule.replaceWith(newBranchRuleHTML());
120
}
121
FloMax.newBranchRuleHTML = function (branchRuleJSON) {
122
    var branchRuleHTML = $("#br_TEMPLATE").clone('withDataAndElements');
123
    if (branchRuleJSON && branchRuleJSON.id) {
124
        $(branchRuleHTML).attr('id','br_'+branchRuleJSON.id);
125
    }
126
    else {
127
        $(branchRuleHTML).removeAttr('id');
128
    }
129
    if (branchRuleJSON && branchRuleJSON.conditionRules) {
130
        $(branchRuleHTML).find("input[name='conditionRules']").removeClass('hidden').val(branchRuleJSON.conditionRules);
131
    }
132
    else {
133
        $(branchRuleHTML).find("input[name='conditionRules']").removeClass('hidden').val('');
134
    }
135
    if (branchRuleJSON && branchRuleJSON.floating) {
136
        $(branchRuleHTML).find("select").val(  branchRuleJSON.floating || 'DISABLED'  );
137
    }
138
    else {
139
        $(branchRuleHTML).find("select").val(  'DISABLED'  );
140
    }
141
    $(branchRuleHTML).removeClass('TEMPLATE');
142
    FloMax.changeBranchRuleColor(branchRuleHTML);
143
    return branchRuleHTML;
144
}
145
/**
146
 *     var brJSON = FloMax.buildBranchRuleFromHTML(branchRule);
147
 *     FloMax.persistBranchRule(brJSON);
148
 *
149
 * INSERTs or UPDATEs or DELETEs the JSON:ified branchRule to the Koha DB
150
 * @param {object} brJSON - JSON:ified object representation of a branchRule
151
 *                          from buildBranchRuleFromHTML()
152
 * @param {object} branchRule - the HTML element matching class ".branchRule".
153
 *                          Used to target display modifications to it.
154
 */
155
FloMax.persistBranchRule = function (brJSON, branchRule) {
156
    if (brJSON.floating == 'DISABLED') {
157
        brJSON.delete = 1; //A hack to trick Perl CGI to understand this is a HTTP DELETE-verb.
158
        $.ajax('floating-matrix-api.pl',
159
               {method : 'DELETE',
160
                data : brJSON,
161
                dataType : 'json',
162
        }).done(function(data, textStatus, jqXHR){
163
            FloMax.resetBranchRule(branchRule);
164
            $(branchRule).removeClass('failedAjax');
165
            //alert("Saving floating rule "+brJSON.fromBranch+"-"+brJSON.toBranch+" OK, because of the following error:\n"+data.status+" "+data.statusText);
166
        }).fail(function (data, textStatus, jqXHR) {
167
            $(branchRule).addClass('failedAjax');
168
            var error = $.parseJSON(data.responseText); //Pass the error as JSON so we don't trigger the default Koha error pages.
169
            alert("Deleting floating rule "+brJSON.fromBranch+"-"+brJSON.toBranch+" failed, because of the following error:\n"+data.status+" "+data.statusText+"\n"+"More specific error: "+error.error);
170
        });
171
    }
172
    else {
173
        $.ajax('floating-matrix-api.pl',
174
               {method : 'POST',
175
                data : brJSON,
176
                dataType : 'json',
177
        }).done(function(data, textStatus, jqXHR){
178
            $(branchRule).removeClass('failedAjax');
179
            $(branchRule).attr('id', 'br_'+data.id);
180
        }).fail(function (data, textStatus, jqXHR) {
181
            $(branchRule).addClass('failedAjax');
182
            var error = $.parseJSON(data.responseText); //Pass the error as JSON so we don't trigger the default Koha error pages.
183
            alert("Saving floating rule "+brJSON.fromBranch+"-"+brJSON.toBranch+" failed, because of the following error:\n"+data.status+" "+data.statusText+"\n"+"More specific error: "+error.error);
184
        });
185
    }
186
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+2 lines)
Lines 50-55 Link Here
50
    <dd>Define extended attributes (identifiers and statistical categories) for patron records</dd>
50
    <dd>Define extended attributes (identifiers and statistical categories) for patron records</dd>
51
    <dt><a href="/cgi-bin/koha/admin/branch_transfer_limits.pl">Library transfer limits</a></dt>
51
    <dt><a href="/cgi-bin/koha/admin/branch_transfer_limits.pl">Library transfer limits</a></dt>
52
	<dd>Limit the ability to transfer items between libraries based on the library sending, the library receiving, and the item type involved. These rules only go into effect if the preference UseBranchTransferLimits is set to ON.</dd>
52
	<dd>Limit the ability to transfer items between libraries based on the library sending, the library receiving, and the item type involved. These rules only go into effect if the preference UseBranchTransferLimits is set to ON.</dd>
53
    <dt><a href="/cgi-bin/koha/admin/floating-matrix.pl">Floating matrix</a></dt>
54
    <dd>Define floating rules between branches</dd>
53
    <dt><a href="/cgi-bin/koha/admin/transport-cost-matrix.pl">Transport cost matrix</a></dt>
55
    <dt><a href="/cgi-bin/koha/admin/transport-cost-matrix.pl">Transport cost matrix</a></dt>
54
    <dd>Define transport costs between branches</dd>
56
    <dd>Define transport costs between branches</dd>
55
    <dt><a href="/cgi-bin/koha/admin/item_circulation_alerts.pl">Item circulation alerts</a></dt>
57
    <dt><a href="/cgi-bin/koha/admin/item_circulation_alerts.pl">Item circulation alerts</a></dt>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/floating-matrix.tt (+97 lines)
Line 0 Link Here
1
[% INCLUDE 'doc-head-open.inc' %]
2
<title>Koha &rsaquo; Administration &rsaquo; Floating matrix</title>
3
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/admin/floating-matrix.css" />
4
[% INCLUDE 'doc-head-close.inc' %]
5
6
</head>
7
<body>
8
[% INCLUDE 'header.inc' %]
9
[% INCLUDE 'cat-search.inc' %]
10
11
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> &rsaquo; Floating matrix</div>
12
13
<div id="doc3" class="yui-t1">
14
15
<div id="bd">
16
    <div id="yui-main">
17
    <div class="yui-b">
18
        <h1 class="parameters">
19
                Defining floating rules between libraries
20
        </h1>
21
22
        <fieldset>
23
            <div class="help">
24
                <p>There are four types of floating:</p>
25
                <ul>
26
                    <li>Disabled, denoted by red cells</li>
27
                    <li>Always, denoted by green cells. This means that Items checked-in to 'From' branch, which would normally be transfered to 'To' branch, stay in the checked-in branch.</li>
28
                    <li>Possible, denoted by blue cells. This is like always, except the librarian is prompted if he/she wants to initiate a transfer or not. Default behaviour is not to transfer.</li>
29
                    <li>Conditional, denoted by purple cells. This is like always, but only when the given condition matches.
30
                        <p class="example">
31
                            itype eq BK - this will always float all Items with item type 'BK'<br/>
32
                            itype eq BK or itype eq CR - this will always float all Items with item type 'BK' or 'CR'<br/>
33
                            ccode eq FLOAT - this will float all Items with a collection code 'FLOAT'<br/>
34
                            itype ne CR and permanent_location eq CART  - this will float all Items not of item type CR and whose permanent location (set when the Item's location is set) is 'CART'<br/>
35
                            <i>These boolean statements are actually evaluable Perl boolean expressions and target the columns in the koha.items-table. See <a href="http://schema.koha-community.org/tables/items.html">here</a> for available data columns.</i>
36
                        </p>
37
                    </li>
38
                </ul>
39
            </div>
40
41
            <table id="floatingMatrix">
42
                <tr id="fmHeaderRow">
43
                    <th>From \ To</th>
44
                    [% FOR branchcode IN branches.keys.sort; branch = branches.$branchcode %]
45
                        <th title="[% branch.branchname %]">[% branchcode %]</th>
46
                    [% END %]
47
                </tr>
48
                [% FOR fromBranchCode IN branches.keys.sort; fromBranch = branches.$fromBranchCode %]
49
                <tr>
50
                    <th title="[% fromBranch.branchname %]">[% fromBranchCode %]</th>
51
                    [% FOR toBranchCode IN branches.keys.sort; toBranch = branches.$toBranchCode; branchRule = fm.getBranchRule(fromBranchCode, toBranchCode) %]
52
                    <td>
53
                        [% IF toBranchCode == fromBranchCode %]
54
                            &nbsp;
55
                        [% ELSE %]
56
                            <div class="branchRule"[% IF branchRule.getId %] id="br_[% branchRule.getId %]"[% END %]>
57
58
                                [% INCLUDE 'admin/floating-matrix-floatingTypes.inc' %]
59
60
                                <input name="conditionRules" type="text" value="[% branchRule.getConditionRules() %]" class="hidden"/>
61
                                <div>
62
                                    <input name="submit" type="submit" value="%"/>
63
                                    <input name="cancel" type="submit" value="V"/>
64
                                </div>
65
                            </div>
66
                        [% END %]
67
                    </td>
68
                [% END %]
69
                </tr>
70
            [% END %]
71
            </table>
72
        </fieldset>
73
    </div>
74
    </div>
75
<div class="yui-b">
76
[% INCLUDE 'admin-menu.inc' %]
77
</div>
78
</div>
79
80
81
82
<!-- HTML Templates -->
83
<div class="branchRule TEMPLATE" id="br_TEMPLATE">
84
85
    [% INCLUDE 'admin/floating-matrix-floatingTypes.inc' %]
86
87
    <input name="conditionRules" type="text" value="" class="hidden"/>
88
    <div>
89
        <input name="submit" type="submit" value="%"/>
90
        <input name="cancel" type="submit" value="V"/>
91
    </div>
92
</div>
93
94
95
96
<script type="text/javascript" src="[% themelang %]/js/admin/floating-matrix.js"></script>
97
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/t/Cucumber/SImpls/FloatingMatrix.pm (+197 lines)
Line 0 Link Here
1
package SImpls::FloatingMatrix;
2
3
# Copyright Vaara-kirjastot 2015
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
use Carp;
22
23
use Test::More;
24
25
use Try::Tiny;
26
use Scalar::Util qw(blessed);
27
28
use Koha::FloatingMatrix;
29
use Koha::FloatingMatrix::BranchRule;
30
use C4::Items;
31
32
sub addFloatingMatrixRules {
33
    my $C = shift;
34
    my $S = $C->{stash}->{scenario};
35
    my $F = $C->{stash}->{feature};
36
37
    $S->{floatingMatrixRules} = {} unless $S->{floatingMatrixRules};
38
    $F->{floatingMatrixRules} = {} unless $F->{floatingMatrixRules};
39
40
    my $fm = Koha::FloatingMatrix->new();
41
42
    for (my $i=0 ; $i<scalar(@{$C->data()}) ; $i++) {
43
        my $hash = $C->data()->[$i];
44
        my $key = $hash->{fromBranch}.'-'.$hash->{toBranch};
45
        my $fmRule = Koha::FloatingMatrix::BranchRule->new($hash);
46
        $fm->upsertBranchRule($fmRule);
47
48
        $S->{floatingMatrixRules}->{ $key } = $fmRule;
49
        $F->{floatingMatrixRules}->{ $key } = $fmRule;
50
    }
51
    $fm->store();
52
}
53
54
sub deleteAllFloatingMatrixRules {
55
    my $fm = Koha::FloatingMatrix->new();
56
    $fm->deleteAllFloatingMatrixRules();
57
}
58
59
sub checkFloatingMatrixRules {
60
    my $C = shift;
61
    my $S = $C->{stash}->{scenario};
62
    my $F = $C->{stash}->{feature};
63
64
    my $floatingMatrixRules = $S->{floatingMatrixRules};
65
66
    if ($floatingMatrixRules && ref $floatingMatrixRules eq 'HASH') {
67
        my $fm = Koha::FloatingMatrix->new();
68
        foreach my $key (keys %$floatingMatrixRules) {
69
            my $fmbr = $floatingMatrixRules->{$key};
70
            if (blessed $fmbr && $fmbr->isa('Koha::FloatingMatrix::BranchRule')) {
71
                my $newFmbr = $fm->getBranchRule($fmbr->getFromBranch(), $fmbr->getToBranch());
72
73
                #Delete the id from the DB-representation, so we can compare them with the id-less test object.
74
                my $storedId = $fmbr->getId(); #Store the id so we wont lose it from the scenario/feature stashes
75
                $fmbr->setId(undef);
76
                $newFmbr->setId(undef);
77
78
                my $ok = is_deeply($fmbr, $newFmbr, "FloatingMatrixRule fromBranch '".$fmbr->getFromBranch()."' toBranch '".$fmbr->getToBranch."' found deeply");
79
                $fmbr->setId($storedId); #Restore the id after comparison.
80
                $newFmbr->setId($storedId);
81
                last unless $ok;
82
83
                #Delete branch rule from the Koha::FloatingMatrix internal mapping,
84
                # but not from the DB. Change is reverted next time FloatingMatrix is loaded from Koha::Cache
85
                # This way we ensure we don't accidentally match the same rule twice.
86
                $fm->deleteBranchRule($fmbr);
87
            }
88
            else {
89
                last unless ok(0, "Test object is not a 'Koha::FloatingMatrix::BranchRule'");
90
            }
91
        }
92
    }
93
}
94
95
sub When_I_ve_deleted_Floating_matrix_rules_then_cannot_find_them {
96
    my ($C) = shift;
97
    my $S = $C->{stash}->{scenario};
98
99
    my $fm = Koha::FloatingMatrix->new();
100
101
    #1. Make sure the rule we are deleting actually exists first
102
    #2. Delete the rules from FloatingMatrix internal mapping.
103
    #3. UPDATE deletion to DB.
104
    #4. Refresh FloatingMatrix
105
    #5. Check that Rules are really deleted.
106
107
    for (my $i=0 ; $i<scalar(@{$C->data()}) ; $i++) {
108
        my $hash = $C->data()->[$i];
109
110
        my $existingBranchRule = $fm->getBranchRule($hash->{fromBranch}, $hash->{toBranch});
111
        ok(($existingBranchRule && blessed $existingBranchRule && $existingBranchRule->isa('Koha::FloatingMatrix::BranchRule')),
112
            "A branchRule for fromBranch '".$hash->{fromBranch}."' toBranch '".$hash->{toBranch}."' exists before deletion");
113
114
        $fm->deleteBranchRule($existingBranchRule);
115
        $existingBranchRule = $fm->getBranchRule($hash->{fromBranch}, $hash->{toBranch});
116
        ok((not($existingBranchRule)),
117
            "A branchRule for fromBranch '".$hash->{fromBranch}."' toBranch '".$hash->{toBranch}."' deleted from internal map");
118
    }
119
    $fm->store(); #update deletion to DB
120
121
    $fm = Koha::FloatingMatrix->new();
122
123
    for (my $i=0 ; $i<scalar(@{$C->data()}) ; $i++) {
124
        my $hash = $C->data()->[$i];
125
126
        my $existingBranchRule = $fm->getBranchRule($hash->{fromBranch}, $hash->{toBranch});
127
        ok((not($existingBranchRule)),
128
            "A branchRule for fromBranch '".$hash->{fromBranch}."' toBranch '".$hash->{toBranch}."' deleted from DB");
129
    }
130
}
131
132
sub When_I_try_to_add_Floating_matrix_rules_with_bad_values_I_get_errors {
133
    my ($C) = shift;
134
    my $data = $C->data();
135
136
    my $fm = Koha::FloatingMatrix->new();
137
138
    my $error = '';
139
    foreach my $dataElem (@$data) {
140
        try {
141
            my $branchRule = $fm->upsertBranchRule($dataElem);
142
        } catch {
143
            if (blessed($_)){
144
                if ($_->isa('Koha::Exception::BadParameter')) {
145
                    $error = $_->error;
146
                }
147
                else {
148
                    $_->rethrow();
149
                }
150
            }
151
            else {
152
                die $_;
153
            }
154
        };
155
156
        my $es = $dataElem->{errorString};
157
        last unless ok($error =~ /\Q$es\E/, "Adding a bad overdueRule failed. Expecting '$error' to contain '$es'.");
158
    }
159
}
160
161
=head When_test_given_Items_floats_then_see_status
162
163
$C->data() must contain columns
164
  barcode - the Barcode of the Item we are checking for floating
165
  fromBranch - branchcode, from which branch we initiate transfer (typically the check-in branch)
166
  toBranch - branchcode, where we would transfer this Item should we initiate a transfer
167
  floatCheck - one of the floatin_matrix.floating enumerations.
168
               To skip testing the given test data-row, use one of the following floatChecks:
169
                   no_rule  - no floating matrix rule defined for the route, so on floating
170
                   same_branch - no floating
171
                   fail_condition - CONDITIONAL floating failed because the logical expression from 'conditionRules' returned false.
172
=cut
173
174
sub When_test_given_Items_floats_then_see_status {
175
    my ($C) = shift;
176
    my $checks = $C->data(); #Get the checks, which might not have manifested itselves to the branchtransfers
177
    ok(($checks && scalar(@$checks) > 0), "You must give checks as the data");
178
179
    #See which checks are supposed to be execute, and which are just to clarify intent.
180
    my @checksInTable;
181
    foreach my $check (@$checks) {
182
        my $status = $check->{floatCheck};
183
        push @checksInTable, $check if ($status ne 'fail_condition' && $status ne 'same_branch' && $status ne 'no_rule');
184
    }
185
186
    my $fm = Koha::FloatingMatrix->new();
187
188
    ##Check that we get the expected floating value for each test.
189
    foreach my $check (@checksInTable) {
190
        my $item = C4::Items::GetItem(undef, $check->{barcode});
191
        my $floatType = $fm->checkFloating($item, $check->{fromBranch}, $check->{toBranch});
192
193
        last unless ok(($floatType eq $check->{floatCheck}), "Adding a bad overdueRule failed. Expecting '$floatType', got '".$check->{floatCheck}."'.");
194
    }
195
}
196
197
1;
(-)a/t/Cucumber/features/FloatingMatrix/floatingMatrixCRUD.feature (+65 lines)
Line 0 Link Here
1
# Copyright Vaara-kirjastot 2015
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
################################################################################
19
@floatingMatrix
20
Feature: Floating matrix CRUD
21
 We must be able to define the Floating matrix rules to use them.
22
23
 Scenario: Remove all Floating matrix rules so we can test unhindered.
24
   Given there are no Floating matrix rules
25
   Then there are no Floating matrix rules
26
27
 Scenario: Create some default Floating matrix rules
28
   Given a set of Floating matrix rules
29
    | fromBranch | toBranch | floating    | conditionRules        |
30
    | CPL        | FFL      | ALWAYS      |                       |
31
    | CPL        | IPT      | POSSIBLE    |                       |
32
    | FFL        | CPL      | ALWAYS      |                       |
33
    | FFL        | IPT      | CONDITIONAL | itype ne BK           |
34
    | IPT        | FFL      | ALWAYS      |                       |
35
    | IPT        | CPL      | ALWAYS      |                       |
36
   Then I should find the rules from the Floating matrix
37
38
 Scenario: Delete some Floating matrix rules
39
  When I've deleted the following Floating matrix rules, then I cannot find them.
40
    | fromBranch | toBranch |
41
    | IPT        | FFL      |
42
    | IPT        | CPL      |
43
44
 Scenario: Update some Floating matrix rules
45
  Given a set of Floating matrix rules
46
    | fromBranch | toBranch | floating    | conditionRules |
47
    | CPL        | FFL      | POSSIBLE    |                |
48
    | CPL        | IPT      | ALWAYS      |                |
49
    | FFL        | CPL      | CONDITIONAL | itype ne BK    |
50
    | FFL        | IPT      | CONDITIONAL | ccode eq FLOAT |
51
   Then I should find the rules from the Floating matrix
52
53
 Scenario: Intercept bad Floating matrix rules.
54
  When I try to add Floating matrix rules with bad values, I get errors.
55
    | fromBranch | toBranch | floating    | conditionRules         | errorString                                       |
56
    |            | FFL      | POSSIBLE    |                        | No 'fromBranch'                                   |
57
    | CPL        |          | ALWAYS      |                        | No 'toBranch'                                     |
58
    | FFL        | CPL      |             | itype ne BK            | No 'floating'                                     |
59
    | FFL        | IPT      | CONDOM      | ccode eq FLOAT         | Bad enum                                          |
60
    | FFL        | CPL      | CONDITIONAL |                        | No 'conditionRules' when floating = 'CONDITIONAL' |
61
    | FFL        | IPT      | CONDITIONAL | {system('rm -rf /');}; | Not allowed 'conditionRules' characters           |
62
    | CPL        | FFL      | ALWAYS      | permanent_location ne REF and permanent_location ne CART and permanent_location ne REF and permanent_location ne REF | 'conditionRules' text is too long. |
63
64
 Scenario: Tear down any database additions from this feature
65
  When all scenarios are executed, tear down database changes.
(-)a/t/Cucumber/features/FloatingMatrix/floatingMatrixUsage.feature (+104 lines)
Line 0 Link Here
1
# Copyright Vaara-kirjastot 2015
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
################################################################################
19
@floatingMatrix
20
Feature: Floating matrix usage
21
 We need to be able to share collections across branch boundaries.
22
 Eg. a bookmobile and the parent library can float the collection to easily move
23
 Items from the library to the bookmobile and vice-versa.
24
 So we want some Items to stay in the branch where they have been checked-in and
25
 want to prevent some Items from staying in the checked-in branch.
26
 By default if there is no rule, then we use default Koha behaviour.
27
28
 Scenario: Set up feature context
29
  Given the Koha-context, we can proceed with other scenarios.
30
   | firstName  | surname   | branchCode | branchName | userFlags | userEmail          | branchPrinter |
31
   | Olli-Antti | Kivilahti | CPL        | CeePeeLib  | 0         | helpme@example.com |               |
32
  And the following system preferences
33
   | systemPreference    | value |
34
   | AutomaticItemReturn | 1     |
35
  And a set of Borrowers
36
   | cardnumber | branchcode | categorycode | surname | firstname | address | guarantorbarcode | dateofbirth |
37
   | 111A0001   | CPL        | S            | Costly  | Colt      | Strt 11 |                  | 1985-10-10  |
38
   | 222A0002   | FFL        | S            | Costly  | Caleb     | Strt 11 | 111A0001         | 2005-12-12  |
39
  And a set of Biblios
40
   | biblio.title             | biblio.author  | biblio.copyrightdate | biblioitems.isbn | biblioitems.itemtype |
41
   | I wish I met your mother | Pertti Kurikka | 1960                 | 9519671580       | BK                   |
42
  And a set of Items
43
   | barcode  | holdingbranch | homebranch | price | replacementprice | itype | biblioisbn |
44
   | 111N0001 | IPT           | CPL        | 0.50  | 0.50             | BK    | 9519671580 |
45
   | 111N0002 | CPL           | CPL        | 0.50  | 0.50             | BK    | 9519671580 |
46
   | 222N0001 | CPL           | FFL        | 1.50  | 1.50             | BK    | 9519671580 |
47
   | 222N0002 | CPL           | FFL        | 1.50  | 1.50             | BK    | 9519671580 |
48
   | 222N0003 | IPT           | FFL        | 1.50  | 1.50             | BK    | 9519671580 |
49
   | 333N0001 | IPT           | IPT        | 1.50  | 1.50             | BK    | 9519671580 |
50
   | 333N0002 | CPL           | IPT        | 1.50  | 1.50             | CF    | 9519671580 |
51
   | 333N0003 | CPL           | IPT        | 1.50  | 1.50             | BK    | 9519671580 |
52
53
 Scenario: Check if Items float as unit test
54
  Given a set of Floating matrix rules
55
    | fromBranch | toBranch | floating    | conditionRules        |
56
    | CPL        | FFL      | ALWAYS      |                       |
57
    | IPT        | FFL      | POSSIBLE    |                       |
58
    | CPL        | IPT      | CONDITIONAL | itype ne CF           |
59
  When I test if given Items can float, then I see if this feature works!
60
   | barcode  | fromBranch | toBranch | floatCheck     |
61
   | 111N0001 | IPT        | CPL      | no_rule        |
62
   | 111N0002 | CPL        | CPL      | same_branch    |
63
   | 222N0001 | CPL        | FFL      | ALWAYS         |
64
   | 222N0002 | CPL        | FFL      | ALWAYS         |
65
   | 222N0003 | IPT        | FFL      | POSSIBLE       |
66
   | 333N0001 | IPT        | IPT      | same_branch    |
67
   | 333N0002 | CPL        | IPT      | fail_condition |
68
   | 333N0003 | CPL        | IPT      | ALWAYS         |
69
70
 Scenario: Check can Items float or not as unit test.
71
  Given a set of Issues, checked out from the Items' current 'holdingbranch'
72
    | cardnumber | barcode  | daysOverdue |
73
    | 111A0001   | 111N0001 | -7          |
74
    | 111A0001   | 111N0002 | -7          |
75
    | 111A0001   | 222N0001 | -7          |
76
    | 111A0001   | 222N0002 | -7          |
77
    | 111A0001   | 222N0003 | -7          |
78
    | 111A0001   | 333N0001 | -7          |
79
    | 111A0001   | 333N0002 | -7          |
80
    | 111A0001   | 333N0003 | -7          |
81
  And a set of Floating matrix rules
82
    | fromBranch | toBranch | floating    | conditionRules |
83
    | CPL        | FFL      | ALWAYS      |                |
84
    | IPT        | FFL      | POSSIBLE    |                |
85
    | CPL        | IPT      | CONDITIONAL | itype ne CF    |
86
  When checked-out Items are checked-in to their 'holdingbranch'
87
  Then the following Items are in-transit
88
    | barcode  | fromBranch | toBranch |
89
    | 111N0001 | IPT        | CPL      |
90
    | 333N0002 | CPL        | IPT      |
91
  #This Item is checked in to the same branch so we don't transfer it
92
  # | 111N0002 | CPL        | CPL      |
93
  #This route is always set to float.
94
  # | 222N0001 | CPL        | FFL      |
95
  # | 222N0002 | CPL        | FFL      |
96
  #This route is set to possibly float, so by default it floats.
97
  # | 222N0003 | IPT        | FFL      |
98
  #There is no route definition, so we transfer, but we don't transfer from home to home.
99
  # | 333N0001 | IPT        | IPT      |
100
  #Conditional route definition, this matches the condition so we don't transfer.
101
  # | 333N0003 | CPL        | IPT      |
102
103
 Scenario: Tear down any database additions from this feature
104
  When all scenarios are executed, tear down database changes.
(-)a/t/Cucumber/steps/common_steps.pl (+1 lines)
Lines 41-44 When qr/all scenarios are executed, tear down database changes./, sub { Link Here
41
    SImpls::MessageQueues::deleteAllMessageQueues($C);
41
    SImpls::MessageQueues::deleteAllMessageQueues($C);
42
    SImpls::LetterTemplates::deleteLetterTemplates($C);
42
    SImpls::LetterTemplates::deleteLetterTemplates($C);
43
    SImpls::SystemPreferences::rollbackSystemPreferences($C);
43
    SImpls::SystemPreferences::rollbackSystemPreferences($C);
44
    SImpls::FloatingMatrix::deleteAllFloatingMatrixRules($C);
44
};
45
};
(-)a/t/Cucumber/steps/floatingMatrix_steps.pl (-1 / +54 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright Vaara-kirjastot 2015
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
use Test::More;
22
use Test::BDD::Cucumber::StepFile;
23
24
use SImpls::FloatingMatrix;
25
26
Given qr/there are no Floating matrix rules/, sub {
27
    SImpls::FloatingMatrix::deleteAllFloatingMatrixRules(@_);
28
};
29
30
Given qr/a set of Floating matrix rules/, sub {
31
    SImpls::FloatingMatrix::addFloatingMatrixRules(@_);
32
};
33
34
When qr/I've deleted the following Floating matrix rules, then I cannot find them./, sub {
35
    SImpls::FloatingMatrix::When_I_ve_deleted_Floating_matrix_rules_then_cannot_find_them(@_);
36
};
37
38
When qr/I try to add Floating matrix rules with bad values, I get errors./, sub {
39
    SImpls::FloatingMatrix::When_I_try_to_add_Floating_matrix_rules_with_bad_values_I_get_errors(@_);
40
};
41
42
When qr/I test if given Items can float, then I see if this feature works!/, sub {
43
    SImpls::FloatingMatrix::When_test_given_Items_floats_then_see_status(@_);
44
};
45
46
Then qr/I should find the rules from the Floating matrix/, sub {
47
    SImpls::FloatingMatrix::checkFloatingMatrixRules(@_);
48
};
49
50
Then qr/there are no Floating matrix rules/, sub {
51
    my $schema = Koha::Database->new()->schema();
52
    my @fmRules = $schema->resultset('FloatingMatrix')->search({})->all;
53
    is((scalar(@fmRules)), 0, "Cleaning Floating matrix rules succeeded");
54
};

Return to bug 9525