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

(-)a/C4/Circulation.pm (-3 / +21 lines)
Lines 37-42 use C4::Overdues qw(CalcFine UpdateFine get_chargeable_units); Link Here
37
use C4::RotatingCollections qw(GetCollectionItemBranches);
37
use C4::RotatingCollections qw(GetCollectionItemBranches);
38
use Algorithm::CheckDigits;
38
use Algorithm::CheckDigits;
39
39
40
use Koha::FloatingMatrix;
40
use Data::Dumper;
41
use Data::Dumper;
41
use Koha::Account;
42
use Koha::Account;
42
use Koha::AuthorisedValues;
43
use Koha::AuthorisedValues;
Lines 2143-2155 sub AddReturn { Link Here
2143
        $request->status('RET') if $request;
2144
        $request->status('RET') if $request;
2144
    }
2145
    }
2145
2146
2147
    my $floatingType = Koha::FloatingMatrix::CheckFloating($item, $branch, $returnbranch);
2146
    # Transfer to returnbranch if Automatic transfer set or append message NeedsTransfer
2148
    # Transfer to returnbranch if Automatic transfer set or append message NeedsTransfer
2147
    if (!$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $returnbranch) and not $messages->{'WrongTransfer'}){
2149
    if (!$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $returnbranch) and not $messages->{'WrongTransfer'} && (not($floatingType) || $floatingType eq 'POSSIBLE')){
2148
        my $BranchTransferLimitsType = C4::Context->preference("BranchTransferLimitsType") eq 'itemtype' ? 'effective_itemtype' : 'ccode';
2150
        my $BranchTransferLimitsType = C4::Context->preference("BranchTransferLimitsType") eq 'itemtype' ? 'effective_itemtype' : 'ccode';
2149
        if  (C4::Context->preference("AutomaticItemReturn"    ) or
2151
        if  ( not($floatingType && $floatingType eq 'POSSIBLE') and
2152
            (C4::Context->preference("AutomaticItemReturn"    ) or
2150
            (C4::Context->preference("UseBranchTransferLimits") and
2153
            (C4::Context->preference("UseBranchTransferLimits") and
2151
             ! IsBranchTransferAllowed($branch, $returnbranch, $item->$BranchTransferLimitsType )
2154
             ! IsBranchTransferAllowed($branch, $returnbranch, $item->$BranchTransferLimitsType )
2152
           )) {
2155
           ))) {
2153
            $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s, %s)", $item->itemnumber,$branch, $returnbranch, $transfer_trigger;
2156
            $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s, %s)", $item->itemnumber,$branch, $returnbranch, $transfer_trigger;
2154
            $debug and warn "item: " . Dumper($item->unblessed);
2157
            $debug and warn "item: " . Dumper($item->unblessed);
2155
            ModItemTransfer($item->itemnumber, $branch, $returnbranch, $transfer_trigger);
2158
            ModItemTransfer($item->itemnumber, $branch, $returnbranch, $transfer_trigger);
Lines 2159-2164 sub AddReturn { Link Here
2159
            $messages->{'TransferTrigger'} = $transfer_trigger;
2162
            $messages->{'TransferTrigger'} = $transfer_trigger;
2160
        }
2163
        }
2161
    }
2164
    }
2165
    #This elsif-clause copypastes the upper if-clause. This is horrible and I cry, but cannot refactor this mess now :( due to Koha upstream master stuff looming.
2166
    elsif (!$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'})
2167
        and !$resfound and ($branch ne $returnbranch)
2168
        and not($messages->{'WrongTransfer'})
2169
        && $floatingType){ #So if we would otherwise transfer, but we have a floating rule overriding it.
2170
                           #We can infer that the transfer was averted because of a floating rule.
2171
        #Make sure we dont log the same floating denial multiple times
2172
        #So first check if we already logged this operation.
2173
        my $logs = C4::Log::GetLogs(DateTime->now(time_zone => C4::Context->tz),
2174
                                    DateTime->now(time_zone => C4::Context->tz),
2175
                                    undef, ['FLOATING'], undef, $item->itemnumber, "$branch -> $returnbranch denied");
2176
        if (not($logs) || (ref($logs) eq 'ARRAY' && scalar(@$logs) == 0)) {
2177
            C4::Log::logaction("FLOATING", $floatingType, $item->itemnumber, "$branch -> $returnbranch denied");
2178
        }
2179
    }
