From 1c3e06d1c2f2c752f2dc1a09c5cba3f9d19b8f27 Mon Sep 17 00:00:00 2001 From: Olli-Antti Kivilahti Date: Fri, 8 May 2015 15:40:42 +0300 Subject: [PATCH] Bug 9525 - group floating rules Cucumber integration tests included. run t/Cucumber/runCucumberTests.pl --tags @floatingMatrix --- C4/Circulation.pm | 11 +- Koha/FloatingMatrix.pm | 479 ++++++++++++++++++++ Koha/FloatingMatrix/BranchRule.pm | 342 ++++++++++++++ Koha/Schema/Result/FloatingMatrix.pm | 114 +++++ admin/floating-matrix-api.pl | 87 ++++ admin/floating-matrix.pl | 139 ++++++ installer/data/mysql/kohastructure.sql | 18 + installer/data/mysql/updatedatabase.pl | 31 ++ .../prog/en/css/admin/floating-matrix.css | 16 + .../admin/floating-matrix-floatingTypes.inc | 26 ++ .../prog/en/js/admin/floating-matrix.js | 186 ++++++++ .../prog/en/modules/admin/admin-home.tt | 2 + .../prog/en/modules/admin/floating-matrix.tt | 97 ++++ t/Cucumber/SImpls/FloatingMatrix.pm | 197 ++++++++ .../FloatingMatrix/floatingMatrixCRUD.feature | 65 +++ .../FloatingMatrix/floatingMatrixUsage.feature | 104 +++++ t/Cucumber/steps/common_steps.pl | 1 + t/Cucumber/steps/floatingMatrix_steps.pl | 54 +++ 18 files changed, 1965 insertions(+), 4 deletions(-) create mode 100644 Koha/FloatingMatrix.pm create mode 100644 Koha/FloatingMatrix/BranchRule.pm create mode 100644 Koha/Schema/Result/FloatingMatrix.pm create mode 100755 admin/floating-matrix-api.pl create mode 100755 admin/floating-matrix.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/css/admin/floating-matrix.css create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/admin/floating-matrix-floatingTypes.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/js/admin/floating-matrix.js create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/admin/floating-matrix.tt create mode 100644 t/Cucumber/SImpls/FloatingMatrix.pm create mode 100644 t/Cucumber/features/FloatingMatrix/floatingMatrixCRUD.feature create mode 100644 t/Cucumber/features/FloatingMatrix/floatingMatrixUsage.feature create mode 100644 t/Cucumber/steps/floatingMatrix_steps.pl diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 302956e..b996655 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -46,6 +46,7 @@ use C4::RotatingCollections qw(GetCollectionItemBranches); use Algorithm::CheckDigits; use Data::Dumper; +use Koha::FloatingMatrix; use Koha::DateUtils; use Koha::Calendar; use Koha::Borrower::Debarments; @@ -2023,15 +2024,18 @@ sub AddReturn { DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' }); } + my $floatingType = Koha::FloatingMatrix::CheckFloating($item, $branch, $hbr); # FIXME: make this comment intelligible. #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch . - if ( !$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){ - if ( C4::Context->preference("AutomaticItemReturn" ) or + if ((not($floatingType) || $floatingType eq 'POSSIBLE') && + !$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){ + if ( not($floatingType && $floatingType eq 'POSSIBLE') and #If floatingType is POSSIBLE, we prompt for transfer but not autoinitiate it. + (C4::Context->preference("AutomaticItemReturn" ) or (C4::Context->preference("UseBranchTransferLimits") and ! IsBranchTransferAllowed($branch, $hbr, $item->{C4::Context->preference("BranchTransferLimitsType")} ) - )) { + ))) { $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s)", $item->{'itemnumber'},$branch, $hbr; $debug and warn "item: " . Dumper($item); ModItemTransfer($item->{'itemnumber'}, $branch, $hbr); @@ -2040,7 +2044,6 @@ sub AddReturn { $messages->{'NeedsTransfer'} = 1; # TODO: instead of 1, specify branchcode that the transfer SHOULD go to, $item->{homebranch} } } - return ( $doreturn, $messages, $issue, $borrower ); } diff --git a/Koha/FloatingMatrix.pm b/Koha/FloatingMatrix.pm new file mode 100644 index 0000000..93e7b36 --- /dev/null +++ b/Koha/FloatingMatrix.pm @@ -0,0 +1,479 @@ +package Koha::FloatingMatrix; + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +=head1 FloatingMatrix + +Koha::FloatingMatrix - Object to control accessing and modifying floating matrix rules. + +=cut + +use Modern::Perl; +use Carp qw(carp croak confess longmess); +use Scalar::Util qw(blessed refaddr); + +use C4::Context qw(dbh); +use Koha::Cache; +use Koha::Database; + +use Koha::FloatingMatrix::BranchRule; + +use Koha::Exception::BadParameter; + +my $cacheExpiryTime = 1200; + +=head new + + my $fm = Koha::FloatingMatrix->new(); + +Finds a FloatingMatrix from Koha::Cache or instantiates a new one. + +=cut + +sub new { + my ($class) = @_; + + my $cache = Koha::Cache->get_instance(); + my $fm = $cache->get_from_cache('floatingMatrix'); + unless ($fm) { + $fm = {}; + bless $fm, $class; + $fm->_getFloatingMatrix(); + $fm->_createModificationBuffer(); + $cache->set_in_cache('floatingMatrix', $fm, {expiry => $cacheExpiryTime}); + } + return $fm if blessed($fm) && $fm->isa('Koha::FloatingMatrix'); + return undef; +} + +sub _getFloatingMatrix { + my ($fm) = @_; + + my $schema = Koha::Database->new()->schema(); + my @rules = $schema->resultset('FloatingMatrix')->search({}); + + my %map; + $fm->_setBranchRules( \%map ); + foreach my $rule (@rules) { + my $branchRule = Koha::FloatingMatrix::BranchRule->newFromDBIx($rule); + $fm->_linkBranchRule($branchRule); + } +} + +=head GetFloatingTypes +Static Method + + my $floatingTypes = Koha::FloatingMatrix::GetFloatingTypes(); + +@RETURNS Reference to ARRAY, of koha.floating_matrix.floating enumerations. + These are all the available ways Items can float in Koha. +=cut + +sub GetFloatingTypes { + my $schema = Koha::Database->new()->schema(); + my $source = $schema->source('FloatingMatrix'); + my $info = $source->column_info('floating'); + my $floatingTypeEnumerations = $info->{extra}->{list}; + return $floatingTypeEnumerations; +} + +=head getFloatingTypes + $fm->getFloatingTypes(); +See. GetFloatingTypes() +=cut +sub getFloatingTypes { + my ($fm) = @_; + + return $fm->{floatingTypes} if $fm->{floatingTypes}; + + $fm->{floatingTypes} = Koha::FloatingMatrix::GetFloatingTypes(); + return $fm->{floatingTypes}; +} + +=head upsertBranchRule + + my ($branchRule, $error) = $fm->upsertBranchRule( $params ); + if ($error) { + #I must have given some bad object properties + } + else { + $fm->store(); + } + +Adds or modifies an existing overduerule. Take note that this is only a local modification +for this instance of OverdueRulesMap-object. +If changes need to persists, call the $orm->store() method to save changes to DB. + +@PARAM1, HASH, See Koha::Overdues::OverdueRule->new() for parameter descriptions. +@RETURN, Koha::Overdues::OverdueRule-object if success and + String errorCode, if something bad hapened. + +@THROWS Koha::Exception::BadParameter from Koha::FloatingMatrix::BranchRule->new(). +=cut + +sub upsertBranchRule { + my ($fm, $params) = @_; + + my $branchRule = Koha::FloatingMatrix::BranchRule->new($params); #While creating this object we sanitate the parameters. + + my $existingBranchRule = $fm->_findBranchRule($branchRule); + my $operation; + if ($existingBranchRule) { #We are modifying an existing rule + #Should we somehow tell that we are updating an existing rule? + $existingBranchRule->replace($branchRule); #Replace with the new sanitated values, we preserve the internal data structure position. + #Replacing might be less costly than recursively unlinking a large internal mapping. + $branchRule = $existingBranchRule; + $operation = 'MOD'; + } + else { #Just adding more rules + $fm->_linkBranchRule($branchRule); + $operation = 'ADD'; + } + + $fm->_pushToModificationBuffer([$branchRule, $operation]); + return $branchRule; +} + +=head getBranchRule + + my $branchRule = $fm->getBranchRule( $fromBranch, $toBranch ); + +@PARAM1 String, koha.floating_matrix.from_branch, must give @PARAM2 as well +@PARAM2 String, koha.floating_matrix.to_branch, must give @PARAM1 as well +@RETURNS Koha::FloatingMatrix::BranchRule-object matching the params or undef. +=cut + +sub getBranchRule { + my ($fm, $fromBranch, $toBranch) = @_; + + my $branchRule = $fm->_findBranchRule(undef, $fromBranch, $toBranch); + + return $branchRule; +} + +=head getBranchRules + + my $branchRules = $fm->getBranchRules(); + +@RETURNS Reference to a HASH of Koha::FloatingMatrix::BranchRule-objects + Hash keys are -, eg. FFL-CPL +=cut + +sub getBranchRules { + my ($fm) = @_; + return $fm->{branchRules}; +} + +=head _setBranchRules +Needs to be called only from the _getFloatingMatrix() to bind the branchRules-map to this object. +@PARAM1, Reference to HASH. +=cut + +sub _setBranchRules { + my ($fm, $hash) = @_; + $fm->{branchRules} = $hash; +} + +sub checkFloating { + my ($fm, $item, $checkinBranch, $transferTargetBranch) = @_; + unless ($transferTargetBranch) { #If no transfer branch is given, then we try our best to figure it out. + my $branchItemRule = C4::Circulation::GetBranchItemRule($item->{'homebranch'}, $item->{'itype'}); + my $returnBranchRule = $branchItemRule->{'returnbranch'} || "homebranch"; + # get the proper branch to which to return the item + $transferTargetBranch = $item->{$returnBranchRule} || C4::Context->userenv->{'branch'}; + } + + #If the check-in branch and transfer branch are the same, then no point in transferring. + if ($checkinBranch eq $transferTargetBranch) { + return undef; + } + + my $branchRule = $fm->getBranchRule($checkinBranch, $transferTargetBranch); + if ($branchRule) { + my $floating = $branchRule->getFloating(); + if ($floating eq 'ALWAYS') { + return 'ALWAYS'; + } + elsif ($floating eq 'POSSIBLE') { + return 'POSSIBLE'; + } + elsif ($floating eq 'CONDITIONAL') { + if(_CheckConditionalFloat($item, $branchRule)) { + return 'ALWAYS'; + } + } + else { + warn "FloatingMatrix->checkFloating():> Bad floating type for route from '$checkinBranch' to '$transferTargetBranch'. Not floating.\n"; + } + } + return undef; +} + +=head _CheckConditionalFloat +Static method + +@PARAM1, HASH of koha.items-row +@PARAM2, Koha::FloatingMatrix::BranchRule-object +@RETURNS 1 if floats, undef if not. +=cut + +sub _CheckConditionalFloat { + my ($item, $branchRule) = @_; + + my $conditionRules = $branchRule->getConditionRules(); + + my $evalCondition = ''; + if (my @conds = $conditionRules =~ /(\w+)\s+(ne|eq|gt|lt|<|>|==|!=)\s+(\w+)\s*(and|or|xor|&&|\|\|)?/ig) { + #Iterate the condition quads, with the fourth index being the logical join operator. + for (my $i=0 ; $i{'$column'}",$operator,"'$value'",$join,''); + } + } + else { + warn "Koha::FloatingMatrix::_CheckConditionalFloat():> Bad condition rules '$conditionRules' couldn't be parsed\n"; + return undef; + } + my $ok = eval("return 1 if($evalCondition);"); + if ($@) { + warn "Koha::FloatingMatrix::_CheckConditionalFloat():> Something bad hapened when trying to evaluate the dynamic conditional:\n$@\n"; + return undef; + } + return $ok; +} + +=head CheckFloating +Static Subroutine + +A convenience Subroutine to checkFloating without intantiating a new Koha::FloatingMatrix-object +=cut + +sub CheckFloating { + my ($item, $checkinBranch, $transferTargetBranch) = @_; + my $fm = Koha::FloatingMatrix->new(); + return $fm->checkFloating($item, $checkinBranch, $transferTargetBranch); +} + +=head _findBranchRule + + my $branchRule = $fm->_findBranchRule( undef, $fromBranch, $toBranch ); + my $existingBranchRule = $fm->_findBranchRule( $branchRule ); + +Finds a branch rule from the internal floating matrix map. + +This abstracts the retrieval of branch rules, so we can later change the internal mapping. +@PARAM1, Koha::FloatingMatrix::BranchRule-object, to see if a BranchRule with same targeting rules is present. + or undef, if you are only interested in retrieving. +@PARAM2-3, MANDATORY if no @PARAM1 given, Targeting rules to find a BranchRule. +@RETURNS Koha::FloatingMatrix::BranchRule-object, of the object occupying the given position. + or undef if nothing is found. +=cut + +sub _findBranchRule { + my ($fm, $branchRule, $fromBranch, $toBranch) = @_; + if (blessed($branchRule) && $branchRule->isa('Koha::FloatingMatrix::BranchRule')) { + $fromBranch = $branchRule->getFromBranch(); + $toBranch = $branchRule->getToBranch(); + } + + my $existingBranchRule = $fm->getBranchRules()->{ $fromBranch }->{ $toBranch }; + return $existingBranchRule; +} + +=head _linkBranchRule + + my $existingBranchRule = $fm->_linkBranchRule( $branchRule ); + +Links the new branchrule to the internal floating matrix map, overwriting a possible existing +reference to a BranchRule, and losing that into the binary limbo. + +This abstracts the retrieval of floating matrix routes, so we can later change the internal mapping. +@PARAM1, Koha::FloatingMatrix::BranchRule-object, to link to the internal mapping +=cut + +sub _linkBranchRule { + my ($fm, $branchRule) = @_; + + $fm->{branchRules}->{ $branchRule->getFromBranch() }->{ $branchRule->getToBranch() } = $branchRule; +} + +=head _unlinkBranchRule + + my $existingBranchRule = $fm->_unlinkBranchRule( $branchRule ); + +Unlinks the branchRule from the internal floating matrix map and releasing it into the binary limbo. + +This abstracts the internal mapping of branch rules, so we can later change it. +@PARAM1, Koha::FloatingMatrix::BranchRule-object + +=cut + +sub _unlinkBranchRule { + my ($fm, $branchRule) = @_; + + #Delete the BranchRule + my $branchRules = $fm->getBranchRules(); + my $fromBranch = $branchRule->getFromBranch(); + my $toBranch = $branchRule->getToBranch(); + + eval{ delete( $branchRules->{ $fromBranch }->{ $toBranch } ); }; + if ($@) { + carp "Unlinking BranchRule failed because of '".$@."'. Something wrong with this BranchRule\n".$branchRule->toString()."\n"; + return $@; + } + + unless (scalar(%{$branchRules->{ $fromBranch }})) { + #Delete the branchLevel + delete( $branchRules->{ $fromBranch } ); + } +} + +=head deleteBranchRule + + $fm->deleteBranchRule($branchRule); + $fm->deleteBranchRule($fromBranch, $toBranch); + +Deletes the BranchRule from the internal mapping, but not from the DB. +Call $fm->store() to persist the removal. + +The given $branchRule must be the same object gained using $fm->getBranchRule(), +or you are misusing the FloatingMatrix-object and bad things might happen. + +@PARAM1, Koha::FloatingMatrix::BranchRule-object to delete from DB. +or +@PARAM1 {String, koha.floating_matrix.from_branch} +@PARAM1 {String, koha.floating_matrix.to_branch} + +@THROWS Koha::Exception::BadParameter if no branchRule, with the given parameters, is found, + or the given BranchRule doesn't match the one in the internal mapping. +=cut + +sub deleteBranchRule { + my ($fm, $fromBranch, $toBranch) = @_; + my ($branchRule, $branchRuleLocal); + #Process given parameters, see if we use parameter group1 (BranchRule-object) or group2 (branchcodes)? + if (blessed $fromBranch && $fromBranch->isa('Koha::FloatingMatrix::BranchRule')) { + $branchRule = $fromBranch; + $fromBranch = $branchRule->getFromBranch(); + $toBranch = $branchRule->getToBranch(); + } + + $branchRuleLocal = $fm->getBranchRule($fromBranch, $toBranch); + Koha::Exception::BadParameter->throw(error => "Koha::FloatingMatrix->deleteBranchRule($fromBranch, $toBranch):> No BranchRule exists for the given fromBranch '$fromBranch' and toBranch '$toBranch'") + unless $branchRuleLocal; + + $fm->_unlinkBranchRule( $branchRuleLocal ); + $fm->_pushToModificationBuffer([$branchRuleLocal, 'DEL']); +} + +=head $fm->deleteAllFloatingMatrixRules() + +Deletes all Floating matrix rules in the DB, shows no pity. +Invalidates $fm in the Koha::Cache +=cut + +sub deleteAllFloatingMatrixRules { + my ($fm) = @_; + my $schema = Koha::Database->new()->schema(); + $schema->resultset('FloatingMatrix')->search({})->delete_all; + $fm = undef; + + my $cache = Koha::Cache->get_instance(); + $cache->clear_from_cache('floatingMatrix'); +} + +=head store + + $fm->store(); + +Saves all pending transactions to DB, by calling the Koha::FloatingMatrix::BranchRule->store() || delete(); + +=cut + +sub store { + my $fm = shift; + my $schema = Koha::Database->new()->schema(); + + my $pendingModifications = $fm->_consumeModificationBuffer(); + foreach my $modRequest (@$pendingModifications) { + my $branchRule = $modRequest->[0]; + my $operation = $modRequest->[1]; + if ($operation eq 'MOD' || $operation eq 'ADD') { + $branchRule->store(); + } + elsif ($operation eq 'DEL') { + $branchRule->delete(); + } + else { + carp "Unsupported database access operation '$operation'!"; + } + } + + my $cache = Koha::Cache->get_instance(); + $cache->set_in_cache('floatingMatrix', $fm, {expiry => $cacheExpiryTime}); +} + +=head _pushToModificationBuffer + + $fm->_pushToModificationBuffer([$branchRule, $operation]); + +To be able to more effectively service DB write operations, especially when using +FloatingMatrix with an REST(ful?) API giving lots of write operations, it is useful +to be able to know which BranchRules need changing and which do not. +Thus we don't need to either +DELETE all BranchRules from DB and re-add them (what if the rewrite request dies?) + or +check each rule for possible changes. + +The modification buffer tells what information needs changing. + +To write the changes to DB, use $fm->store(). + +@PARAM1, Tuple (Two-index ARRAY reference). [0] = $branchRule (see. upsertBranchRule()) + [1] = The operation, either 'ADD', 'MOD' or 'DEL' +@RETURN, always nothing. + +=cut + +sub _pushToModificationBuffer { + my ($fm, $tuple) = @_; + push @{$fm->{modBuffer}}, $tuple; + return undef; +} +=head _consumeModificationBuffer +Detaches the modification buffer from the FloatingMatrix (parent) and returns it. +Fm now has an empty modification buffer ready for new modifications. +=cut + +sub _consumeModificationBuffer { + my ($fm) = @_; + my $modBuffer = $fm->{modBuffer}; + $fm->{modBuffer} = []; + return $modBuffer; +} +sub _createModificationBuffer { + my ($fm) = @_; + $fm->{modBuffer} = []; + return undef; +} + +1; #Satisfy the compiler \ No newline at end of file diff --git a/Koha/FloatingMatrix/BranchRule.pm b/Koha/FloatingMatrix/BranchRule.pm new file mode 100644 index 0000000..3d38d41 --- /dev/null +++ b/Koha/FloatingMatrix/BranchRule.pm @@ -0,0 +1,342 @@ +package Koha::FloatingMatrix::BranchRule; + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +=head1 Rule + +Koha::FloatingMatrix::BranchRule - Object representing floating rules for one branch + +=head1 DESCRIPTION + + +=cut + +use Modern::Perl; +use Carp qw(carp croak confess longmess); +use Scalar::Util 'blessed'; +use Data::Dumper; + +use C4::Context qw(dbh); +use Koha::Cache; +use Koha::Database; + +use Koha::Exception::BadParameter; + + +=head new + + my ($branchRule, $error) = Koha::FloatingMatrix::BranchRule->new({ + fromBranch => 'CPL', + toBranch => 'FFL', + floating => ['ALWAYS'|'POSSIBLE'|'CONDITIONAL'], + conditionRules => "items->{itype} eq 'BK' && $items->{permanent_location} eq 'CART'" + }); + +BranchRule defines the floating rule for one transfer route. +Eg. If you check-in an Item to CFL and based on your library policies tha Item is transferred to IPT, +We check if there is a floating rule for that fromBranch-toBranch -combination and apply that if applicable. + +@PARAM1, HASH +@RETURN, Koha::FloatingMatrix::BranchRule-object, +@THROWS Koha::Exception::BadParameter if given parameters don't validate properly. +=cut + +sub new { + my ($class, $params) = @_; + + ValidateParams($params); + + bless $params, $class; + return $params; +} + +=head newFromDBIx + + Koha::FloatingMatrix::BranchRule->newFromDBIx( $Koha::Schema::Result::FloatingMatrix ); + +Creates a BranchRule-object from DBIx. +See new() for more info. +=cut + +sub newFromDBIx { + my ($class, $dbix) = @_; + + my $params = { + id => $dbix->id(), + fromBranch => $dbix->get_column('from_branch'), + toBranch => $dbix->get_column('to_branch'), + floating => $dbix->get_column('floating'), + conditionRules => $dbix->get_column('condition_rules'), + }; + return $class->new($params); +} + +=head ValidateParams, Static subroutine + + my $error = Koha::Overdues::OverdueRule::ValidateParams($params); + +@PARAM1, HASH, see new() for valid values. +@THROWS Koha::Exception::BadParameter if given parameters don't validate properly. +=cut + +my $maximumConditionRulesDatabaseLength = 100; +sub ValidateParams { + my ($params) = @_; + my $errorMsg; + + if (not($params->{fromBranch}) || length $params->{fromBranch} < 1) { + $errorMsg = "No 'fromBranch'"; + } + elsif (not($params->{toBranch}) || length $params->{toBranch} < 1) { + $errorMsg = "No 'toBranch'"; + } + elsif (not($params->{floating}) || length $params->{floating} < 1) { + $errorMsg = "No 'floating'"; + } + elsif ($params->{floating} && ($params->{floating} ne 'CONDITIONAL' && + $params->{floating} ne 'ALWAYS' && + $params->{floating} ne 'POSSIBLE') + ) { + $errorMsg = "Bad enum '".$params->{floating}."' for 'floating'"; + } + elsif (not($params->{conditionRules}) && $params->{floating} eq 'CONDITIONAL') { + $errorMsg = "No 'conditionRules' when floating = 'CONDITIONAL'"; + } + elsif ($params->{conditionRules} && $params->{conditionRules} =~ /[};{]/gsmi) { + $errorMsg = "Not allowed 'conditionRules' characters '};{' present"; + } + elsif ($params->{conditionRules} && length($params->{conditionRules}) > $maximumConditionRulesDatabaseLength) { + $errorMsg = "'conditionRules' text is too long. Maximum length is '$maximumConditionRulesDatabaseLength' characters"; + } + elsif ($params->{conditionRules}) { + ParseConditionRules(undef, $params->{conditionRules}); + } + + if ($errorMsg) { + my $fb = $params->{fromBranch} || ''; + my $tb = $params->{toBranch} || ''; + my $id = $params->{id} || ''; + Koha::Exception::BadParameter->throw(error => "Koha::FloatingMatrix::BranchRule::ValidateParams():> $errorMsg. For branch rule id '$id', fromBranch '$fb', toBranch '$tb'."); + } + #Now that we have sanitated the input, we can rest assured that bad input won't crash this Object :) +} + +=head parseConditionRules, Static method + + my $evalCondition = ParseConditionRules($item, $conditionRules); + my $evalCondition = ParseConditionRules(undef, $conditionRules); + +Parses the given Perl boolean expression into an eval()-able expression. +If Item is given, uses the Item's columns to create a executable expression to check for +conditional floating for this specific Item. + +@PARAM1 {Reference to HASH of koha.items-row} Item to check for conditional floating. +@PARAM2 {String koha.floating_matrix.condition_rules} The boolean expression to turn + into a Perl code to check the floating condition. +@THROWS Koha::Exception::BadParameter, if the conditionRules couldn't be parsed. +=cut +sub ParseConditionRules { + my ($item, $conditionRulesString) = @_; + my $evalCondition = ''; + if (my @conds = $conditionRulesString =~ /(\w+)\s+(ne|eq|gt|lt|<|>|==|!=)\s+(\w+)\s*(and|or|xor|&&|\|\|)?/ig) { + + #If we haven't got no Item, simply stop here to aknowledge that the given condition logic is valid (atleast parseable) + return undef unless $item; + + #If we have an Item, then prepare and return an eval-expression to test if the Item should float. + #Iterate the condition quads, with the fourth index being the logical join operator. + for (my $i=0 ; $i{'$column'}",$operator,"'$value'",$join,''); + } + + return $evalCondition; + } + else { + Koha::Exception::BadParameter->throw(error => + "Koha::FloatingMatrix::parseConditionRules():> Bad condition rules '$conditionRulesString' couldn't be parsed\n". + "See 'Help' for more info"); + } +} + +=head replace + + my $fmBranchRule->replace( $replacementBranchRule ); + +Replaces the calling branch rule's keys with the given parameters'. +=cut + +sub replace { + my ($branchRule, $replacementBranchRule) = @_; + + foreach my $key (keys %$replacementBranchRule) { + $branchRule->{$key} = $replacementBranchRule->{$key}; + } +} + +=head clone + $fmBranchCode->clone(); +Returns a duplicate of self +=cut +sub clone { + my ($branchRule) = @_; + + my %newBranchRuleParams; + foreach my $key (keys %$branchRule) { + $newBranchRuleParams{$key} = $branchRule->{$key}; + } + return Koha::FloatingMatrix::BranchRule->new(\%newBranchRuleParams); +} + +=head store +Saves the BranchRule into the floating_matrix-table +@THROWS Koha::Exception::BadParameter if id given but no object exists with that id. +=cut +sub store { + my $branchRule = shift; + my $schema = Koha::Database->new()->schema(); + + my $params = $branchRule->_buildBranchRuleColumns(); + my $id = $branchRule->getId(); + my $oldBranchRule; + if ($id) { + $oldBranchRule = $schema->resultset( 'FloatingMatrix' )->find( $id ); + } + if ($id && not($oldBranchRule)) { + Koha::Exception::BadParameter->throw(error => "Koha::FloatingMatrix::BranchRule->store():> floating_matrix.id given, but no matching row exist in DB"); + } + + if ($oldBranchRule) { + $oldBranchRule->update( $params ); + } + else { + my $newBranchRule = $schema->resultset( 'FloatingMatrix' )->create( $params ); + $branchRule->setId( $newBranchRule->id() ); + } +} + +=head _buildBranchRuleColumns +Transforms the BranchRule into a DBIx $parameters HASH which can be UPDATED to DB. +DBIx is crazy about excess parameters with no mapped DB column, so we cannot just pass the +BranchRule-object to the DBIx. +=cut +sub _buildBranchRuleColumns { + my ($branchRule) = @_; + + my $columns = {}; + $columns->{"from_branch"} = $branchRule->getFromBranch(); + $columns->{"to_branch"} = $branchRule->getToBranch(); + $columns->{"floating"} = $branchRule->getFloating(); + $columns->{"condition_rules"} = $branchRule->getConditionRules(); + + return $columns; +} + +sub delete { + my ($branchRule) = @_; + my $schema = Koha::Database->new()->schema(); + $schema->resultset('FloatingMatrix')->find($branchRule->getId())->delete(); +} + +sub setId { + my ($self, $val) = @_; + if ($val) { + $self->{id} = $val; + } + else { + delete $self->{id}; + } +} +sub getId { + my ($self) = @_; + return $self->{id}; +} +sub setFromBranch { + my ($self, $fromBranch) = @_; + $self->{fromBranch} = $fromBranch; +} +sub getFromBranch { + my ($self) = @_; + return $self->{fromBranch}; +} +sub setToBranch { + my ($self, $toBranch) = @_; + $self->{toBranch} = $toBranch; +} +sub getToBranch { + my ($self) = @_; + return $self->{toBranch}; +} +sub setFloating { + my ($self, $val) = @_; + $self->{floating} = $val; +} +sub getFloating { + my ($self) = @_; + return $self->{floating}; +} +=head setConditionRules +See parseConditionRules() +@THROWS Koha::Exception::BadParameter, if the conditionRules couldn't be parsed. +=cut +sub setConditionRules { + my ($self, $val) = @_; + #Validate the conditinal rules. + ParseConditionRules(undef, $val); + $self->{conditionRules} = $val; +} +sub getConditionRules { + my ($self) = @_; + return $self->{conditionRules}; +} + +=head toString + + my $stringRepresentationOfThisObject = $branchRule->toString(); + print $stringRepresentationOfThisObject."\n"; + +=cut + +sub toString { + my ($self) = @_; + + return Data::Dumper::Dump($self); +} +=head TO_JSON + + my $json = JSON::XS->new->utf8->convert_blessed->encode( $branchRule ); + or + my $json = $branchRule->TO_JSON(); + +Used to serialize this object as JSON. +=cut +sub TO_JSON { + my ($branchRule) = @_; + + my $json = {}; + while (my ($key, $val) = each(%$branchRule)) { + $json->{$key} = $val; + } + return $json; +} + +1; #Satisfy the compiler \ No newline at end of file diff --git a/Koha/Schema/Result/FloatingMatrix.pm b/Koha/Schema/Result/FloatingMatrix.pm new file mode 100644 index 0000000..db7f0c1 --- /dev/null +++ b/Koha/Schema/Result/FloatingMatrix.pm @@ -0,0 +1,114 @@ +package Koha::Schema::Result::FloatingMatrix; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + + +=head1 NAME + +Koha::Schema::Result::FloatingMatrix + +=cut + +__PACKAGE__->table("floating_matrix"); + +=head1 ACCESSORS + +=head2 id + + data_type: 'integer' + is_auto_increment: 1 + is_nullable: 0 + +=head2 from_branch + + data_type: 'varchar' + is_foreign_key: 1 + is_nullable: 0 + size: 10 + +=head2 to_branch + + data_type: 'varchar' + is_foreign_key: 1 + is_nullable: 0 + size: 10 + +=head2 floating + + data_type: 'enum' + default_value: 'ALWAYS' + extra: {list => ["ALWAYS","POSSIBLE","CONDITIONAL"]} + is_nullable: 0 + +=head2 condition_rules + + data_type: 'varchar' + is_nullable: 1 + size: 20 + +=cut + +__PACKAGE__->add_columns( + "id", + { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, + "from_branch", + { data_type => "varchar", is_foreign_key => 1, is_nullable => 0, size => 10 }, + "to_branch", + { data_type => "varchar", is_foreign_key => 1, is_nullable => 0, size => 10 }, + "floating", + { + data_type => "enum", + default_value => "ALWAYS", + extra => { list => ["ALWAYS", "POSSIBLE", "CONDITIONAL"] }, + is_nullable => 0, + }, + "condition_rules", + { data_type => "varchar", is_nullable => 1, size => 20 }, +); +__PACKAGE__->set_primary_key("id"); + +=head1 RELATIONS + +=head2 from_branch + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "from_branch", + "Koha::Schema::Result::Branch", + { branchcode => "from_branch" }, + { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" }, +); + +=head2 to_branch + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "to_branch", + "Koha::Schema::Result::Branch", + { branchcode => "to_branch" }, + { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" }, +); + + +# Created by DBIx::Class::Schema::Loader v0.07010 @ 2015-05-08 18:06:26 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:v6/Qk4/BTfzIuwD0Z2bXmA + + +# You can replace this text with custom code or comments, and it will be preserved on regeneration +1; diff --git a/admin/floating-matrix-api.pl b/admin/floating-matrix-api.pl new file mode 100755 index 0000000..47cfe8d --- /dev/null +++ b/admin/floating-matrix-api.pl @@ -0,0 +1,87 @@ +#!/usr/bin/perl +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; +use CGI qw ( -utf8 ); +use Try::Tiny; +use Scalar::Util qw(blessed); +use JSON::XS; + +use C4::Context; +use C4::Output; +use C4::Auth qw(check_cookie_auth); + +use Koha::FloatingMatrix; + +my $input = new CGI; + +#my ( $auth_status, $sessionID ) = +# check_cookie_auth( $input->cookie('CGISESSID'), +# { circulate => 'circulate_remaining_permissions' } ); +my ( $auth_status, $sessionID ) = + check_cookie_auth( $input->cookie('CGISESSID'), + { + parameters => 1, + } ); + + +binmode STDOUT, ":encoding(UTF-8)"; + +my $fm = Koha::FloatingMatrix->new(); + +my $data = $input->Vars(); +if ($data) { + try { + ##If we are getting a DELETE-request, we DELETE (CGI doesn't know what DELETE is :((( + if ($data->{delete}) { + $fm->deleteBranchRule($data->{fromBranch}, $data->{toBranch}); + $fm->store(); + } + ##If we are getting a POST-request, we UPSERT + else { + $fm->upsertBranchRule($data); + $fm->store(); + } + } catch { + if (blessed $_ && $_->isa('Koha::Exception::BadParameter')) { + respondBadParameterException($_); + } + else { + die $_; + } + }; + + print $input->header( -type => 'text/json', + -charset => 'UTF-8', + -status => "200 OK"); + print JSON::XS->new->utf8->convert_blessed->encode($data); +} +else { + print $input->header( -type => 'text/plain', + -charset => 'UTF-8', + -status => "405 Method Not Allowed"); +} + +sub respondBadParameterException { + my ($e) = @_; + print $input->header( -type => 'text/json', + -charset => 'UTF-8', + -status => "400 Bad Request"); + print JSON::XS->new->utf8->encode({error => $e->as_string()}); + exit 1; +} \ No newline at end of file diff --git a/admin/floating-matrix.pl b/admin/floating-matrix.pl new file mode 100755 index 0000000..3275f2a --- /dev/null +++ b/admin/floating-matrix.pl @@ -0,0 +1,139 @@ +#!/usr/bin/perl +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; +use CGI qw ( -utf8 ); +use C4::Context; +use C4::Output; +use C4::Auth; +use C4::Branch; # GetBranches + +use Koha::FloatingMatrix; + +my $input = new CGI; + +my ($template, $loggedinuser, $cookie) + = get_template_and_user({template_name => "admin/floating-matrix.tt", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => {parameters => 1}, + debug => 1, + }); + +my $operation = 'showMatrix'; #Default op is show. + +my $fm = Koha::FloatingMatrix->new(); +my $branches = GetBranches(); + +$template->param( + fm => $fm, + branches => $branches, +); + + +output_html_with_http_headers $input, $cookie, $template->output; + +exit 0; + +=head +my $update = $input->param('op') eq 'set-cost-matrix'; + +my ($cost_matrix, $have_matrix); +unless ($update) { + $cost_matrix = TransportCostMatrix(); + $have_matrix = keys %$cost_matrix if $cost_matrix; +} + +my $branches = GetBranches(); +my @branchloop = map { code => $_, + name => $branches->{$_}->{'branchname'} }, + sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} } + keys %$branches; +my (@branchfromloop, @cost, @errors); +foreach my $branchfrom ( @branchloop ) { + my $fromcode = $branchfrom->{code}; + + my %from_row = ( code => $fromcode, name => $branchfrom->{name} ); + foreach my $branchto ( @branchloop ) { + my $tocode = $branchto->{code}; + + my %from_to_input_def = ( code => $tocode, name => $branchto->{name} ); + push @{ $from_row{branchtoloop} }, \%from_to_input_def; + + if ($fromcode eq $tocode) { + $from_to_input_def{skip} = 1; + next; + } + + (my $from_to = "${fromcode}_${tocode}") =~ s/\W//go; + $from_to_input_def{id} = $from_to; + my $input_name = "cost_$from_to"; + my $disable_name = "disable_$from_to"; + + if ($update) { + my $value = $from_to_input_def{value} = $input->param($input_name); + if ( $input->param($disable_name) ) { + $from_to_input_def{disabled} = 1; + } + else { + push @errors, "$from_row{name} -> $from_to_input_def{name}" + unless $value =~ /\d/o && $value >= 0.0; + } + } + else { + if ($have_matrix) { + if ( my $cell = $cost_matrix->{$tocode}{$fromcode} ) { + $from_to_input_def{value} = $cell->{cost}; + $from_to_input_def{disabled} = 1 if $cell->{disable_transfer}; + } else { + # matrix has been previously initialized, but a branch referenced here was created afterward. + $from_to_input_def{disabled} = 1; + } + } else { + # First time initializing the matrix + $from_to_input_def{disabled} = 1; + } + } + } + +# die Dumper(\%from_row); + push @branchfromloop, \%from_row; +} + +if ($update && !@errors) { + my @update_recs = map { + my $from = $_->{code}; + map { frombranch => $from, tobranch => $_->{code}, cost => $_->{value}, disable_transfer => $_->{disabled} || 0 }, + grep { $_->{code} ne $from } + @{ $_->{branchtoloop} }; + } @branchfromloop; + + UpdateTransportCostMatrix(\@update_recs); +} + +$template->param( + branchloop => \@branchloop, + branchfromloop => \@branchfromloop, + errors => \@errors, +); +output_html_with_http_headers $input, $cookie, $template->output; + +exit 0; + +=cut \ No newline at end of file diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index b54468a..faf1320 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -1886,6 +1886,24 @@ CREATE TABLE `reviews` ( -- patron opac comments ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- +-- Table structure for table `floating_matrix` +-- + +DROP TABLE IF EXISTS floating_matrix; +CREATE TABLE floating_matrix ( -- Controls should we automatically transfer Items checked in to one branch to the Item's configured normal destination + id int(11) NOT NULL AUTO_INCREMENT, -- unique id + from_branch varchar(10) NOT NULL, -- branch where the Item has been checked in + to_branch varchar(10) NOT NULL, -- where the Item would normally be transferred to + 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 + condition_rules varchar(100), -- if floating = CONDITIONAL, then the special condition to trigger floating. + CHECK ( from_branch <> to_branch ), -- a dud check, mysql does not support that + PRIMARY KEY (`id`), + UNIQUE KEY `floating_matrix_uniq_branches` (`from_branch`,`to_branch`), + CONSTRAINT floating_matrix_ibfk_1 FOREIGN KEY (from_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT floating_matrix_ibfk_2 FOREIGN KEY (to_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -- Table structure for table `saved_sql` -- diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index c97f41f..e648feb 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -5769,6 +5769,37 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) { SetVersion($DBversion); } +$DBversion = "3.99.00.XXX"; +if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { + + #Check if the table has already been CREATEd and possibly CREATE it + my $dbh = C4::Context->dbh(); + my $sth = $dbh->table_info( '', '', 'floating_matrix', 'TABLE' ); + my $table = $sth->fetchrow_hashref(); + unless ($table) { + $dbh->do(" + CREATE TABLE floating_matrix ( -- Controls should we automatically transfer Items checked in to one branch to the Item's configured normal destination + id int(11) NOT NULL AUTO_INCREMENT, -- unique id + from_branch varchar(10) NOT NULL, -- branch where the Item has been checked in + to_branch varchar(10) NOT NULL, -- where the Item would normally be transferred to + 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 + condition_rules varchar(100), -- if floating = CONDITIONAL, then the special condition to trigger floating. + CHECK ( from_branch <> to_branch ), -- a dud check, mysql does not support that + PRIMARY KEY (`id`), + UNIQUE KEY `floating_matrix_uniq_branches` (`from_branch`,`to_branch`), + CONSTRAINT floating_matrix_ibfk_1 FOREIGN KEY (from_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT floating_matrix_ibfk_2 FOREIGN KEY (to_branch) REFERENCES branches (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + "); + print "Upgrade to $DBversion done (Bug 9525 - group floating rules)\n"; + } + else { + print "Upgrade to $DBversion already applied (Bug 9525 - group floating rules)\n"; + } + + SetVersion($DBversion); +} + $DBversion ="3.09.00.038"; if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { $dbh->do("ALTER TABLE borrower_attributes CHANGE attribute attribute VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL"); diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/admin/floating-matrix.css b/koha-tmpl/intranet-tmpl/prog/en/css/admin/floating-matrix.css new file mode 100644 index 0000000..de8df9a --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/css/admin/floating-matrix.css @@ -0,0 +1,16 @@ +.disabled-transfer { + background-color: #FF8888; +} +.branchRule select { width: 100%; } +.branchRule select {padding: 0px; margin: 5px 0px;} + +.branchRule input {padding: 0px; margin: 0px;} +.branchRule input[type="text"] { width: 100%; display: none; } +.branchRule input[type="submit"] {display:none} + +.branchRule.selected {color: #1b93d3; box-shadow:0px 0px 10px;} +.branchRule.selected input[type="submit"] {display:initial;} +.branchRule.selected input[type="text"] {display:initial; border: 0px none;} + +.failedAjax {box-shadow:0px 0px 30px 20px #ff0000} +.TEMPLATE {display: none; visibility: hidden;} \ No newline at end of file diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/admin/floating-matrix-floatingTypes.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/admin/floating-matrix-floatingTypes.inc new file mode 100644 index 0000000..fe208f5 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/admin/floating-matrix-floatingTypes.inc @@ -0,0 +1,26 @@ +[%# Summon this block to the template by INCLUDEing it like this in the header: + % INCLUDE 'admin/floating-matrix-floatingType.inc' % + You must have the required parameters in the template toolkit parameter-space. +%] +[%#REQUIRES branchRule (Koha::FloatingMatrix::BranchRule), fm (Koha::FloatingMatrix) %] +[% preselectedFloatingType = branchRule.getFloating() %] + diff --git a/koha-tmpl/intranet-tmpl/prog/en/js/admin/floating-matrix.js b/koha-tmpl/intranet-tmpl/prog/en/js/admin/floating-matrix.js new file mode 100644 index 0000000..1547f9e --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/js/admin/floating-matrix.js @@ -0,0 +1,186 @@ +////Create javascript namespace +var FloMax = FloMax || {}; + +//// DOCUMENT READY INIT SCRIPTS + +FloMax.branchRules = {}; //Stores all the edited branchRules so we can rollback HTML changes and CRUD over AJAX. + +FloMax.branchRuleDisabledColor = "#e83519"; +FloMax.branchRuleAlwaysColor = "#219e0e"; +FloMax.branchRulePossibleColor = "#2626bf"; +FloMax.branchRuleConditionalColor = "#af21bc"; +$(document).ready(function() { + //Color cells based on their floatingType + $(".branchRule").each(function(){ + FloMax.changeBranchRuleColor(this); + }); + + //Bind action listeners to the floating matrix + $(".branchRule").bind({ + click: function() { + //If we are clicking a branchCode element, mark it as selected and remove other selections. + if (! $(this).hasClass('selected')) { + $(".branchRule").removeClass('selected'); + $("input[name='conditionRules']").addClass('hidden'); + + $(this).addClass('selected'); + FloMax.changeBranchRuleColor(this); + $(this).find("input[name='conditionRules']").removeClass('hidden'); + } + }, + }); + $(".branchRule input[name='cancel']").bind({ + click: function(event) { + FloMax.replaceBranchRule( $(this).parents(".branchRule") ); + $(this).parents(".branchRule").removeClass('selected'); + $("input[name='conditionRules']").addClass('hidden'); + event.stopPropagation(); + }, + }); + $(".branchRule input[name='submit']").bind({ + click: function(event) { + FloMax.storeBranchRule( $(this).parents(".branchRule") ); + $(this).parents(".branchRule").removeClass('selected'); + $("input[name='conditionRules']").addClass('hidden'); + event.stopPropagation(); + }, + }); + $(".branchRule select").bind({ + change: function() { + //When the floatingType changes, so does the color + var branchRule = $(this).parents(".branchRule"); + FloMax.changeBranchRuleColor(branchRule); + }, + }); +}); +////EOF DOCUMENT READY INIT SCRIPTS + +FloMax.changeBranchRuleColor = function (branchRule) { + var floatingType = $(branchRule).find("select[name='floating']").val(); + var color = "#000000"; + if (floatingType == 'DISABLED') { + color = FloMax.branchRuleDisabledColor; + } + else if (floatingType == 'ALWAYS') { + color = FloMax.branchRuleAlwaysColor; + } + else if (floatingType == 'POSSIBLE') { + color = FloMax.branchRulePossibleColor; + } + else if (floatingType == 'CONDITIONAL') { + color = FloMax.branchRuleConditionalColor; + } + $(branchRule).css('color', color); + $(branchRule).css('background-color', color); +} + +FloMax.displayConditionRulesInput = function (branchRule) { + var conditionRulesInput = $(branchRule).find("input[name='conditionRules']"); + conditionRulesInput.removeClass('hidden'); +} + +FloMax.buildBranchRuleFromHTML = function (branchRule) { + //Get fromBranch by looking at the first column at this row. + var siblingCells = $(branchRule).parent().prevAll(); + var fromBranch = $(siblingCells).eq( ($(siblingCells).size())-1 ); //Get the first cell in this row. + fromBranch = $(fromBranch).html(); + //Get toBranch by looking at the first row of the current column. + var nthColumn = ($(siblingCells).size()); //Get x-position from all siblings + the column header, into this branchRule + var toBranch = $("#floatingMatrix").find("#fmHeaderRow").children("th").eq( nthColumn ).html(); + //Get floating + var floating = $(branchRule).find("select").val(); + //Get conditionRules + var conditionRules = $(branchRule).find("input[name='conditionRules']").val(); + //Get id + var id; + if ($(branchRule).attr('id')) { + id = $(branchRule).attr('id').substring(3); //Skip characters 'br_' + } + var brJSON = {'fromBranch' : fromBranch, + 'toBranch' : toBranch, + 'floating' : floating, + 'conditionRules' : conditionRules, + 'id' : id, + }; + return brJSON; +} +FloMax.storeBranchRule = function (branchRule) { + var brJSON = FloMax.buildBranchRuleFromHTML(branchRule); + FloMax.branchRules[brJSON.fromBranch+'-'+brJSON.toBranch] = brJSON; + FloMax.persistBranchRule(brJSON, branchRule); +} +FloMax.replaceBranchRule = function (branchRule) { + var brJSON = FloMax.buildBranchRuleFromHTML(branchRule); + var oldBrJSON = FloMax.branchRules[brJSON.fromBranch+'-'+brJSON.toBranch]; + var newBranchRule = FloMax.newBranchRuleHTML(oldBrJSON); + branchRule.replaceWith(newBranchRule); +} +FloMax.resetBranchRule = function (branchRule) { + branchRule.replaceWith(newBranchRuleHTML()); +} +FloMax.newBranchRuleHTML = function (branchRuleJSON) { + var branchRuleHTML = $("#br_TEMPLATE").clone('withDataAndElements'); + if (branchRuleJSON && branchRuleJSON.id) { + $(branchRuleHTML).attr('id','br_'+branchRuleJSON.id); + } + else { + $(branchRuleHTML).removeAttr('id'); + } + if (branchRuleJSON && branchRuleJSON.conditionRules) { + $(branchRuleHTML).find("input[name='conditionRules']").removeClass('hidden').val(branchRuleJSON.conditionRules); + } + else { + $(branchRuleHTML).find("input[name='conditionRules']").removeClass('hidden').val(''); + } + if (branchRuleJSON && branchRuleJSON.floating) { + $(branchRuleHTML).find("select").val( branchRuleJSON.floating || 'DISABLED' ); + } + else { + $(branchRuleHTML).find("select").val( 'DISABLED' ); + } + $(branchRuleHTML).removeClass('TEMPLATE'); + FloMax.changeBranchRuleColor(branchRuleHTML); + return branchRuleHTML; +} +/** + * var brJSON = FloMax.buildBranchRuleFromHTML(branchRule); + * FloMax.persistBranchRule(brJSON); + * + * INSERTs or UPDATEs or DELETEs the JSON:ified branchRule to the Koha DB + * @param {object} brJSON - JSON:ified object representation of a branchRule + * from buildBranchRuleFromHTML() + * @param {object} branchRule - the HTML element matching class ".branchRule". + * Used to target display modifications to it. + */ +FloMax.persistBranchRule = function (brJSON, branchRule) { + if (brJSON.floating == 'DISABLED') { + brJSON.delete = 1; //A hack to trick Perl CGI to understand this is a HTTP DELETE-verb. + $.ajax('floating-matrix-api.pl', + {method : 'DELETE', + data : brJSON, + dataType : 'json', + }).done(function(data, textStatus, jqXHR){ + FloMax.resetBranchRule(branchRule); + $(branchRule).removeClass('failedAjax'); + //alert("Saving floating rule "+brJSON.fromBranch+"-"+brJSON.toBranch+" OK, because of the following error:\n"+data.status+" "+data.statusText); + }).fail(function (data, textStatus, jqXHR) { + $(branchRule).addClass('failedAjax'); + var error = $.parseJSON(data.responseText); //Pass the error as JSON so we don't trigger the default Koha error pages. + 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); + }); + } + else { + $.ajax('floating-matrix-api.pl', + {method : 'POST', + data : brJSON, + dataType : 'json', + }).done(function(data, textStatus, jqXHR){ + $(branchRule).removeClass('failedAjax'); + $(branchRule).attr('id', 'br_'+data.id); + }).fail(function (data, textStatus, jqXHR) { + $(branchRule).addClass('failedAjax'); + var error = $.parseJSON(data.responseText); //Pass the error as JSON so we don't trigger the default Koha error pages. + 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); + }); + } +} \ No newline at end of file diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt index c85aa8e..a4bfafe 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt @@ -50,6 +50,8 @@
Define extended attributes (identifiers and statistical categories) for patron records
Library transfer limits
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.
+
Floating matrix
+
Define floating rules between branches
Transport cost matrix
Define transport costs between branches
Item circulation alerts
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/floating-matrix.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/floating-matrix.tt new file mode 100644 index 0000000..1d113a3 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/floating-matrix.tt @@ -0,0 +1,97 @@ +[% INCLUDE 'doc-head-open.inc' %] +Koha › Administration › Floating matrix + +[% INCLUDE 'doc-head-close.inc' %] + + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'cat-search.inc' %] + + + +
+ +
+
+
+

+ Defining floating rules between libraries +

+ +
+
+

There are four types of floating:

+
    +
  • Disabled, denoted by red cells
  • +
  • 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.
  • +
  • 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.
  • +
  • Conditional, denoted by purple cells. This is like always, but only when the given condition matches. +

    + itype eq BK - this will always float all Items with item type 'BK'
    + itype eq BK or itype eq CR - this will always float all Items with item type 'BK' or 'CR'
    + ccode eq FLOAT - this will float all Items with a collection code 'FLOAT'
    + 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'
    + These boolean statements are actually evaluable Perl boolean expressions and target the columns in the koha.items-table. See here for available data columns. +

    +
  • +
+
+ + + + + [% FOR branchcode IN branches.keys.sort; branch = branches.$branchcode %] + + [% END %] + + [% FOR fromBranchCode IN branches.keys.sort; fromBranch = branches.$fromBranchCode %] + + + [% FOR toBranchCode IN branches.keys.sort; toBranch = branches.$toBranchCode; branchRule = fm.getBranchRule(fromBranchCode, toBranchCode) %] + + [% END %] + + [% END %] +
From \ To[% branchcode %]
[% fromBranchCode %] + [% IF toBranchCode == fromBranchCode %] +   + [% ELSE %] +
+ + [% INCLUDE 'admin/floating-matrix-floatingTypes.inc' %] + + +
+ + +
+
+ [% END %] +
+
+
+
+
+[% INCLUDE 'admin-menu.inc' %] +
+
+ + + + +
+ + [% INCLUDE 'admin/floating-matrix-floatingTypes.inc' %] + + +
+ + +
+
+ + + + +[% INCLUDE 'intranet-bottom.inc' %] \ No newline at end of file diff --git a/t/Cucumber/SImpls/FloatingMatrix.pm b/t/Cucumber/SImpls/FloatingMatrix.pm new file mode 100644 index 0000000..4eaff0d --- /dev/null +++ b/t/Cucumber/SImpls/FloatingMatrix.pm @@ -0,0 +1,197 @@ +package SImpls::FloatingMatrix; + +# Copyright Vaara-kirjastot 2015 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; +use Carp; + +use Test::More; + +use Try::Tiny; +use Scalar::Util qw(blessed); + +use Koha::FloatingMatrix; +use Koha::FloatingMatrix::BranchRule; +use C4::Items; + +sub addFloatingMatrixRules { + my $C = shift; + my $S = $C->{stash}->{scenario}; + my $F = $C->{stash}->{feature}; + + $S->{floatingMatrixRules} = {} unless $S->{floatingMatrixRules}; + $F->{floatingMatrixRules} = {} unless $F->{floatingMatrixRules}; + + my $fm = Koha::FloatingMatrix->new(); + + for (my $i=0 ; $idata()}) ; $i++) { + my $hash = $C->data()->[$i]; + my $key = $hash->{fromBranch}.'-'.$hash->{toBranch}; + my $fmRule = Koha::FloatingMatrix::BranchRule->new($hash); + $fm->upsertBranchRule($fmRule); + + $S->{floatingMatrixRules}->{ $key } = $fmRule; + $F->{floatingMatrixRules}->{ $key } = $fmRule; + } + $fm->store(); +} + +sub deleteAllFloatingMatrixRules { + my $fm = Koha::FloatingMatrix->new(); + $fm->deleteAllFloatingMatrixRules(); +} + +sub checkFloatingMatrixRules { + my $C = shift; + my $S = $C->{stash}->{scenario}; + my $F = $C->{stash}->{feature}; + + my $floatingMatrixRules = $S->{floatingMatrixRules}; + + if ($floatingMatrixRules && ref $floatingMatrixRules eq 'HASH') { + my $fm = Koha::FloatingMatrix->new(); + foreach my $key (keys %$floatingMatrixRules) { + my $fmbr = $floatingMatrixRules->{$key}; + if (blessed $fmbr && $fmbr->isa('Koha::FloatingMatrix::BranchRule')) { + my $newFmbr = $fm->getBranchRule($fmbr->getFromBranch(), $fmbr->getToBranch()); + + #Delete the id from the DB-representation, so we can compare them with the id-less test object. + my $storedId = $fmbr->getId(); #Store the id so we wont lose it from the scenario/feature stashes + $fmbr->setId(undef); + $newFmbr->setId(undef); + + my $ok = is_deeply($fmbr, $newFmbr, "FloatingMatrixRule fromBranch '".$fmbr->getFromBranch()."' toBranch '".$fmbr->getToBranch."' found deeply"); + $fmbr->setId($storedId); #Restore the id after comparison. + $newFmbr->setId($storedId); + last unless $ok; + + #Delete branch rule from the Koha::FloatingMatrix internal mapping, + # but not from the DB. Change is reverted next time FloatingMatrix is loaded from Koha::Cache + # This way we ensure we don't accidentally match the same rule twice. + $fm->deleteBranchRule($fmbr); + } + else { + last unless ok(0, "Test object is not a 'Koha::FloatingMatrix::BranchRule'"); + } + } + } +} + +sub When_I_ve_deleted_Floating_matrix_rules_then_cannot_find_them { + my ($C) = shift; + my $S = $C->{stash}->{scenario}; + + my $fm = Koha::FloatingMatrix->new(); + + #1. Make sure the rule we are deleting actually exists first + #2. Delete the rules from FloatingMatrix internal mapping. + #3. UPDATE deletion to DB. + #4. Refresh FloatingMatrix + #5. Check that Rules are really deleted. + + for (my $i=0 ; $idata()}) ; $i++) { + my $hash = $C->data()->[$i]; + + my $existingBranchRule = $fm->getBranchRule($hash->{fromBranch}, $hash->{toBranch}); + ok(($existingBranchRule && blessed $existingBranchRule && $existingBranchRule->isa('Koha::FloatingMatrix::BranchRule')), + "A branchRule for fromBranch '".$hash->{fromBranch}."' toBranch '".$hash->{toBranch}."' exists before deletion"); + + $fm->deleteBranchRule($existingBranchRule); + $existingBranchRule = $fm->getBranchRule($hash->{fromBranch}, $hash->{toBranch}); + ok((not($existingBranchRule)), + "A branchRule for fromBranch '".$hash->{fromBranch}."' toBranch '".$hash->{toBranch}."' deleted from internal map"); + } + $fm->store(); #update deletion to DB + + $fm = Koha::FloatingMatrix->new(); + + for (my $i=0 ; $idata()}) ; $i++) { + my $hash = $C->data()->[$i]; + + my $existingBranchRule = $fm->getBranchRule($hash->{fromBranch}, $hash->{toBranch}); + ok((not($existingBranchRule)), + "A branchRule for fromBranch '".$hash->{fromBranch}."' toBranch '".$hash->{toBranch}."' deleted from DB"); + } +} + +sub When_I_try_to_add_Floating_matrix_rules_with_bad_values_I_get_errors { + my ($C) = shift; + my $data = $C->data(); + + my $fm = Koha::FloatingMatrix->new(); + + my $error = ''; + foreach my $dataElem (@$data) { + try { + my $branchRule = $fm->upsertBranchRule($dataElem); + } catch { + if (blessed($_)){ + if ($_->isa('Koha::Exception::BadParameter')) { + $error = $_->error; + } + else { + $_->rethrow(); + } + } + else { + die $_; + } + }; + + my $es = $dataElem->{errorString}; + last unless ok($error =~ /\Q$es\E/, "Adding a bad overdueRule failed. Expecting '$error' to contain '$es'."); + } +} + +=head When_test_given_Items_floats_then_see_status + +$C->data() must contain columns + barcode - the Barcode of the Item we are checking for floating + fromBranch - branchcode, from which branch we initiate transfer (typically the check-in branch) + toBranch - branchcode, where we would transfer this Item should we initiate a transfer + floatCheck - one of the floatin_matrix.floating enumerations. + To skip testing the given test data-row, use one of the following floatChecks: + no_rule - no floating matrix rule defined for the route, so on floating + same_branch - no floating + fail_condition - CONDITIONAL floating failed because the logical expression from 'conditionRules' returned false. +=cut + +sub When_test_given_Items_floats_then_see_status { + my ($C) = shift; + my $checks = $C->data(); #Get the checks, which might not have manifested itselves to the branchtransfers + ok(($checks && scalar(@$checks) > 0), "You must give checks as the data"); + + #See which checks are supposed to be execute, and which are just to clarify intent. + my @checksInTable; + foreach my $check (@$checks) { + my $status = $check->{floatCheck}; + push @checksInTable, $check if ($status ne 'fail_condition' && $status ne 'same_branch' && $status ne 'no_rule'); + } + + my $fm = Koha::FloatingMatrix->new(); + + ##Check that we get the expected floating value for each test. + foreach my $check (@checksInTable) { + my $item = C4::Items::GetItem(undef, $check->{barcode}); + my $floatType = $fm->checkFloating($item, $check->{fromBranch}, $check->{toBranch}); + + last unless ok(($floatType eq $check->{floatCheck}), "Adding a bad overdueRule failed. Expecting '$floatType', got '".$check->{floatCheck}."'."); + } +} + +1; diff --git a/t/Cucumber/features/FloatingMatrix/floatingMatrixCRUD.feature b/t/Cucumber/features/FloatingMatrix/floatingMatrixCRUD.feature new file mode 100644 index 0000000..e11f716 --- /dev/null +++ b/t/Cucumber/features/FloatingMatrix/floatingMatrixCRUD.feature @@ -0,0 +1,65 @@ +# Copyright Vaara-kirjastot 2015 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +################################################################################ +@floatingMatrix +Feature: Floating matrix CRUD + We must be able to define the Floating matrix rules to use them. + + Scenario: Remove all Floating matrix rules so we can test unhindered. + Given there are no Floating matrix rules + Then there are no Floating matrix rules + + Scenario: Create some default Floating matrix rules + Given a set of Floating matrix rules + | fromBranch | toBranch | floating | conditionRules | + | CPL | FFL | ALWAYS | | + | CPL | IPT | POSSIBLE | | + | FFL | CPL | ALWAYS | | + | FFL | IPT | CONDITIONAL | itype ne BK | + | IPT | FFL | ALWAYS | | + | IPT | CPL | ALWAYS | | + Then I should find the rules from the Floating matrix + + Scenario: Delete some Floating matrix rules + When I've deleted the following Floating matrix rules, then I cannot find them. + | fromBranch | toBranch | + | IPT | FFL | + | IPT | CPL | + + Scenario: Update some Floating matrix rules + Given a set of Floating matrix rules + | fromBranch | toBranch | floating | conditionRules | + | CPL | FFL | POSSIBLE | | + | CPL | IPT | ALWAYS | | + | FFL | CPL | CONDITIONAL | itype ne BK | + | FFL | IPT | CONDITIONAL | ccode eq FLOAT | + Then I should find the rules from the Floating matrix + + Scenario: Intercept bad Floating matrix rules. + When I try to add Floating matrix rules with bad values, I get errors. + | fromBranch | toBranch | floating | conditionRules | errorString | + | | FFL | POSSIBLE | | No 'fromBranch' | + | CPL | | ALWAYS | | No 'toBranch' | + | FFL | CPL | | itype ne BK | No 'floating' | + | FFL | IPT | CONDOM | ccode eq FLOAT | Bad enum | + | FFL | CPL | CONDITIONAL | | No 'conditionRules' when floating = 'CONDITIONAL' | + | FFL | IPT | CONDITIONAL | {system('rm -rf /');}; | Not allowed 'conditionRules' characters | + | 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. | + + Scenario: Tear down any database additions from this feature + When all scenarios are executed, tear down database changes. diff --git a/t/Cucumber/features/FloatingMatrix/floatingMatrixUsage.feature b/t/Cucumber/features/FloatingMatrix/floatingMatrixUsage.feature new file mode 100644 index 0000000..fffcdf2 --- /dev/null +++ b/t/Cucumber/features/FloatingMatrix/floatingMatrixUsage.feature @@ -0,0 +1,104 @@ +# Copyright Vaara-kirjastot 2015 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +################################################################################ +@floatingMatrix +Feature: Floating matrix usage + We need to be able to share collections across branch boundaries. + Eg. a bookmobile and the parent library can float the collection to easily move + Items from the library to the bookmobile and vice-versa. + So we want some Items to stay in the branch where they have been checked-in and + want to prevent some Items from staying in the checked-in branch. + By default if there is no rule, then we use default Koha behaviour. + + Scenario: Set up feature context + Given the Koha-context, we can proceed with other scenarios. + | firstName | surname | branchCode | branchName | userFlags | userEmail | branchPrinter | + | Olli-Antti | Kivilahti | CPL | CeePeeLib | 0 | helpme@example.com | | + And the following system preferences + | systemPreference | value | + | AutomaticItemReturn | 1 | + And a set of Borrowers + | cardnumber | branchcode | categorycode | surname | firstname | address | guarantorbarcode | dateofbirth | + | 111A0001 | CPL | S | Costly | Colt | Strt 11 | | 1985-10-10 | + | 222A0002 | FFL | S | Costly | Caleb | Strt 11 | 111A0001 | 2005-12-12 | + And a set of Biblios + | biblio.title | biblio.author | biblio.copyrightdate | biblioitems.isbn | biblioitems.itemtype | + | I wish I met your mother | Pertti Kurikka | 1960 | 9519671580 | BK | + And a set of Items + | barcode | holdingbranch | homebranch | price | replacementprice | itype | biblioisbn | + | 111N0001 | IPT | CPL | 0.50 | 0.50 | BK | 9519671580 | + | 111N0002 | CPL | CPL | 0.50 | 0.50 | BK | 9519671580 | + | 222N0001 | CPL | FFL | 1.50 | 1.50 | BK | 9519671580 | + | 222N0002 | CPL | FFL | 1.50 | 1.50 | BK | 9519671580 | + | 222N0003 | IPT | FFL | 1.50 | 1.50 | BK | 9519671580 | + | 333N0001 | IPT | IPT | 1.50 | 1.50 | BK | 9519671580 | + | 333N0002 | CPL | IPT | 1.50 | 1.50 | CF | 9519671580 | + | 333N0003 | CPL | IPT | 1.50 | 1.50 | BK | 9519671580 | + + Scenario: Check if Items float as unit test + Given a set of Floating matrix rules + | fromBranch | toBranch | floating | conditionRules | + | CPL | FFL | ALWAYS | | + | IPT | FFL | POSSIBLE | | + | CPL | IPT | CONDITIONAL | itype ne CF | + When I test if given Items can float, then I see if this feature works! + | barcode | fromBranch | toBranch | floatCheck | + | 111N0001 | IPT | CPL | no_rule | + | 111N0002 | CPL | CPL | same_branch | + | 222N0001 | CPL | FFL | ALWAYS | + | 222N0002 | CPL | FFL | ALWAYS | + | 222N0003 | IPT | FFL | POSSIBLE | + | 333N0001 | IPT | IPT | same_branch | + | 333N0002 | CPL | IPT | fail_condition | + | 333N0003 | CPL | IPT | ALWAYS | + + Scenario: Check can Items float or not as unit test. + Given a set of Issues, checked out from the Items' current 'holdingbranch' + | cardnumber | barcode | daysOverdue | + | 111A0001 | 111N0001 | -7 | + | 111A0001 | 111N0002 | -7 | + | 111A0001 | 222N0001 | -7 | + | 111A0001 | 222N0002 | -7 | + | 111A0001 | 222N0003 | -7 | + | 111A0001 | 333N0001 | -7 | + | 111A0001 | 333N0002 | -7 | + | 111A0001 | 333N0003 | -7 | + And a set of Floating matrix rules + | fromBranch | toBranch | floating | conditionRules | + | CPL | FFL | ALWAYS | | + | IPT | FFL | POSSIBLE | | + | CPL | IPT | CONDITIONAL | itype ne CF | + When checked-out Items are checked-in to their 'holdingbranch' + Then the following Items are in-transit + | barcode | fromBranch | toBranch | + | 111N0001 | IPT | CPL | + | 333N0002 | CPL | IPT | + #This Item is checked in to the same branch so we don't transfer it + # | 111N0002 | CPL | CPL | + #This route is always set to float. + # | 222N0001 | CPL | FFL | + # | 222N0002 | CPL | FFL | + #This route is set to possibly float, so by default it floats. + # | 222N0003 | IPT | FFL | + #There is no route definition, so we transfer, but we don't transfer from home to home. + # | 333N0001 | IPT | IPT | + #Conditional route definition, this matches the condition so we don't transfer. + # | 333N0003 | CPL | IPT | + + Scenario: Tear down any database additions from this feature + When all scenarios are executed, tear down database changes. diff --git a/t/Cucumber/steps/common_steps.pl b/t/Cucumber/steps/common_steps.pl index b07c915..613c57f 100644 --- a/t/Cucumber/steps/common_steps.pl +++ b/t/Cucumber/steps/common_steps.pl @@ -41,4 +41,5 @@ When qr/all scenarios are executed, tear down database changes./, sub { SImpls::MessageQueues::deleteAllMessageQueues($C); SImpls::LetterTemplates::deleteLetterTemplates($C); SImpls::SystemPreferences::rollbackSystemPreferences($C); + SImpls::FloatingMatrix::deleteAllFloatingMatrixRules($C); }; diff --git a/t/Cucumber/steps/floatingMatrix_steps.pl b/t/Cucumber/steps/floatingMatrix_steps.pl new file mode 100644 index 0000000..10226f7 --- /dev/null +++ b/t/Cucumber/steps/floatingMatrix_steps.pl @@ -0,0 +1,54 @@ +#!/usr/bin/perl + +# Copyright Vaara-kirjastot 2015 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; +use Test::More; +use Test::BDD::Cucumber::StepFile; + +use SImpls::FloatingMatrix; + +Given qr/there are no Floating matrix rules/, sub { + SImpls::FloatingMatrix::deleteAllFloatingMatrixRules(@_); +}; + +Given qr/a set of Floating matrix rules/, sub { + SImpls::FloatingMatrix::addFloatingMatrixRules(@_); +}; + +When qr/I've deleted the following Floating matrix rules, then I cannot find them./, sub { + SImpls::FloatingMatrix::When_I_ve_deleted_Floating_matrix_rules_then_cannot_find_them(@_); +}; + +When qr/I try to add Floating matrix rules with bad values, I get errors./, sub { + SImpls::FloatingMatrix::When_I_try_to_add_Floating_matrix_rules_with_bad_values_I_get_errors(@_); +}; + +When qr/I test if given Items can float, then I see if this feature works!/, sub { + SImpls::FloatingMatrix::When_test_given_Items_floats_then_see_status(@_); +}; + +Then qr/I should find the rules from the Floating matrix/, sub { + SImpls::FloatingMatrix::checkFloatingMatrixRules(@_); +}; + +Then qr/there are no Floating matrix rules/, sub { + my $schema = Koha::Database->new()->schema(); + my @fmRules = $schema->resultset('FloatingMatrix')->search({})->all; + is((scalar(@fmRules)), 0, "Cleaning Floating matrix rules succeeded"); +}; -- 1.7.9.5