2162
2180
2163
    if ( C4::Context->preference('ClaimReturnedLostValue') ) {
2181
    if ( C4::Context->preference('ClaimReturnedLostValue') ) {
2164
        my $claims = Koha::Checkouts::ReturnClaims->search(
2182
        my $claims = Koha::Checkouts::ReturnClaims->search(
(-)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::Caches;
32
use Koha::Database;
33
34
use Koha::FloatingMatrix::BranchRule;
35
36
use Koha::Exceptions::BadParameter;
37
38
my $cacheExpiryTime = 1200;
39
40
=head new
41
42
    my $fm = Koha::FloatingMatrix->new();
43
44
Finds a FloatingMatrix from Koha::Caches or instantiates a new one.
45
46
=cut
47
48
sub new {
49
    my ($class) = @_;
50
51
    my $cache = Koha::Caches->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::Exceptions::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::Exceptions::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::Exceptions::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::Caches
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::Caches->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::Caches->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::Exceptions::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::Exceptions::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::Exceptions::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::Exceptions::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::Exceptions::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::Exceptions::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::Exceptions::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::Exceptions::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::Exceptions::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/admin/floating-matrix-api.pl (+98 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
use C4::Items;
29
30
use Koha::Items;
31
use Koha::FloatingMatrix;
32
33
my $input = new CGI;
34
35
#my ( $auth_status, $sessionID ) =
36
#  check_cookie_auth( $input->cookie('CGISESSID'),
37
#    { circulate => 'circulate_remaining_permissions' } );
38
my ( $auth_status, $sessionID ) =
39
  check_cookie_auth( $input->cookie('CGISESSID'),
40
                    {
41
                        parameters => 1,
42
                    } );
43
44
45
binmode STDOUT, ":encoding(UTF-8)";
46
47
my $fm = Koha::FloatingMatrix->new();
48
49
my $data = $input->Vars();
50
if ($data) {
51
    try {
52
        ##If we are getting a DELETE-request, we DELETE (CGI doesn't know what DELETE is :(((
53
        if ($data->{delete}) {
54
            $fm->deleteBranchRule($data->{fromBranch}, $data->{toBranch});
55
            $fm->store();
56
        }
57
        elsif ($data->{test}) {
58
            my $item = Koha::Items->find({ barcode => $data->{barcode} });
59
            if ($data->{barcode} && $item) {
60
                $data->{testResult} = $fm->checkFloating($item, $data->{fromBranch}, $data->{toBranch});
61
            }
62
            else {
63
                Koha::Exceptions::BadParameter->throw(error => "No Item found using barcode '".$data->{barcode}."'");
64
            }
65
        }
66
        ##If we are getting a POST-request, we UPSERT
67
        else {
68
            $fm->upsertBranchRule($data);
69
            $fm->store();
70
        }
71
    } catch {
72
        if (blessed $_ && $_->isa('Koha::Exceptions::BadParameter')) {
73
            respondBadParameterException($_);
74
        }
75
        else {
76
            die $_;
77
        }
78
    };
79
80
    print $input->header( -type => 'text/json',
81
                          -charset => 'UTF-8',
82
                          -status => "200 OK");
83
    print JSON::XS->new->utf8->convert_blessed->encode($data);
84
}
85
else {
86
    print $input->header( -type => 'text/plain',
87
                          -charset => 'UTF-8',
88
                          -status => "405 Method Not Allowed");
89
}
90
91
sub respondBadParameterException {
92
    my ($e) = @_;
93
    print $input->header( -type => 'text/json',
94
                          -charset => 'UTF-8',
95
                          -status => "400 Bad Request");
96
    print JSON::XS->new->utf8->encode({error => $e->as_string()});
97
    exit 1;
98
}
(-)a/admin/floating-matrix.pl (+138 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
25
use Koha::FloatingMatrix;
26
27
my $input = new CGI;
28
29
my ($template, $loggedinuser, $cookie)
30
    = get_template_and_user({template_name => "admin/floating-matrix.tt",
31
                            query => $input,
32
                            type => "intranet",
33
                            authnotrequired => 0,
34
                            flagsrequired => {parameters => 1},
35
                            debug => 1,
36
                            });
37
38
my $operation = 'showMatrix'; #Default op is show.
39
40
my $fm = Koha::FloatingMatrix->new();
41
my $branches = Koha::Libraries->search();
42
43
$template->param(
44
    fm => $fm,
45
    branches => $branches,
46
);
47
48
49
output_html_with_http_headers $input, $cookie, $template->output;
50
51
exit 0;
52
53
=head
54
my $update = $input->param('op') eq 'set-cost-matrix';
55
56
my ($cost_matrix, $have_matrix);
57
unless ($update) {
58
    $cost_matrix = TransportCostMatrix();
59
    $have_matrix = keys %$cost_matrix if $cost_matrix;
60
}
61
62
my $branches = GetBranches();
63
my @branchloop = map { code => $_,
64
                       name => $branches->{$_}->{'branchname'} },
65
                 sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} }
66
                 keys %$branches;
67
my (@branchfromloop, @cost, @errors);
68
foreach my $branchfrom ( @branchloop ) {
69
    my $fromcode = $branchfrom->{code};
70
71
    my %from_row = ( code => $fromcode, name => $branchfrom->{name} );
72
    foreach my $branchto ( @branchloop ) {
73
        my $tocode = $branchto->{code};
74
75
        my %from_to_input_def = ( code => $tocode, name => $branchto->{name} );
76
        push @{ $from_row{branchtoloop} }, \%from_to_input_def;
77
78
        if ($fromcode eq $tocode) {
79
            $from_to_input_def{skip} = 1;
80
            next;
81
        }
82
83
        (my $from_to = "${fromcode}_${tocode}") =~ s/\W//go;
84
         $from_to_input_def{id} = $from_to;
85
        my $input_name   = "cost_$from_to";
86
        my $disable_name = "disable_$from_to";
87
88
        if ($update) {
89
            my $value = $from_to_input_def{value} = $input->param($input_name);
90
            if ( $input->param($disable_name) ) {
91
                $from_to_input_def{disabled} = 1;
92
            }
93
            else {
94
                push @errors, "$from_row{name} -> $from_to_input_def{name}"
95
                  unless $value =~ /\d/o && $value >= 0.0;
96
            }
97
        }
98
        else {
99
            if ($have_matrix) {
100
                if ( my $cell = $cost_matrix->{$tocode}{$fromcode} ) {
101
                    $from_to_input_def{value} = $cell->{cost};
102
                    $from_to_input_def{disabled} = 1 if $cell->{disable_transfer};
103
                } else {
104
                    # matrix has been previously initialized, but a branch referenced here was created afterward.
105
                    $from_to_input_def{disabled} = 1;
106
                }
107
            } else {
108
                # First time initializing the matrix
109
                $from_to_input_def{disabled} = 1;
110
            }
111
        }
112
    }
113
114
#              die Dumper(\%from_row);
115
    push @branchfromloop, \%from_row;
116
}
117
118
if ($update && !@errors) {
119
    my @update_recs = map {
120
        my $from = $_->{code};
121
        map { frombranch => $from, tobranch => $_->{code}, cost => $_->{value}, disable_transfer => $_->{disabled} || 0 },
122
            grep { $_->{code} ne $from }
123
            @{ $_->{branchtoloop} };
124
    } @branchfromloop;
125
126
    UpdateTransportCostMatrix(\@update_recs);
127
}
128
129
$template->param(
130
    branchloop => \@branchloop,
131
    branchfromloop => \@branchfromloop,
132
    errors => \@errors,
133
);
134
output_html_with_http_headers $input, $cookie, $template->output;
135
136
exit 0;
137
138
=cut
(-)a/installer/data/mysql/atomicupdate/Bug-9525_group_floating_rules.perl (+26 lines)
Line 0 Link Here
1
$DBversion = 'XXX';  # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
4
    #Check if the table has already been CREATEd and possibly CREATE it
5
    my $dbh = C4::Context->dbh();
6
    my $sth = $dbh->table_info( '', '', 'floating_matrix', 'TABLE' );
7
    my $table = $sth->fetchrow_hashref();
8
    unless ($table) {
9
        $dbh->do("
10
        CREATE TABLE floating_matrix ( -- Controls should we automatically transfer Items checked in to one branch to the Item's configured normal destination
11
            id int(11) NOT NULL AUTO_INCREMENT, -- unique id
12
            from_branch varchar(10) NOT NULL, -- branch where the Item has been checked in
13
            to_branch varchar(10) NOT NULL, -- where the Item would normally be transferred to
14
            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
15
            condition_rules varchar(100), -- if floating = CONDITIONAL, then the special condition to trigger floating.
16
            CHECK ( from_branch <> to_branch ), -- a dud check, mysql does not support that
17
            PRIMARY KEY (`id`),
18
            UNIQUE KEY `floating_matrix_uniq_branches` (`from_branch`,`to_branch`),
19
            CONSTRAINT floating_matrix_ibfk_1 FOREIGN KEY (from_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE,
20
            CONSTRAINT floating_matrix_ibfk_2 FOREIGN KEY (to_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE
21
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
22
        ");
23
        SetVersion( $DBversion );
24
        print "Upgrade to $DBversion done (Bug 9525 - group floating rules)\n";
25
    }
26
}
(-)a/installer/data/mysql/kohastructure.sql (+18 lines)
Lines 287-292 CREATE TABLE IF NOT EXISTS branches_overdrive ( Link Here
287
  CONSTRAINT `branches_overdrive_ibfk_1` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE
287
  CONSTRAINT `branches_overdrive_ibfk_1` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE
288
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
288
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
289
289
290
--
291
-- Table structure for table `floating_matrix`
292
--
293
294
DROP TABLE IF EXISTS floating_matrix;
295
CREATE TABLE floating_matrix ( -- Controls should we automatically transfer Items checked in to one branch to the Item's configured normal destination
296
    id int(11) NOT NULL AUTO_INCREMENT, -- unique id
297
    from_branch varchar(10) NOT NULL, -- branch where the Item has been checked in
298
    to_branch varchar(10) NOT NULL, -- where the Item would normally be transferred to
299
    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
300
    condition_rules varchar(100), -- if floating = CONDITIONAL, then the special condition to trigger floating.
301
    CHECK ( from_branch <> to_branch ), -- a dud check, mysql does not support that
302
    PRIMARY KEY (`id`),
303
    UNIQUE KEY `floating_matrix_uniq_branches` (`from_branch`,`to_branch`),
304
    CONSTRAINT floating_matrix_ibfk_1 FOREIGN KEY (from_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE,
305
    CONSTRAINT floating_matrix_ibfk_2 FOREIGN KEY (to_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE
306
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
307
290
--
308
--
291
-- Table structure for table `browser`
309
-- Table structure for table `browser`
292
--
310
--
(-)a/koha-tmpl/intranet-tmpl/prog/css/floating-matrix-loader.css (+78 lines)
Line 0 Link Here
1
.cssload-loader {
2
	position: relative;
3
	left: calc(50% - 8px);
4
	width: 16px;
5
	height: 16px;
6
	border-radius: 50%;
7
		-moz-border-radius: 50%;
8
	perspective: 200px;
9
    margin-top: 3px;
10
    visibility: hidden;
11
}
12
13
.cssload-inner {
14
	position: absolute;
15
	width: 100%;
16
	height: 100%;
17
	box-sizing: border-box;
18
		-moz-box-sizing: border-box;
19
	border-radius: 50%;
20
		-moz-border-radius: 50%;
21
}
22
23
.cssload-inner.cssload-one {
24
	left: 0%;
25
	top: 0%;
26
	animation: cssload-rotate-one 1.3s linear infinite;
27
		-moz-animation: cssload-rotate-one 1.3s linear infinite;
28
	border-bottom: 2px solid rgb(0,0,0);
29
}
30
31
.cssload-inner.cssload-two {
32
	right: 0%;
33
	top: 0%;
34
	animation: cssload-rotate-two 1.3s linear infinite;
35
		-moz-animation: cssload-rotate-two 1.3s linear infinite;
36
	border-right: 2px solid rgb(0,0,0);
37
}
38
39
.cssload-inner.cssload-three {
40
	right: 0%;
41
	bottom: 0%;
42
	animation: cssload-rotate-three 1.3s linear infinite;
43
		-moz-animation: cssload-rotate-three 1.3s linear infinite;
44
	border-top: 2px solid rgba(0,0,0,0.99);
45
}
46
47
48
49
50
51
52
53
@keyframes cssload-rotate-one {
54
	0% {
55
		transform: rotateX(35deg) rotateY(-45deg) rotateZ(0deg);
56
	}
57
	100% {
58
		transform: rotateX(35deg) rotateY(-45deg) rotateZ(360deg);
59
	}
60
}
61
62
@keyframes cssload-rotate-two {
63
	0% {
64
		transform: rotateX(50deg) rotateY(10deg) rotateZ(0deg);
65
	}
66
	100% {
67
		transform: rotateX(50deg) rotateY(10deg) rotateZ(360deg);
68
	}
69
}
70
71
@keyframes cssload-rotate-three {
72
	0% {
73
		transform: rotateX(35deg) rotateY(55deg) rotateZ(0deg);
74
	}
75
	100% {
76
		transform: rotateX(35deg) rotateY(55deg) rotateZ(360deg);
77
	}
78
}
(-)a/koha-tmpl/intranet-tmpl/prog/css/floating-matrix.css (+24 lines)
Line 0 Link Here
1
.disabled-transfer {
2
    background-color: #FF8888;
3
}
4
5
#testResulDisplay { width: 20px;
6
                    height: 22px;
7
                    background-color: #B9D8D9;
8
                    vertical-align: middle;
9
                    display: inline-block;
10
}
11
12
.branchRule select { width: 100%; }
13
.branchRule select {padding: 0px; margin: 5px 0px;}
14
15
.branchRule input {padding: 0px; margin: 0px;}
16
.branchRule input[type="text"]  { width: 100%; display: none; }
17
.branchRule input[type="submit"] {display:none}
18
19
.branchRule.selected {color: #1b93d3; box-shadow:0px 0px 10px;}
20
.branchRule.selected input[type="submit"] {display:initial;}
21
.branchRule.selected input[type="text"] {display:initial;  border: 0px none;}
22
23
.failedAjax {box-shadow:0px 0px 30px 20px #ff0000}
24
.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/modules/admin/admin-home.tt (+2 lines)
Lines 95-100 Link Here
95
                [% IF ( CAN_user_parameters_manage_transfers ) %]
95
                [% IF ( CAN_user_parameters_manage_transfers ) %]
96
                    <dt><a href="/cgi-bin/koha/admin/branch_transfer_limits.pl">Library transfer limits</a></dt>
96
                    <dt><a href="/cgi-bin/koha/admin/branch_transfer_limits.pl">Library transfer limits</a></dt>
97
                    <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>
97
                    <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>
98
                    <dt><a href="/cgi-bin/koha/admin/floating-matrix.pl">Floating matrix</a></dt>
99
                    <dd>Define floating rules between branches</dd>
98
                    <dt><a href="/cgi-bin/koha/admin/transport-cost-matrix.pl">Transport cost matrix</a></dt>
100
                    <dt><a href="/cgi-bin/koha/admin/transport-cost-matrix.pl">Transport cost matrix</a></dt>
99
                    <dd>Define transport costs between branches</dd>
101
                    <dd>Define transport costs between branches</dd>
100
                [% END %]
102
                [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/floating-matrix.tt (+128 lines)
Line 0 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% INCLUDE 'doc-head-open.inc' %]
4
<title>Koha &rsaquo; Administration &rsaquo; Floating matrix</title>
5
[% Asset.css("css/floating-matrix.css") | $raw %]
6
[% Asset.css("css/floating-matrix-loader.css") | $raw %]
7
[% INCLUDE 'doc-head-close.inc' %]
8
9
</head>
10
<body>
11
[% INCLUDE 'header.inc' %]
12
[% INCLUDE 'cat-search.inc' %]
13
14
<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>
15
16
<div class="main container-fluid">
17
18
    <div class="row">
19
        <div class="col-sm-10 col-sm-push-2">
20
            <main>
21
                <h1 class="parameters">
22
                        Defining floating rules between libraries
23
                </h1>
24
25
                <fieldset>
26
                    <div class="help">
27
                        <p>There are four types of floating:</p>
28
                        <ul>
29
                            <li>Disabled, denoted by red cells</li>
30
                            <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>
31
                            <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>
32
                            <li>Conditional, denoted by purple cells. This is like always, but only when the given condition matches.
33
                                <p class="example">
34
                                    itype eq BK - this will always float all Items with item type 'BK'<br/>
35
                                    itype eq BK or itype eq CR - this will always float all Items with item type 'BK' or 'CR'<br/>
36
                                    ccode eq FLOAT - this will float all Items with a collection code 'FLOAT'<br/>
37
                                    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/>
38
                                    <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>
39
                                </p>
40
                            </li>
41
                        </ul>
42
                    </div>
43
44
                    <fieldset>
45
                    <legend>Floating rule tester</legend>
46
                    <div id="floatingRuleTester">
47
                        <label for="testerFromBranch">Choose the origin library:</label>
48
                        <select id="testerFromBranch">
49
                        [% FOREACH branch IN branches %]
50
                            <option value="[% branch.branchcode %]">[% branch.branchcode %]</option>
51
                        [% END %]
52
                        </select>
53
                        <label for="testerToBranch">Choose the destination library:</label>
54
                        <select id="testerToBranch">
55
                        [% FOREACH branch IN branches%]
56
                            <option value="[% branch.branchcode %]">[% branch.branchcode %]</option>
57
                        [% END %]
58
                        </select>
59
                        <input id="testerBarcode" type="test" width="12"/>
60
                        <input id="tester" type="submit" value="Test"/>
61
                        <span id="testResulDisplay"/>
62
                        <div class="cssload-loader">
63
                            <div class="cssload-inner cssload-one"></div>
64
                            <div class="cssload-inner cssload-two"></div>
65
                            <div class="cssload-inner cssload-three"></div>
66
                        </div>
67
                    </div>
68
                    </fieldset>
69
70
                    <table id="floatingMatrix">
71
                        <tr id="fmHeaderRow">
72
                            <th>From \ To</th>
73
                            [% FOREACH branch IN branches %]
74
                                <th title="[% branch.branchname %]">[% branch.branchcode %]</th>
75
                            [% END %]
76
                        </tr>
77
                        [% FOREACH fromBranchCode IN branches %]
78
                        <tr>
79
                            <th title="[% fromBranchCode.branchname %]">[% fromBranchCode.branchcode %]</th>
80
                            [% FOREACH toBranchCode IN branches; branchRule = fm.getBranchRule(fromBranchCode.branchcode, toBranchCode.branchcode) %]
81
                            <td>
82
                                [% IF toBranchCode.branchcode == fromBranchCode.branchcode %]
83
                                    &nbsp;
84
                                [% ELSE %]
85
                                    <div class="branchRule"[% IF branchRule.getId %] id="br_[% branchRule.getId %]"[% END %]>
86
87
                                        [% INCLUDE 'admin/floating-matrix-floatingTypes.inc' %]
88
89
                                        <input name="conditionRules" type="text" value="[% branchRule.getConditionRules() %]" class="hidden"/>
90
                                        <div>
91
                                            <input name="submit" type="submit" value="%"/>
92
                                            <input name="cancel" type="submit" value="V"/>
93
                                        </div>
94
                                    </div>
95
                                [% END %]
96
                            </td>
97
                            [% END %]
98
                        </tr>
99
                        [% END %]
100
                    </table>
101
                </fieldset>
102
            </main>
103
        </div>
104
        <div class="col-sm-2 col-sm-pull-10">
105
            <aside>
106
                [% INCLUDE 'admin-menu.inc' %]
107
            </aside>
108
        </div>
109
    </div>
110
</div>
111
112
113
114
<!-- HTML Templates -->
115
<div class="branchRule TEMPLATE" id="br_TEMPLATE">
116
117
    [% INCLUDE 'admin/floating-matrix-floatingTypes.inc' %]
118
119
    <input name="conditionRules" type="text" value="" class="hidden"/>
120
    <div>
121
        <input name="submit" type="submit" value="%"/>
122
        <input name="cancel" type="submit" value="V"/>
123
    </div>
124
</div>
125
126
127
[% Asset.js("js/floating-matrix.js") | $raw %]
128
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/floating-matrix.js (-1 / +250 lines)
Line 0 Link Here
0
- 
1
////Create javascript namespace
2
var FloMax = FloMax || {};
3
FloMax.Tester = FloMax.Tester || {};
4
5
//// DOCUMENT READY INIT SCRIPTS
6
7
FloMax.branchRules = {}; //Stores all the edited branchRules so we can rollback HTML changes and CRUD over AJAX.
8
9
FloMax.branchRuleDisabledColor = "#e83519";
10
FloMax.branchRuleAlwaysColor = "#219e0e";
11
FloMax.branchRulePossibleColor = "#2626bf";
12
FloMax.branchRuleConditionalColor = "#af21bc";
13
$(document).ready(function() {
14
    //Color cells based on their floatingType
15
    $(".branchRule").each(function(){
16
        FloMax.changeBranchRuleColor(this);
17
    });
18
19
    //Bind actions to the FloMax Tester
20
    $("#floatingRuleTester input[type='submit']").bind({
21
        click: function() {
22
            FloMax.Tester.testFloating();
23
        }
24
    });
25
26
    //Bind action listeners to the floating matrix
27
    $(".branchRule").bind({
28
        click: function() {
29
            //If we are clicking a branchCode element, mark it as selected and remove other selections.
30
            if (! $(this).hasClass('selected')) {
31
                $(".branchRule").removeClass('selected');
32
                $("input[name='conditionRules']").addClass('hidden');
33
34
                $(this).addClass('selected');
35
                FloMax.changeBranchRuleColor(this);
36
                $(this).find("input[name='conditionRules']").removeClass('hidden');
37
            }
38
        },
39
    });
40
    $(".branchRule input[name='cancel']").bind({
41
        click: function(event) {
42
            FloMax.replaceBranchRule( $(this).parents(".branchRule") );
43
            $(this).parents(".branchRule").removeClass('selected');
44
            $("input[name='conditionRules']").addClass('hidden');
45
            event.stopPropagation();
46
        },
47
    });
48
    $(".branchRule input[name='submit']").bind({
49
        click: function(event) {
50
            FloMax.storeBranchRule( $(this).parents(".branchRule") );
51
            $(this).parents(".branchRule").removeClass('selected');
52
            $("input[name='conditionRules']").addClass('hidden');
53
            event.stopPropagation();
54
        },
55
    });
56
    $(".branchRule select").bind({
57
        change: function() {
58
            //When the floatingType changes, so does the color
59
            var branchRule = $(this).parents(".branchRule");
60
            FloMax.changeBranchRuleColor(branchRule);
61
        },
62
    });
63
});
64
////EOF DOCUMENT READY INIT SCRIPTS
65
66
FloMax.changeBranchRuleColor = function (branchRule) {
67
    var floatingType = $(branchRule).find("select[name='floating']").val();
68
    var color = "#000000";
69
    if (floatingType == 'DISABLED') {
70
        color = FloMax.branchRuleDisabledColor;
71
    }
72
    else if (floatingType == 'ALWAYS') {
73
        color = FloMax.branchRuleAlwaysColor;
74
    }
75
    else if (floatingType == 'POSSIBLE') {
76
        color = FloMax.branchRulePossibleColor;
77
    }
78
    else if (floatingType == 'CONDITIONAL') {
79
        color = FloMax.branchRuleConditionalColor;
80
    }
81
    $(branchRule).css('color', color);
82
    $(branchRule).css('background-color', color);
83
}
84
85
FloMax.displayConditionRulesInput = function (branchRule) {
86
    var conditionRulesInput = $(branchRule).find("input[name='conditionRules']");
87
    conditionRulesInput.removeClass('hidden');
88
}
89
90
FloMax.buildBranchRuleFromHTML = function (branchRule) {
91
    //Get fromBranch by looking at the first column at this row.
92
    var siblingCells = $(branchRule).parent().prevAll();
93
    var fromBranch = $(siblingCells).eq(  ($(siblingCells).size())-1  ); //Get the first cell in this row.
94
    fromBranch = $(fromBranch).html();
95
    //Get toBranch by looking at the first row of the current column.
96
    var nthColumn = ($(siblingCells).size()); //Get x-position from all siblings + the column header, into this branchRule
97
    var toBranch = $("#floatingMatrix").find("#fmHeaderRow").children("th").eq( nthColumn ).html();
98
    //Get floating
99
    var floating = $(branchRule).find("select").val();
100
    //Get conditionRules
101
    var conditionRules = $(branchRule).find("input[name='conditionRules']").val();
102
    //Get id
103
    var id;
104
    if ($(branchRule).attr('id')) {
105
        id = $(branchRule).attr('id').substring(3); //Skip characters 'br_'
106
    }
107
    var brJSON = {'fromBranch' : fromBranch,
108
              'toBranch' : toBranch,
109
              'floating' : floating,
110
              'conditionRules' : conditionRules,
111
              'id' : id,
112
    };
113
    return brJSON;
114
}
115
FloMax.storeBranchRule = function (branchRule) {
116
    var brJSON = FloMax.buildBranchRuleFromHTML(branchRule);
117
    FloMax.branchRules[brJSON.fromBranch+'-'+brJSON.toBranch] = brJSON;
118
    FloMax.persistBranchRule(brJSON, branchRule);
119
}
120
FloMax.replaceBranchRule = function (branchRule) {
121
    var brJSON = FloMax.buildBranchRuleFromHTML(branchRule);
122
    var oldBrJSON = FloMax.branchRules[brJSON.fromBranch+'-'+brJSON.toBranch];
123
    var newBranchRule = FloMax.newBranchRuleHTML(oldBrJSON);
124
    branchRule.replaceWith(newBranchRule);
125
}
126
FloMax.resetBranchRule = function (branchRule) {
127
    branchRule.replaceWith(newBranchRuleHTML());
128
}
129
FloMax.newBranchRuleHTML = function (branchRuleJSON) {
130
    var branchRuleHTML = $("#br_TEMPLATE").clone('withDataAndElements');
131
    if (branchRuleJSON && branchRuleJSON.id) {
132
        $(branchRuleHTML).attr('id','br_'+branchRuleJSON.id);
133
    }
134
    else {
135
        $(branchRuleHTML).removeAttr('id');
136
    }
137
    if (branchRuleJSON && branchRuleJSON.conditionRules) {
138
        $(branchRuleHTML).find("input[name='conditionRules']").removeClass('hidden').val(branchRuleJSON.conditionRules);
139
    }
140
    else {
141
        $(branchRuleHTML).find("input[name='conditionRules']").removeClass('hidden').val('');
142
    }
143
    if (branchRuleJSON && branchRuleJSON.floating) {
144
        $(branchRuleHTML).find("select").val(  branchRuleJSON.floating || 'DISABLED'  );
145
    }
146
    else {
147
        $(branchRuleHTML).find("select").val(  'DISABLED'  );
148
    }
149
    $(branchRuleHTML).removeClass('TEMPLATE');
150
    FloMax.changeBranchRuleColor(branchRuleHTML);
151
    return branchRuleHTML;
152
}
153
/**
154
 *     var brJSON = FloMax.buildBranchRuleFromHTML(branchRule);
155
 *     FloMax.persistBranchRule(brJSON);
156
 *
157
 * INSERTs or UPDATEs or DELETEs the JSON:ified branchRule to the Koha DB
158
 * @param {object} brJSON - JSON:ified object representation of a branchRule
159
 *                          from buildBranchRuleFromHTML()
160
 * @param {object} branchRule - the HTML element matching class ".branchRule".
161
 *                          Used to target display modifications to it.
162
 */
163
FloMax.persistBranchRule = function (brJSON, branchRule) {
164
    if (brJSON.floating == 'DISABLED') {
165
        brJSON.delete = 1; //A hack to trick Perl CGI to understand this is a HTTP DELETE-verb.
166
        $.ajax('floating-matrix-api.pl',
167
               {method : 'POST', //Should be DELETE but damn CGI
168
                data : brJSON,
169
                dataType : 'json',
170
        }).done(function(data, textStatus, jqXHR){
171
            FloMax.resetBranchRule(branchRule);
172
            $(branchRule).removeClass('failedAjax');
173
            //alert("Saving floating rule "+brJSON.fromBranch+"-"+brJSON.toBranch+" OK, because of the following error:\n"+data.status+" "+data.statusText);
174
        }).fail(function (data, textStatus, jqXHR) {
175
            $(branchRule).addClass('failedAjax');
176
            var error = $.parseJSON(data.responseText); //Pass the error as JSON so we don't trigger the default Koha error pages.
177
            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);
178
        });
179
    }
180
    else {
181
        $.ajax('floating-matrix-api.pl',
182
               {method : 'POST',
183
                data : brJSON,
184
                dataType : 'json',
185
        }).done(function(data, textStatus, jqXHR){
186
            $(branchRule).removeClass('failedAjax');
187
            $(branchRule).attr('id', 'br_'+data.id);
188
        }).fail(function (data, textStatus, jqXHR) {
189
            $(branchRule).addClass('failedAjax');
190
            var error = $.parseJSON(data.responseText); //Pass the error as JSON so we don't trigger the default Koha error pages.
191
            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);
192
        });
193
    }
194
}
195
196
FloMax.Tester.defaultTestResultColor = "#B9D8D9";
197
198
FloMax.Tester.testFloating = function() {
199
    var testCase = FloMax.Tester.buildTestCaseFromHTML();
200
    $(".cssload-loader").css('visibility', 'visible');
201
202
    $.ajax('floating-matrix-api.pl',
203
           {method : 'POST',
204
            data : testCase,
205
            dataType : 'json',
206
    }).done(function(data, textStatus, jqXHR){
207
208
        FloMax.Tester.displayTestResult(data.testResult);
209
210
    }).fail(function (data, textStatus, jqXHR) {
211
        var error = $.parseJSON(data.responseText); //Pass the error as JSON so we don't trigger the default Koha error pages.
212
        alert("Testing floating rule "+testCase.fromBranch+"-"+testCase.toBranch+" failed, because of the following error:\n"+data.status+" "+data.statusText+"\n"+"More specific error: "+error.error);
213
        FloMax.Tester.displayTestResult("error");
214
    });
215
}
216
FloMax.Tester.buildTestCaseFromHTML = function () {
217
    var fromBranch = $("#floatingRuleTester #testerFromBranch").val();
218
    var toBranch   = $("#floatingRuleTester #testerToBranch").val();
219
    var barcode    = $("#floatingRuleTester #testerBarcode").val();
220
221
    var testCase = {
222
        'fromBranch' : fromBranch,
223
        'toBranch' : toBranch,
224
        'barcode' : barcode,
225
        'test': true,
226
    };
227
    return testCase;
228
}
229
FloMax.Tester.displayTestResult = function (testResult) {
230
    $(".cssload-loader").css('visibility', 'hidden');
231
    var color;
232
    if (testResult == null) {
233
        color = FloMax.branchRuleDisabledColor;
234
    }
235
    else if (testResult == 'ALWAYS') {
236
        color = FloMax.branchRuleAlwaysColor;
237
    }
238
    else if (testResult == 'POSSIBLE') {
239
        color = FloMax.branchRulePossibleColor;
240
    }
241
    else if (testResult == "error") {
242
        color = FloMax.Tester.defaultTestResultColor;
243
    }
244
    else {
245
        color = FloMax.Tester.defaultTestResultColor;
246
        alert("Couldn't display test result '"+testResult+"'. Value is unknown.");
247
    }
248
249
    $("#testResulDisplay").css('background-color', color);
250
}

Return to bug 9525