@@ -, +, @@ --- .gitmodules | 3 + C4/Circulation.pm | 894 +++++++------------- C4/IssuingRules.pm | 278 ++++++ C4/Members.pm | 2 + C4/Overdues.pm | 79 +-- C4/Reserves.pm | 632 ++++++++++---- admin/smart-rules-service.pl | 82 ++ admin/smart-rules.pl | 571 +++----------- circ/branchtransfers.pl | 4 +- circ/circulation.pl | 231 ++++-- circ/returns.pl | 130 +++- installer/data/mysql/de-DE/mandatory/sysprefs.sql | 2 +- installer/data/mysql/en/mandatory/sysprefs.sql | 1 + .../1-Obligatoire/unimarc_standard_systemprefs.sql | 2 +- installer/data/mysql/it-IT/necessari/sysprefs.sql | 1 + installer/data/mysql/kohastructure.sql | 14 +- .../data/mysql/nb-NO/1-Obligatorisk/sysprefs.sql | 1 + installer/data/mysql/pl-PL/mandatory/sysprefs.sql | 1 + ...m_preferences_full_optimal_for_install_only.sql | 1 + ...m_preferences_full_optimal_for_install_only.sql | 2 +- installer/data/mysql/updatedatabase.pl | 53 ++ .../intranet-tmpl/prog/en/css/staff-global.css | 3 + .../prog/en/lib/jquery/plugins/jeditable | 1 + .../prog/en/modules/admin/smart-rules.tt | 675 ++++++++------- .../intranet-tmpl/prog/en/modules/circ/returns.tt | 3 + .../prog/en/modules/reserve/request.tt | 41 +- kohaversion.pl | 2 +- opac/opac-modrequest.pl | 4 +- opac/opac-reserve.pl | 67 +- reserve/modrequest.pl | 19 +- reserve/placerequest.pl | 96 +-- reserve/renewscript.pl | 57 ++- reserve/request.pl | 68 +- t/db_dependent/lib/KohaTest/Circulation.pm | 2 - .../lib/KohaTest/Members/GetMemberDetails.pm | 6 +- t/db_dependent/lib/KohaTest/Overdues.pm | 1 - 36 files changed, 2205 insertions(+), 1824 deletions(-) create mode 100644 .gitmodules create mode 100644 C4/IssuingRules.pm create mode 100755 admin/smart-rules-service.pl create mode 160000 koha-tmpl/intranet-tmpl/prog/en/lib/jquery/plugins/jeditable --- a/.gitmodules +++ a/.gitmodules @@ -0,0 +1,3 @@ +[submodule "koha-tmpl/intranet-tmpl/prog/en/lib/jquery/plugins/jeditable"] + path = koha-tmpl/intranet-tmpl/prog/en/lib/jquery/plugins/jeditable + url = https://github.com/tuupola/jquery_jeditable.git --- a/C4/Circulation.pm +++ a/C4/Circulation.pm @@ -28,13 +28,16 @@ use C4::Koha; use C4::Biblio; use C4::Items; use C4::Members; -use C4::Dates; +use C4::IssuingRules; +use C4::Dates qw/format_date/; use C4::Calendar; use C4::Accounts; +use C4::Overdues ; use C4::ItemCirculationAlertPreference; use C4::Dates qw(format_date); use C4::Message; use C4::Debug; +use YAML; use Date::Calc qw( Today Today_and_Now @@ -43,6 +46,9 @@ use Date::Calc qw( Date_to_Days Day_of_Week Add_Delta_Days + Delta_Days + check_date + Add_Delta_Days ); use POSIX qw(strftime); use C4::Branch; # GetBranches @@ -74,9 +80,6 @@ BEGIN { &GetItemIssues &GetBorrowerIssues &GetIssuingCharges - &GetIssuingRule - &GetBranchBorrowerCircRule - &GetBranchItemRule &GetBiblioIssues &GetOpenIssue &AnonymiseIssueHistory @@ -99,6 +102,7 @@ BEGIN { &CreateBranchTransferLimit &DeleteBranchTransferLimits ); + } =head1 NAME @@ -347,116 +351,97 @@ sub TooMany { my $item = shift; my $cat_borrower = $borrower->{'categorycode'}; my $dbh = C4::Context->dbh; - my $branch; + my $exactbranch; # Get which branchcode we need - $branch = _GetCircControlBranch($item,$borrower); - my $type = (C4::Context->preference('item-level_itypes')) + $exactbranch = _GetCircControlBranch($item,$borrower); + my $itype = (C4::Context->preference('item-level_itypes')) ? $item->{'itype'} # item-level : $item->{'itemtype'}; # biblio-level # given branch, patron category, and item type, determine # applicable issuing rule - my $issuing_rule = GetIssuingRule($cat_borrower, $type, $branch); - - # if a rule is found and has a loan limit set, count - # how many loans the patron already has that meet that - # rule - if (defined($issuing_rule) and defined($issuing_rule->{'maxissueqty'})) { - my @bind_params; - my $count_query = "SELECT COUNT(*) FROM issues - JOIN items USING (itemnumber) "; - - my $rule_itemtype = $issuing_rule->{itemtype}; - if ($rule_itemtype eq "*") { - # matching rule has the default item type, so count only - # those existing loans that don't fall under a more - # specific rule - if (C4::Context->preference('item-level_itypes')) { - $count_query .= " WHERE items.itype NOT IN ( - SELECT itemtype FROM issuingrules - WHERE branchcode = ? - AND (categorycode = ? OR categorycode = ?) - AND itemtype <> '*' - ) "; - } else { - $count_query .= " JOIN biblioitems USING (biblionumber) - WHERE biblioitems.itemtype NOT IN ( - SELECT itemtype FROM issuingrules - WHERE branchcode = ? - AND (categorycode = ? OR categorycode = ?) - AND itemtype <> '*' - ) "; - } - push @bind_params, $issuing_rule->{branchcode}; - push @bind_params, $issuing_rule->{categorycode}; - push @bind_params, $cat_borrower; - } else { - # rule has specific item type, so count loans of that - # specific item type - if (C4::Context->preference('item-level_itypes')) { - $count_query .= " WHERE items.itype = ? "; - } else { - $count_query .= " JOIN biblioitems USING (biblionumber) - WHERE biblioitems.itemtype= ? "; - } - push @bind_params, $type; - } - - $count_query .= " AND borrowernumber = ? "; - push @bind_params, $borrower->{'borrowernumber'}; - my $rule_branch = $issuing_rule->{branchcode}; - if ($rule_branch ne "*") { - if (C4::Context->preference('CircControl') eq 'PickupLibrary') { - $count_query .= " AND issues.branchcode = ? "; - push @bind_params, $branch; - } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') { - ; # if branch is the patron's home branch, then count all loans by patron - } else { - $count_query .= " AND items.homebranch = ? "; - push @bind_params, $branch; - } - } - - my $count_sth = $dbh->prepare($count_query); - $count_sth->execute(@bind_params); - my ($current_loan_count) = $count_sth->fetchrow_array; + my $branchfield = C4::Context->Preference('HomeOrHoldingBranch') || "homebranch"; + #By default, Patron is supposed not to be able to borrow + my $toomany = 1; + + foreach my $branch ( $exactbranch, '*' ) { + foreach my $type ( $itype, '*' ) { + my $issuing_rule = GetIssuingRule( $cat_borrower, $type, $branch ); + + # if a rule is found and has a loan limit set, count + # how many loans the patron already has that meet that + # rule + if ( defined($issuing_rule) and defined( $issuing_rule->{'maxissueqty'} ) ) { + my @bind_params; + my $count_query = "SELECT COUNT(*) FROM issues + JOIN items USING (itemnumber) "; + + my $rule_itemtype = $issuing_rule->{itemtype}; + if ($rule_itemtype eq "*") { + # matching rule has the default item type, so count all the items issued for that branch no + # those existing loans that don't fall under a more + # specific rule Not QUITE + if (C4::Context->preference('item-level_itypes')) { + $count_query .= " WHERE items.itype NOT IN ( + SELECT itemtype FROM issuingrules + WHERE branchcode = ? + AND (categorycode = ? OR categorycode = ?) + AND itemtype <> '*' + )"; + } else { + $count_query .= " JOIN biblioitems USING (biblionumber) + WHERE biblioitems.itemtype NOT IN ( + SELECT itemtype FROM issuingrules + WHERE branchcode = ? + AND (categorycode = ? OR categorycode = ?) + AND itemtype <> '*' + )"; + } + push @bind_params, $issuing_rule->{branchcode}; + push @bind_params, $issuing_rule->{categorycode}; + push @bind_params, $cat_borrower; + } else { + # rule has specific item type, so count loans of that + # specific item type + if (C4::Context->preference('item-level_itypes')) { + $count_query .= " WHERE items.itype = ? "; + } else { + $count_query .= " JOIN biblioitems USING (biblionumber) + WHERE biblioitems.itemtype= ? "; + } + push @bind_params, $type; + } - my $max_loans_allowed = $issuing_rule->{'maxissueqty'}; - if ($current_loan_count >= $max_loans_allowed) { - return ($current_loan_count, $max_loans_allowed); - } - } + $count_query .= " AND borrowernumber = ? "; + push @bind_params, $borrower->{'borrowernumber'}; + my $rule_branch = $issuing_rule->{branchcode}; + if ( $rule_branch ne "*" ) { + if ( C4::Context->preference('CircControl') eq 'PickupLibrary' ) { + $count_query .= " AND issues.branchcode = ? "; + push @bind_params, $branch; + } elsif ( C4::Context->preference('CircControl') eq 'PatronLibrary' ) { + ; # if branch is the patron's home branch, then count all loans by patron + } else { + $count_query .= " AND items.$branchfield = ? "; + push @bind_params, $branch; + } + } - # Now count total loans against the limit for the branch - my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower); - if (defined($branch_borrower_circ_rule->{maxissueqty})) { - my @bind_params = (); - my $branch_count_query = "SELECT COUNT(*) FROM issues - JOIN items USING (itemnumber) - WHERE borrowernumber = ? "; - push @bind_params, $borrower->{borrowernumber}; - - if (C4::Context->preference('CircControl') eq 'PickupLibrary') { - $branch_count_query .= " AND issues.branchcode = ? "; - push @bind_params, $branch; - } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') { - ; # if branch is the patron's home branch, then count all loans by patron - } else { - $branch_count_query .= " AND items.homebranch = ? "; - push @bind_params, $branch; - } - my $branch_count_sth = $dbh->prepare($branch_count_query); - $branch_count_sth->execute(@bind_params); - my ($current_loan_count) = $branch_count_sth->fetchrow_array; + my $count_sth = $dbh->prepare($count_query); + $count_sth->execute(@bind_params); + my ($current_loan_count) = $count_sth->fetchrow_array; - my $max_loans_allowed = $branch_borrower_circ_rule->{maxissueqty}; - if ($current_loan_count >= $max_loans_allowed) { - return ($current_loan_count, $max_loans_allowed); + my $max_loans_allowed = $issuing_rule->{'maxissueqty'}; + if ( $current_loan_count >= $max_loans_allowed ) { + return 1,$current_loan_count,$max_loans_allowed; + } + else { + $toomany=0; + } + } } } - - # OK, the patron can issue !!! - return; + return $toomany; } =head2 itemissues @@ -684,7 +669,11 @@ sub CanBookBeIssued { my $branch = _GetCircControlBranch($item,$borrower); my $itype = ( C4::Context->preference('item-level_itypes') ) ? $item->{'itype'} : $biblioitem->{'itemtype'}; - $duedate = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $itype, $branch, $borrower ); + my $loanlength = GetLoanLength( $borrower->{'categorycode'}, $itype, $branch ); + unless ($loanlength or C4::Context->preference("AllowNotForLoanOverride")) { + $issuingimpossible{LOAN_LENGTH_UNDEFINED} = "$borrower->{'categorycode'}, $itype, $branch"; + } + $duedate = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $itype, $branch, $borrower ) ; # Offline circ calls AddIssue directly, doesn't run through here # So issuingimpossible should be ok. @@ -712,15 +701,22 @@ sub CanBookBeIssued { $issuingimpossible{CARD_LOST} = 1; } if ( $borrower->{flags}->{'DBARRED'} ) { - $issuingimpossible{DEBARRED} = 1; + if (my $dateenddebarred=$borrower->{flags}->{'DBARRED'}->{'dateend'}){ + $issuingimpossible{DEBARRED} = format_date($dateenddebarred); + } + else { + $issuingimpossible{DEBARRED} = 1; + } } if ( $borrower->{'dateexpiry'} eq '0000-00-00') { $issuingimpossible{EXPIRED} = 1; } else { - my @expirydate= split /-/,$borrower->{'dateexpiry'}; - if($expirydate[0]==0 || $expirydate[1]==0|| $expirydate[2]==0 || - Date_to_Days(Today) > Date_to_Days( @expirydate )) { - $issuingimpossible{EXPIRED} = 1; + my @expirydate = split (/-/, $borrower->{'dateexpiry'}); + if ( $expirydate[0] == 0 + || $expirydate[1] == 0 + || $expirydate[2] == 0 + || Date_to_Days(Today) > Date_to_Days(@expirydate) ) { + $issuingimpossible{EXPIRED} = 1; } } # @@ -769,18 +765,15 @@ sub CanBookBeIssued { # # JB34 CHECKS IF BORROWERS DONT HAVE ISSUE TOO MANY BOOKS # - my ($current_loan_count, $max_loans_allowed) = TooMany( $borrower, $item->{biblionumber}, $item ); - # if TooMany max_loans_allowed returns 0 the user doesn't have permission to check out this book - if ($max_loans_allowed eq 0) { + my @toomany = TooMany( $borrower, $item->{biblionumber}, $item ); + + if ( $toomany[0] == 1 && scalar(@toomany)<3) { $needsconfirmation{PATRON_CANT} = 1; - } else { - if($max_loans_allowed){ - $needsconfirmation{TOO_MANY} = 1; - $needsconfirmation{current_loan_count} = $current_loan_count; - $needsconfirmation{max_loans_allowed} = $max_loans_allowed; - } + } elsif (scalar(@toomany)==3) { + $needsconfirmation{TOO_MANY} = "$toomany[1] / $toomany[2]"; } + # # ITEM CHECKING # @@ -817,8 +810,18 @@ sub CanBookBeIssued { } } } - if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} == 1 ) - { + if ( $item->{'damaged'} && $item->{'damaged'} > 0 ){ + $needsconfirmation{DAMAGED} = $item->{'damaged'}; + my $fw = GetFrameworkCode($item->{biblionumber}); + my $category = GetAuthValCode('items.damaged',$fw); + my $authorizedvalues = GetAuthorisedValues($category, $item->{damaged}); + + foreach my $authvalue (@$authorizedvalues){ + $needsconfirmation{DAMAGED} = $authvalue->{lib} if $authvalue->{'authorised_value'} eq $item->{'damaged'}; + } + + } + if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} == 1 ) { $issuingimpossible{WTHDRAWN} = 1; } if ( $item->{'restricted'} @@ -826,15 +829,23 @@ sub CanBookBeIssued { { $issuingimpossible{RESTRICTED} = 1; } + my $userenv = C4::Context->userenv; + my $branch=$userenv->{branch}; + my $hbr= $item->{ C4::Context->preference("HomeOrHoldingBranch") }; if ( C4::Context->preference("IndependantBranches") ) { - my $userenv = C4::Context->userenv; if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) { $issuingimpossible{ITEMNOTSAMEBRANCH} = 1 - if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} ); + if ( $hbr ne $branch ); $needsconfirmation{BORRNOTSAMEBRANCH} = GetBranchName( $borrower->{'branchcode'} ) if ( $borrower->{'branchcode'} ne $userenv->{branch} ); } } + my $branchtransferfield=C4::Context->preference("BranchTransferLimitsType") eq "ccode" ? "ccode" : "itype"; + if ( C4::Context->preference("UseBranchTransferLimits") + and !IsBranchTransferAllowed( $branch, $hbr, $item->{ $branchtransferfield } ) ) { + $needsconfirmation{BRANCH_TRANSFER_NOT_ALLOWED} = $hbr; + } + # # CHECK IF BOOK ALREADY ISSUED TO THIS BORROWER @@ -875,30 +886,21 @@ sub CanBookBeIssued { my ( $resborrower ) = C4::Members::GetMemberDetails( $resbor, 0 ); my $branches = GetBranches(); my $branchname = $branches->{ $res->{'branchcode'} }->{'branchname'}; - if ( $resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting" ) - { - # The item is on reserve and waiting, but has been - # reserved by some other patron. - $needsconfirmation{RESERVE_WAITING} = 1; - $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'}; - $needsconfirmation{'ressurname'} = $resborrower->{'surname'}; - $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'}; - $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'}; - $needsconfirmation{'resbranchname'} = $branchname; - $needsconfirmation{'reswaitingdate'} = format_date($res->{'waitingdate'}); - } - elsif ( $restype eq "Reserved" ) { - # The item is on reserve for someone else. - $needsconfirmation{RESERVED} = 1; - $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'}; - $needsconfirmation{'ressurname'} = $resborrower->{'surname'}; - $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'}; - $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'}; - $needsconfirmation{'resbranchname'} = $branchname; - $needsconfirmation{'resreservedate'} = format_date($res->{'reservedate'}); + if( $resbor ne $borrower->{'borrowernumber'}){ + if ( $restype eq "Waiting" ) { + # The item is on reserve and waiting, but has been + # reserved by some other patron. + $needsconfirmation{RESERVE_WAITING} = + "$resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'}, $branchname)"; + } + elsif ( $restype eq "Reserved" ) { + # The item is on reserve for someone else. + $needsconfirmation{RESERVED} = + "$res->{'reservedate'} : $resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'})"; + } } } - return ( \%issuingimpossible, \%needsconfirmation ); + return ( \%issuingimpossible, \%needsconfirmation ); } =head2 AddIssue @@ -979,7 +981,10 @@ sub AddIssue { # who wants to borrow it now. mark it returned before issuing to the new borrower AddReturn( $item->{'barcode'}, - C4::Context->userenv->{'branch'} + C4::Context->userenv->{'branch'}, + undef, + undef, + 1 ); } @@ -1012,7 +1017,9 @@ sub AddIssue { ModReserve(1, $res->{'biblionumber'}, $res->{'borrowernumber'}, - $res->{'branchcode'} + $res->{'branchcode'}, + undef, + $res->{'reservenumber'} ); } } @@ -1101,10 +1108,11 @@ sub AddIssue { branch => $branch, }); } + + logaction( "CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $item->{'itemnumber'} ) + if C4::Context->preference("IssueLog"); } - logaction("CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $biblio->{'biblionumber'}) - if C4::Context->preference("IssueLog"); } return ($datedue); # not necessarily the same as when it came in! } @@ -1119,314 +1127,8 @@ Get loan length for an itemtype, a borrower type and a branch sub GetLoanLength { my ( $borrowertype, $itemtype, $branchcode ) = @_; - my $dbh = C4::Context->dbh; - my $sth = - $dbh->prepare( -"select issuelength from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null" - ); -# warn "in get loan lenght $borrowertype $itemtype $branchcode "; -# try to find issuelength & return the 1st available. -# check with borrowertype, itemtype and branchcode, then without one of those parameters - $sth->execute( $borrowertype, $itemtype, $branchcode ); - my $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; - - $sth->execute( $borrowertype, "*", $branchcode ); - $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; - - $sth->execute( "*", $itemtype, $branchcode ); - $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; - - $sth->execute( "*", "*", $branchcode ); - $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; - - $sth->execute( $borrowertype, $itemtype, "*" ); - $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; - - $sth->execute( $borrowertype, "*", "*" ); - $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; - - $sth->execute( "*", $itemtype, "*" ); - $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; - - $sth->execute( "*", "*", "*" ); - $loanlength = $sth->fetchrow_hashref; - return $loanlength->{issuelength} - if defined($loanlength) && $loanlength->{issuelength} ne 'NULL'; - - # if no rule is set => 21 days (hardcoded) - return 21; -} - - -=head2 GetHardDueDate - - my ($hardduedate,$hardduedatecompare) = &GetHardDueDate($borrowertype,$itemtype,branchcode) - -Get the Hard Due Date and it's comparison for an itemtype, a borrower type and a branch - -=cut - -sub GetHardDueDate { - my ( $borrowertype, $itemtype, $branchcode ) = @_; - my $dbh = C4::Context->dbh; - my $sth = - $dbh->prepare( -"select hardduedate, hardduedatecompare from issuingrules where categorycode=? and itemtype=? and branchcode=?" - ); - $sth->execute( $borrowertype, $itemtype, $branchcode ); - my $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; - - $sth->execute( $borrowertype, "*", $branchcode ); - $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; - - $sth->execute( "*", $itemtype, $branchcode ); - $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; - - $sth->execute( "*", "*", $branchcode ); - $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; - - $sth->execute( $borrowertype, $itemtype, "*" ); - $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; - - $sth->execute( $borrowertype, "*", "*" ); - $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; - - $sth->execute( "*", $itemtype, "*" ); - $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; - - $sth->execute( "*", "*", "*" ); - $results = $sth->fetchrow_hashref; - return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare}) - if defined($results) && $results->{hardduedate} ne 'NULL'; - - # if no rule is set => return undefined - return (undef, undef); -} - -=head2 GetIssuingRule - - my $irule = &GetIssuingRule($borrowertype,$itemtype,branchcode) - -FIXME - This is a copy-paste of GetLoanLength -as a stop-gap. Do not wish to change API for GetLoanLength -this close to release, however, Overdues::GetIssuingRules is broken. - -Get the issuing rule for an itemtype, a borrower type and a branch -Returns a hashref from the issuingrules table. - -=cut - -sub GetIssuingRule { - my ( $borrowertype, $itemtype, $branchcode ) = @_; - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare( "select * from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null" ); - my $irule; - - $sth->execute( $borrowertype, $itemtype, $branchcode ); - $irule = $sth->fetchrow_hashref; - return $irule if defined($irule) ; - - $sth->execute( $borrowertype, "*", $branchcode ); - $irule = $sth->fetchrow_hashref; - return $irule if defined($irule) ; - - $sth->execute( "*", $itemtype, $branchcode ); - $irule = $sth->fetchrow_hashref; - return $irule if defined($irule) ; - - $sth->execute( "*", "*", $branchcode ); - $irule = $sth->fetchrow_hashref; - return $irule if defined($irule) ; - - $sth->execute( $borrowertype, $itemtype, "*" ); - $irule = $sth->fetchrow_hashref; - return $irule if defined($irule) ; - - $sth->execute( $borrowertype, "*", "*" ); - $irule = $sth->fetchrow_hashref; - return $irule if defined($irule) ; - - $sth->execute( "*", $itemtype, "*" ); - $irule = $sth->fetchrow_hashref; - return $irule if defined($irule) ; - - $sth->execute( "*", "*", "*" ); - $irule = $sth->fetchrow_hashref; - return $irule if defined($irule) ; - - # if no rule matches, - return undef; -} - -=head2 GetBranchBorrowerCircRule - - my $branch_cat_rule = GetBranchBorrowerCircRule($branchcode, $categorycode); - -Retrieves circulation rule attributes that apply to the given -branch and patron category, regardless of item type. -The return value is a hashref containing the following key: - -maxissueqty - maximum number of loans that a -patron of the given category can have at the given -branch. If the value is undef, no limit. - -This will first check for a specific branch and -category match from branch_borrower_circ_rules. - -If no rule is found, it will then check default_branch_circ_rules -(same branch, default category). If no rule is found, -it will then check default_borrower_circ_rules (default -branch, same category), then failing that, default_circ_rules -(default branch, default category). - -If no rule has been found in the database, it will default to -the buillt in rule: - -maxissueqty - undef - -C<$branchcode> and C<$categorycode> should contain the -literal branch code and patron category code, respectively - no -wildcards. - -=cut - -sub GetBranchBorrowerCircRule { - my $branchcode = shift; - my $categorycode = shift; - - my $branch_cat_query = "SELECT maxissueqty - FROM branch_borrower_circ_rules - WHERE branchcode = ? - AND categorycode = ?"; - my $dbh = C4::Context->dbh(); - my $sth = $dbh->prepare($branch_cat_query); - $sth->execute($branchcode, $categorycode); - my $result; - if ($result = $sth->fetchrow_hashref()) { - return $result; - } - - # try same branch, default borrower category - my $branch_query = "SELECT maxissueqty - FROM default_branch_circ_rules - WHERE branchcode = ?"; - $sth = $dbh->prepare($branch_query); - $sth->execute($branchcode); - if ($result = $sth->fetchrow_hashref()) { - return $result; - } - - # try default branch, same borrower category - my $category_query = "SELECT maxissueqty - FROM default_borrower_circ_rules - WHERE categorycode = ?"; - $sth = $dbh->prepare($category_query); - $sth->execute($categorycode); - if ($result = $sth->fetchrow_hashref()) { - return $result; - } - - # try default branch, default borrower category - my $default_query = "SELECT maxissueqty - FROM default_circ_rules"; - $sth = $dbh->prepare($default_query); - $sth->execute(); - if ($result = $sth->fetchrow_hashref()) { - return $result; - } - - # built-in default circulation rule - return { - maxissueqty => undef, - }; -} - -=head2 GetBranchItemRule - - my $branch_item_rule = GetBranchItemRule($branchcode, $itemtype); - -Retrieves circulation rule attributes that apply to the given -branch and item type, regardless of patron category. - -The return value is a hashref containing the following key: - -holdallowed => Hold policy for this branch and itemtype. Possible values: - 0: No holds allowed. - 1: Holds allowed only by patrons that have the same homebranch as the item. - 2: Holds allowed from any patron. - -This searches branchitemrules in the following order: - - * Same branchcode and itemtype - * Same branchcode, itemtype '*' - * branchcode '*', same itemtype - * branchcode and itemtype '*' - -Neither C<$branchcode> nor C<$categorycode> should be '*'. - -=cut - -sub GetBranchItemRule { - my ( $branchcode, $itemtype ) = @_; - my $dbh = C4::Context->dbh(); - my $result = {}; - - my @attempts = ( - ['SELECT holdallowed - FROM branch_item_rules - WHERE branchcode = ? - AND itemtype = ?', $branchcode, $itemtype], - ['SELECT holdallowed - FROM default_branch_circ_rules - WHERE branchcode = ?', $branchcode], - ['SELECT holdallowed - FROM default_branch_item_rules - WHERE itemtype = ?', $itemtype], - ['SELECT holdallowed - FROM default_circ_rules'], - ); - - foreach my $attempt (@attempts) { - my ($query, @bind_params) = @{$attempt}; - - # Since branch/category and branch/itemtype use the same per-branch - # defaults tables, we have to check that the key we want is set, not - # just that a row was returned - return $result if ( defined( $result->{'holdallowed'} = $dbh->selectrow_array( $query, {}, @bind_params ) ) ); - } - - # built-in default circulation rule - return { - holdallowed => 2, - }; + my $loanlength=GetIssuingRule($borrowertype,$itemtype,$branchcode); + return $loanlength->{issuelength}; } =head2 AddReturn @@ -1504,8 +1206,8 @@ patron who last borrowed the book. =cut sub AddReturn { - my ( $barcode, $branch, $exemptfine, $dropbox ) = @_; - if ($branch and not GetBranchDetail($branch)) { + my ( $barcode, $branch, $exemptfine, $dropbox, $force) = @_; + if ( $branch and not GetBranchDetail($branch) ) { warn "AddReturn error: branch '$branch' not found. Reverting to " . C4::Context->userenv->{'branch'}; undef $branch; } @@ -1549,9 +1251,17 @@ sub AddReturn { my $branches = GetBranches(); # a potentially expensive call for a non-feature. $branches->{$hbr}->{PE} and $messages->{'IsPermanent'} = $hbr; } - + my $branchtransferfield=C4::Context->preference("BranchTransferLimitsType") eq "ccode" ? "ccode" : "itype"; + $debug && warn "$branch, $hbr, ",C4::Context->preference("BranchTransferLimitsType")," ,",$item->{ $branchtransferfield } ; + $debug && warn Dump($item); + $debug && warn IsBranchTransferAllowed( $branch, $hbr, $item->{ C4::Context->preference("BranchTransferLimitsType") } ); # if indy branches and returning to different branch, refuse the return - if ($hbr ne $branch && C4::Context->preference("IndependantBranches")){ + if ( !$force && ($hbr ne $branch) + && (C4::Context->preference("IndependantBranches") + or ( C4::Context->preference("UseBranchTransferLimits") + and !IsBranchTransferAllowed( $branch, $hbr, $item->{$branchtransferfield } ) ) + ) + ){ $messages->{'Wrongbranch'} = { Wrongbranch => $branch, Rightbranch => $hbr, @@ -1562,7 +1272,6 @@ sub AddReturn { # FIXME - even in an indy branches situation, there should # still be an option for the library to accept the item # and transfer it to its owning library. - return ( $doreturn, $messages, $issue, $borrower ); } if ( $item->{'wthdrawn'} ) { # book has been cancelled @@ -1573,12 +1282,11 @@ sub AddReturn { # case of a return of document (deal with issues and holdingbranch) if ($doreturn) { $borrower or warn "AddReturn without current borrower"; - my $circControlBranch; + my $circControlBranch; if ($dropbox) { - # define circControlBranch only if dropbox mode is set - # don't allow dropbox mode to create an invalid entry in issues (issuedate > today) - # FIXME: check issuedate > returndate, factoring in holidays - $circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );; + $circControlBranch = _GetCircControlBranch( $item, $borrower ); + # don't allow dropbox mode to create an invalid entry in issues (issuedate > returndate) FIXME: actually checks eq, not gt + undef($dropbox) if ( $item->{'issuedate'} eq C4::Dates->today('iso') ); } if ($borrowernumber) { @@ -1586,15 +1294,15 @@ sub AddReturn { $messages->{'WasReturned'} = 1; # FIXME is the "= 1" right? This could be the borrower hash. } - ModItem({ onloan => undef }, $issue->{'biblionumber'}, $item->{'itemnumber'}); + ModItem( { renewals=>0, onloan => undef }, $issue->{'biblionumber'}, $item->{'itemnumber'} ); + # the holdingbranch is updated if the document is returned to another location. + # this is always done regardless of whether the item was on loan or not + if ( $item->{'holdingbranch'} ne $branch ) { + UpdateHoldingbranch( $branch, $item->{'itemnumber'} ); + $item->{'holdingbranch'} = $branch; # update item data holdingbranch too + } } - # the holdingbranch is updated if the document is returned to another location. - # this is always done regardless of whether the item was on loan or not - if ($item->{'holdingbranch'} ne $branch) { - UpdateHoldingbranch($branch, $item->{'itemnumber'}); - $item->{'holdingbranch'} = $branch; # update item data holdingbranch too - } ModDateLastSeen( $item->{'itemnumber'} ); # check if we have a transfer for this document @@ -1621,11 +1329,26 @@ sub AddReturn { _FixAccountForLostAndReturned($item->{'itemnumber'}, $borrowernumber, $barcode); # can tolerate undef $borrowernumber $messages->{'WasLost'} = 1; } + if ($item->{'notforloan'}){ + $messages->{'NotForLoan'} = $item->{'notforloan'}; + } + if ($item->{'damaged'}){ + $messages->{'Damaged'} = $item->{'damaged'}; + } - # fix up the overdues in accounts... - if ($borrowernumber) { + if ($borrowernumber && $doreturn) { + # fix up the overdues in accounts... my $fix = _FixOverduesOnReturn($borrowernumber, $item->{itemnumber}, $exemptfine, $dropbox); defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!"; # zero is OK, check defined + + # fix fine days + my $debardate = _FixFineDaysOnReturn($borrower, $item, $issue->{date_due}); + $messages->{'Debarred'} = $debardate if($debardate); + + # get fines for the borrower + my $fineamount = C4::Overdues::GetFine($borrowernumber); + $messages->{'HaveFines'} = $fineamount if($fineamount); + } # find reserves..... @@ -1661,20 +1384,20 @@ sub AddReturn { branch => $branch, }); } - - logaction("CIRCULATION", "RETURN", $borrowernumber, $item->{'biblionumber'}) - if C4::Context->preference("ReturnLog"); - + + logaction( "CIRCULATION", "RETURN", $borrowernumber, $item->{'itemnumber'} ) + if C4::Context->preference("ReturnLog"); + # 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 (($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){ - if ( 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; + if ( ($doreturn or $messages->{'NotIssued'}) and ( $branch ne $hbr ) and not $messages->{'WrongTransfer'} and ( $validTransfert ne 1 ) ) { + if (C4::Context->preference("AutomaticItemReturn") + or ( C4::Context->preference("UseBranchTransferLimits") + and !IsBranchTransferAllowed( $branch, $hbr, $item->{ $branchtransferfield } ) ) + ) { + $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); $messages->{'WasTransfered'} = 1; @@ -1749,6 +1472,61 @@ sub MarkIssueReturned { $sth_del->execute($borrowernumber, $itemnumber); } +=head2 _FixFineDaysOnReturn + + &_FixFineDaysOnReturn($borrower, $item, $datedue); + +C<$borrower> borrower hashref + +C<$item> item hashref + +C<$datedue> date due + +Internal function, called only by AddReturn that calculate and update the user fine days, and debars him + +=cut + +sub _FixFineDaysOnReturn { + my ($borrower, $item, $datedue) = @_; + + if($datedue){ + $datedue = C4::Dates->new($datedue,"iso"); + }else{ + return; + } + + my $branchcode =_GetCircControlBranch($item, $borrower); + my $calendar = C4::Calendar->new( branchcode => $branchcode ); + my $today = C4::Dates->new(); + + my $deltadays = $calendar->daysBetween($datedue, C4::Dates->new()); + + my $circcontrol = C4::Context::preference('CircControl'); + my $issuingrule = GetIssuingRule($borrower->{categorycode}, $item->{itype}, $branchcode); + my $finedays = $issuingrule->{finedays}; + # exit if no finedays defined + return unless $finedays; + my $grace = $issuingrule->{firstremind}; + + if( $deltadays - $grace > 0){ + my @newdate = Add_Delta_Days(Today(), $deltadays * $finedays ); + my $isonewdate = join('-',@newdate); + my ($deby, $debm, $debd) = split(/-/,$borrower->{debarred}); + if(check_date($deby, $debm, $debd)){ + my @olddate = split(/-/, $borrower->{debarred}); + + if(Delta_Days(@olddate,@newdate) > 0){ + C4::Members::DebarMember($borrower->{borrowernumber}, $isonewdate); + return $isonewdate; + } + }else{ + C4::Members::DebarMember($borrower->{borrowernumber}, $isonewdate); + return $isonewdate; + } + } +} + + =head2 _FixOverduesOnReturn &_FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode); @@ -1923,8 +1701,8 @@ sub _GetCircControlBranch { my $circcontrol = C4::Context->preference('CircControl'); my $branch; - if ($circcontrol eq 'PickupLibrary') { - $branch= C4::Context->userenv->{'branch'} if C4::Context->userenv; + if ($circcontrol eq 'PickupLibrary' && C4::Context->userenv->{'branch'}) { + $branch= C4::Context->userenv->{'branch'}; } elsif ($circcontrol eq 'PatronLibrary') { $branch=$borrower->{branchcode}; } else { @@ -1960,7 +1738,7 @@ sub GetItemIssue { my ($itemnumber) = @_; return unless $itemnumber; my $sth = C4::Context->dbh->prepare( - "SELECT * + "SELECT *, issues.renewals as 'issues.renewals' FROM issues LEFT JOIN items ON issues.itemnumber=items.itemnumber WHERE issues.itemnumber=?"); @@ -2031,7 +1809,7 @@ sub GetItemIssues { } my $results = $sth->fetchall_arrayref({}); foreach (@$results) { - $_->{'overdue'} = ($_->{'date_due'} lt $today) ? 1 : 0; + $_->{'overdue'} = ( $_->{'date_due'} lt $today && !defined($_->{return_date}) ) ? 1 : 0; } return $results; } @@ -2138,69 +1916,42 @@ already renewed the loan. $error will contain the reason the renewal can not pro sub CanBookBeRenewed { # check renewal status - my ( $borrowernumber, $itemnumber, $override_limit ) = @_; + my ( $borrowernumber, $itemnumber ) = @_; my $dbh = C4::Context->dbh; my $renews = 1; - my $renewokay = 0; - my $error; + my $renewokay = 1; + my $error; # Look in the issues table for this item, lent to this borrower, # and not yet returned. # Look in the issues table for this item, lent to this borrower, # and not yet returned. - my %branch = ( - 'ItemHomeLibrary' => 'items.homebranch', - 'PickupLibrary' => 'items.holdingbranch', - 'PatronLibrary' => 'borrowers.branchcode' - ); - my $controlbranch = $branch{C4::Context->preference('CircControl')}; - my $itype = C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype'; + my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef; + my $item = GetItem($itemnumber) or return undef; + my $itemissue = GetItemIssue($itemnumber) or return undef; + my $branchcode = _GetCircControlBranch($item, $borrower); + if ($itemissue->{'overdue'}){ + $renewokay=0; + $error->{message}='overdue'; + } - my $sthcount = $dbh->prepare(" - SELECT - borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch - FROM issuingrules, - issues - LEFT JOIN items USING (itemnumber) - LEFT JOIN borrowers USING (borrowernumber) - LEFT JOIN biblioitems USING (biblioitemnumber) - - WHERE - (issuingrules.categorycode = borrowers.categorycode OR issuingrules.categorycode = '*') - AND - (issuingrules.itemtype = $itype OR issuingrules.itemtype = '*') - AND - (issuingrules.branchcode = $controlbranch OR issuingrules.branchcode = '*') - AND - borrowernumber = ? - AND - itemnumber = ? - ORDER BY - issuingrules.categorycode desc, - issuingrules.itemtype desc, - issuingrules.branchcode desc - LIMIT 1; - "); - - $sthcount->execute( $borrowernumber, $itemnumber ); - if ( my $data1 = $sthcount->fetchrow_hashref ) { - - if ( ( $data1->{renewalsallowed} && $data1->{renewalsallowed} > $data1->{renewals} ) || $override_limit ) { - $renewokay = 1; - } - else { - $error="too_many"; - } - - my ( $resfound, $resrec ) = C4::Reserves::CheckReserves($itemnumber); - if ($resfound) { - $renewokay = 0; - $error="on_reserve" - } + my $issuingrule = GetIssuingRule($borrower->{categorycode}, $item->{itype}, $branchcode); + + if ( $issuingrule->{renewalsallowed} <= $itemissue->{'issues.renewals'} ) { + $renewokay=0; + $error->{message} = "too_many"; + } + my ( $resfound, $resrec ) = C4::Reserves::CheckReserves($itemnumber); + if ($resfound) { + $renewokay = 0; + $error->{message} = "on_reserve"; } - return ($renewokay,$error); + $error->{renewals} = $itemissue->{'issues.renewals'}; + $error->{renewalsallowed}= $issuingrule->{renewalsallowed}; + + return ( $renewokay, $error ); } =head2 AddRenewal @@ -2255,19 +2006,29 @@ sub AddRenewal { # based on the value of the RenewalPeriodBase syspref. unless ($datedue) { - my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef; - my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'}; + my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef; + my $branchcode = _GetCircControlBranch($item, $borrower); + my $loanlength = GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode ); + + $datedue = + ( C4::Context->preference('RenewalPeriodBase') eq 'date_due' ) + ? C4::Dates->new( $issuedata->{date_due}, 'iso' ) + : C4::Dates->new(); + + my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'}; + my $controlbranch = _GetCircControlBranch( $item, $borrower ); + warn "RENEWALPERIOD : ".$loanlength->{renewalperiod}; + my $renewalperiod = $loanlength->{renewalperiod} || GetLoanLength( $borrower->{'categorycode'}, $itype, $controlbranch ); + + $datedue = CalcDateDue( $datedue, $biblio->{'itemtype'}, $issuedata->{'branchcode'}, $borrower, $renewalperiod ); - $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ? - C4::Dates->new($issuedata->{date_due}, 'iso') : - C4::Dates->new(); - $datedue = CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower); } # Update the issues record to have the new due date, and a new count # of how many times it has been renewed. my $renews = $issuedata->{'renewals'} + 1; - $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = ? + $sth = $dbh->prepare( + "UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = ? WHERE borrowernumber=? AND itemnumber=?" ); @@ -2275,8 +2036,8 @@ sub AddRenewal { $sth->finish; # Update the renewal count on the item, and tell zebra to reindex - $renews = $biblio->{'renewals'} + 1; - ModItem({ renewals => $renews, onloan => $datedue->output('iso') }, $biblio->{'biblionumber'}, $itemnumber); + $renews = $item->{'renewals'} + 1; + ModItem( { renewals => $renews, onloan => $datedue->output('iso') }, undef, $itemnumber ); # Charge a new rental fee, if applicable? my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber ); @@ -2692,20 +2453,22 @@ C<$startdate> = C4::Dates object representing start date of loan period (assum C<$itemtype> = itemtype code of item in question C<$branch> = location whose calendar to use C<$borrower> = Borrower object +C<$renewalperiod> = if it's a renewal, give the duration of the renew. If empty, it will be set to initial checkout duration =cut sub CalcDateDue { - my ($startdate,$itemtype,$branch,$borrower) = @_; + my ($startdate,$itemtype,$branch,$borrower,$renewalperiod) = @_; my $datedue; - my $loanlength = GetLoanLength($borrower->{'categorycode'},$itemtype, $branch); # if globalDueDate ON the datedue is set to that date + my $rule = GetIssuingRule($borrower->{'categorycode'},$itemtype, $branch); if ( C4::Context->preference('globalDueDate') && ( C4::Context->preference('globalDueDate') =~ C4::Dates->regexp('syspref') ) ) { $datedue = C4::Dates->new( C4::Context->preference('globalDueDate') ); } else { # otherwise, calculate the datedue as normal + my $loanlength = $renewalperiod || $rule->{issuelength}; if(C4::Context->preference('useDaysMode') eq 'Days') { # ignoring calendar my $timedue = time + ($loanlength) * 86400; #FIXME - assumes now even though we take a startdate @@ -2717,18 +2480,17 @@ sub CalcDateDue { } } - # if Hard Due Dates are used, retreive them and apply as necessary - my ($hardduedate, $hardduedatecompare) = GetHardDueDate($borrower->{'categorycode'},$itemtype, $branch); - if ( $hardduedate && $hardduedate->output('iso') && $hardduedate->output('iso') ne '0000-00-00') { + my ($hardduedate, $hardduedatecompare) = $rule->{qw(hardduedate hardduedatecompare)}; + if ( $hardduedate and $hardduedate ne '0000-00-00') { # if the calculated due date is after the 'before' Hard Due Date (ceiling), override - if ( $datedue->output( 'iso' ) gt $hardduedate->output( 'iso' ) && $hardduedatecompare == -1) { - $datedue = $hardduedate; + if ( $datedue->output( 'iso' ) gt $hardduedate && $hardduedatecompare == -1) { + $datedue = C4::Dates->new( $hardduedate ); # if the calculated date is before the 'after' Hard Due Date (floor), override - } elsif ( $datedue->output( 'iso' ) lt $hardduedate->output( 'iso' ) && $hardduedatecompare == 1) { - $datedue = $hardduedate; + } elsif ( $datedue->output( 'iso' ) lt $hardduedate && $hardduedatecompare == 1) { + $datedue = C4::Dates->new( $hardduedate ); # if the hard due date is set to 'exactly', overrride } elsif ( $hardduedatecompare == 0) { - $datedue = $hardduedate; + $datedue = C4::Dates->new( $hardduedate ); } # in all other cases, keep the date due as it is } @@ -2932,18 +2694,18 @@ sub CreateBranchTransferLimit { =head2 DeleteBranchTransferLimits - DeleteBranchTransferLimits(); + DeleteBranchTransferLimits($tobranch); =cut sub DeleteBranchTransferLimits { - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare("TRUNCATE TABLE branch_transfer_limits"); - $sth->execute(); + my $branch = shift; + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE toBranch = ?"); + $sth->execute($branch); } - - 1; +1; __END__ --- a/C4/IssuingRules.pm +++ a/C4/IssuingRules.pm @@ -0,0 +1,278 @@ +package C4::IssuingRules; + +# Copyright 2009 BibLibre SARL +# +# 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., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA + +use strict; +use warnings; +use C4::Context; +use C4::Koha; +use C4::SQLHelper qw( SearchInTable InsertInTable UpdateInTable DeleteInTable ); +eval { + my $servers = C4::Context->config('memcached_servers'); + if ($servers) { + require Memoize::Memcached; + import Memoize::Memcached qw(memoize_memcached flush_cache); + my $memcached = { + servers => [$servers], + key_prefix => C4::Context->config('memcached_namespace') || 'koha', + }; + memoize_memcached( 'GetIssuingRule', memcached => $memcached, expire_time => 600000 ); #cache for 10 minutes + memoize_memcached( 'GetIssuingRules', memcached => $memcached, expire_time => 600000 ); #cache for 10 minutes + } + +}; +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + +BEGIN { + # set the version for version checking + $VERSION = 3.0.5; + @ISA = qw(Exporter); + @EXPORT = qw( + &GetIssuingRule + &GetIssuingRulesByBranchCode + &GetIssuingRules + &AddIssuingRule + &ModIssuingRule + &DelIssuingRule + ); +} + +=head1 NAME + +C4::IssuingRules - Koha issuing rules module + +=head1 SYNOPSIS + +use C4::IssuingRules; + +=head1 DESCRIPTION + +The functions in this module deal with issuing rules. + +=head1 FUNCTIONS + +=head2 GetIssuingRule + +Compute the issuing rule for an itemtype, a borrower category and a branch. +Returns a hashref from the issuingrules table. + +my $rule = &GetIssuingRule($categorycode, $itemtype, $branchcode); + +The rules are applied from most specific to less specific, using the first found in this order: + * same library, same patron type, same item type + * same library, same patron type, default item type + * same library, default patron type, same item type + * same library, default patron type, default item type + * default library, same patron type, same item type + * default library, same patron type, default item type + * default library, default patron type, same item type + * default library, default patron type, default item type + +The values in the returned hashref are inherited from a more generic rules if undef. + +=cut +#Caching GetIssuingRule + +sub GetIssuingRule { + my ( $categorycode, $itemtype, $branchcode ) = @_; + $categorycode||="*"; + $itemtype||="*"; + $branchcode||="*"; + + # This configuration table defines the order of inheritance. We'll loop over it. + my @attempts = ( + [ "*" , "*" , "*" ], + [ "*" , $itemtype, "*" ], + [ $categorycode, "*" , "*" ], + [ $categorycode, $itemtype, "*" ], + [ "*" , "*" , $branchcode ], + [ "*" , $itemtype, $branchcode ], + [ $categorycode, "*" , $branchcode ], + [ $categorycode, $itemtype, $branchcode ], + ); + + # This complex query returns a nested hashref, so we can access a rule using : + # my $rule = $$rules{$categorycode}{$itemtype}{$branchcode}; + # this will be usefull in the inheritance computation code + my $dbh = C4::Context->dbh; + my $rules = $dbh->selectall_hashref( + "SELECT * FROM issuingrules where branchcode IN ('*',?) and itemtype IN ('*', ?) and categorycode IN ('*',?)", + ["branchcode", "itemtype", "categorycode"], + undef, + ( $branchcode, $itemtype, $categorycode ) + ); + + # This block is for inheritance. It loops over rules returned by the + # previous query. If a value is found in a more specific rule, it replaces + # the old value from the more generic rule. + my $oldrule; + for my $attempt ( @attempts ) { + if ( my $rule = $$rules{@$attempt[2]}{@$attempt[1]}{@$attempt[0]} ) { + if ( $oldrule ) { + for ( keys %$oldrule ) { + if ( defined $rule->{$_} ) { + $oldrule->{$_} = $rule->{$_}; + } + } + } else { + $oldrule = $rule; + } + } + } + if($oldrule){ + return $oldrule; + }else{ + return { + 'itemtype' => $itemtype, + 'categorycode' => $categorycode, + 'branchcode' => $branchcode, + 'holdspickupdelay' => 0, + # 'maxissueqty' => 0, + 'renewalsallowed' => 0, + 'firstremind' => 0, + 'accountsent' => 0, + 'reservecharge' => 0, + 'fine' => 0, + 'restrictedtype' => 0, + 'rentaldiscount' => 0, + 'chargename' => 0, + 'finedays' => 0, + 'holdrestricted' => 0, + 'allowonshelfholds' => 0, + 'reservesallowed' => 0, + 'chargeperiod' => 0, + # 'issuelength' => 0, + 'renewalperiod' => 0, + }; + } +} + +=head2 GetIssuingRulesByBranchCode + + my @issuingrules = &GetIssuingRulesByBranchCode($branchcode); + + Retruns a list of hashref from the issuingrules Koha table for a given + branchcode. + Each hashref will contain data from issuingrules plus human readable names of + patron and item categories. + +=cut + +sub GetIssuingRulesByBranchCode { + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare(" + SELECT issuingrules.*, itemtypes.description AS humanitemtype, categories.description AS humancategorycode + FROM issuingrules + LEFT JOIN itemtypes + ON (itemtypes.itemtype = issuingrules.itemtype) + LEFT JOIN categories + ON (categories.categorycode = issuingrules.categorycode) + WHERE issuingrules.branchcode = ? + ORDER BY humancategorycode, humanitemtype + "); + $sth->execute(shift); + + my $res = $sth->fetchall_arrayref({}); + + return @$res; +} + +=head2 GetIssuingRules + + my @issuingrules = &GetIssuingRules({ + branchcode => $branch, + categorycode => $input->param('categorycode'), + itemtype => $input->param('itemtype'), + }); + + Get an issuing rule from Koha database. + An alias for SearchInTable, see C4::SQLHelper for more help. + +=cut + +sub GetIssuingRules { + my $res = SearchInTable('issuingrules', shift); + return @$res; +} + +=head2 AddIssuingRule + + my $issuingrule = { + branchcode => $branch, + categorycode => $input->param('categorycode'), + itemtype => $input->param('itemtype'), + maxissueqty => $maxissueqty, + renewalsallowed => $input->param('renewalsallowed'), + reservesallowed => $input->param('reservesallowed'), + issuelength => $input->param('issuelength'), + fine => $input->param('fine'), + finedays => $input->param('finedays'), + firstremind => $input->param('firstremind'), + chargeperiod => $input->param('chargeperiod'), + }; + + &AddIssuingRule( $issuingrule ); + + Adds an issuing rule to Koha database. + An alias for InsertInTable, see C4::SQLHelper for more help. + +=cut + +sub AddIssuingRule { InsertInTable('issuingrules',shift); } + +=head2 ModIssuingRule + + &ModIssuingRule( $issuingrule ); + + Update an issuing rule of the Koha database. + An alias for UpdateInTable, see C4::SQLHelper for more help. + +=cut + +sub ModIssuingRule { + if (C4::Context->config('memcached_servers')){ + flush_cache('GetIssuingRule'); + flush_cache('GetIssuingRules'); + } + UpdateInTable('issuingrules',shift); +} + +=head2 DelIssuingRule + + DelIssuingRule({ + branchcode => $branch, + categorycode => $input->param('categorycode'), + itemtype => $input->param('itemtype'), + }); + + Delete an issuing rule from Koha database. + An alias for DeleteInTable, see C4::SQLHelper for more help. + +=cut + +sub DelIssuingRule { DeleteInTable('issuingrules',shift); } + +1; + +=head1 AUTHOR + +Koha Developement team + +Jean-André Santoni + +=cut --- a/C4/Members.pm +++ a/C4/Members.pm @@ -1041,6 +1041,8 @@ sub GetPendingIssues { $bquery .= " OR" if ($i < (scalar(@borrowernumbers) - 1)); } + $bquery="1" unless $bquery; # if nothing, set to 1 (true) to avoid a SQL error + # must avoid biblioitems.* to prevent large marc and marcxml fields from killing performance # FIXME: namespace collision: each table has "timestamp" fields. Which one is "timestamp" ? # FIXME: circ/ciculation.pl tries to sort by timestamp! --- a/C4/Overdues.pm +++ a/C4/Overdues.pm @@ -24,6 +24,7 @@ use strict; use Date::Calc qw/Today Date_to_Days/; use Date::Manip qw/UnixDate/; use C4::Circulation; +use C4::IssuingRules; use C4::Context; use C4::Accounts; use C4::Log; # logaction @@ -72,9 +73,6 @@ BEGIN { push @EXPORT, qw( &GetIssuesIteminfo ); - # - # &GetIssuingRules - delete. - # use C4::Circulation::GetIssuingRule instead. # subs to move to Members.pm push @EXPORT, qw( @@ -125,7 +123,7 @@ sub Getoverdues { SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode FROM issues LEFT JOIN items USING (itemnumber) - WHERE date_due < CURDATE() + WHERE DATE(date_due) < CURDATE() "; } else { $statement = " @@ -133,7 +131,7 @@ LEFT JOIN items USING (itemnumber) FROM issues LEFT JOIN items USING (itemnumber) LEFT JOIN biblioitems USING (biblioitemnumber) - WHERE date_due < CURDATE() + WHERE DATE(date_due) < CURDATE() "; } @@ -171,7 +169,7 @@ sub checkoverdues { LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber WHERE issues.borrowernumber = ? - AND issues.date_due < CURDATE()" + AND DATE(issues.date_due) < CURDATE()" ); # FIXME: SELECT * across 4 tables? do we really need the marc AND marcxml blobs?? $sth->execute($borrowernumber); @@ -246,7 +244,7 @@ sub CalcFine { my $daystocharge; # get issuingrules (fines part will be used) $debug and warn sprintf("CalcFine calling GetIssuingRule(%s, %s, %s)", $bortype, $item->{'itemtype'}, $branchcode); - my $data = C4::Circulation::GetIssuingRule($bortype, $item->{'itemtype'}, $branchcode); + my $data = GetIssuingRule($bortype, $item->{'itemtype'}, $branchcode); if($difference) { # if $difference is supplied, the difference has already been calculated, but we still need to adjust for the calendar. # use copy-pasted functions from calendar module. (deprecated -- these functions will be removed from C4::Overdues ). @@ -270,7 +268,7 @@ sub CalcFine { } else { # a zero (or null) chargeperiod means no charge. } - $amount = C4::Context->preference('maxFine') if(C4::Context->preference('maxFine') && ( $amount > C4::Context->preference('maxFine'))); + $amount = C4::Context->preference('MaxFine') if ( C4::Context->preference('MaxFine') && ( $amount > C4::Context->preference('MaxFine') ) ); $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $data->{'chargename'}, $days_minus_grace, $daystocharge); return ($amount, $data->{'chargename'}, $days_minus_grace, $daystocharge); # FIXME: chargename is NEVER populated anywhere. @@ -586,7 +584,7 @@ Returns the replacement cost of the item with the given item number. =cut -#' + sub ReplacementCost { my ($itemnum) = @_; my $dbh = C4::Context->dbh; @@ -605,60 +603,22 @@ sub ReplacementCost { return the total of fine -C<$itemnum> is item number - C<$borrowernumber> is the borrowernumber -=cut - +=cut sub GetFine { - my ( $itemnum, $borrowernumber ) = @_; + my ( $borrowernumber ) = @_; my $dbh = C4::Context->dbh(); my $query = "SELECT sum(amountoutstanding) FROM accountlines where accounttype like 'F%' - AND amountoutstanding > 0 AND itemnumber = ? AND borrowernumber=?"; + AND amountoutstanding > 0 AND borrowernumber=?"; my $sth = $dbh->prepare($query); - $sth->execute( $itemnum, $borrowernumber ); + $sth->execute( $borrowernumber ); my $data = $sth->fetchrow_hashref(); return ( $data->{'sum(amountoutstanding)'} ); } - -=head2 GetIssuingRules - -FIXME - This sub should be deprecated and removed. -It ignores branch and defaults. - - $data = &GetIssuingRules($itemtype,$categorycode); - -Looks up for all issuingrules an item info - -C<$itemnumber> is a reference-to-hash whose keys are all of the fields -from the borrowers and categories tables of the Koha database. Thus, - -C<$categorycode> contains information about borrowers category - -C<$data> contains all information about both the borrower and -category he or she belongs to. -=cut - -sub GetIssuingRules { - warn "GetIssuingRules is deprecated: use GetIssuingRule from C4::Circulation instead."; - my ($itemtype,$categorycode)=@_; - my $dbh = C4::Context->dbh(); - my $query=qq|SELECT * - FROM issuingrules - WHERE issuingrules.itemtype=? - AND issuingrules.categorycode=? - |; - my $sth = $dbh->prepare($query); - # print $query; - $sth->execute($itemtype,$categorycode); - return $sth->fetchrow_hashref; -} - - sub ReplacementCost2 { my ( $itemnum, $borrowernumber ) = @_; my $dbh = C4::Context->dbh(); @@ -1034,7 +994,7 @@ sub GetOverduerules { Check if the borrowers is already debarred -C<$debarredstatus> return 0 for not debarred and return 1 for debarred +C<$debarredstatus> return undef for not debarred and return end of debar date for debarred C<$borrowernumber> contains the borrower number @@ -1048,32 +1008,35 @@ sub CheckBorrowerDebarred { SELECT debarred FROM borrowers WHERE borrowernumber=? + AND debarred > NOW() |; my $sth = $dbh->prepare($query); $sth->execute($borrowernumber); - my ($debarredstatus) = $sth->fetchrow; - return ( $debarredstatus eq '1' ? 1 : 0 ); + my $debarredstatus= $sth->fetchrow; + return $debarredstatus; + } =head2 UpdateBorrowerDebarred - ($borrowerstatut) = &UpdateBorrowerDebarred($borrowernumber); + ($borrowerstatut) = &UpdateBorrowerDebarred($borrowernumber, $todate); update status of borrowers in borrowers table (field debarred) C<$borrowernumber> borrower number +C<$todate> end of bare =cut sub UpdateBorrowerDebarred{ - my($borrowernumber) = @_; + my($borrowernumber, $todate) = @_; my $dbh = C4::Context->dbh; my $query=qq|UPDATE borrowers - SET debarred='1' + SET debarred=? WHERE borrowernumber=? |; my $sth=$dbh->prepare($query); - $sth->execute($borrowernumber); + $sth->execute($todate, $borrowernumber); $sth->finish; return 1; } --- a/C4/Reserves.pm +++ a/C4/Reserves.pm @@ -22,6 +22,7 @@ package C4::Reserves; use strict; #use warnings; FIXME - Bug 2505 +use Date::Calc qw( Add_Delta_Days Time_to_Date Today); use C4::Context; use C4::Biblio; use C4::Members; @@ -32,10 +33,11 @@ use C4::Accounts; # for _koha_notify_reserve use C4::Members::Messaging; -use C4::Members qw(); +use C4::Members ; use C4::Letters; use C4::Branch qw( GetBranchDetail ); use C4::Dates qw( format_date_in_iso ); +use C4::IssuingRules; use List::MoreUtils qw( firstidx ); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); @@ -91,11 +93,14 @@ BEGIN { @EXPORT = qw( &AddReserve + &GetMaxPickupDelay + &GetMaxPickupDate &GetReservesFromItemnumber &GetReservesFromBiblionumber &GetReservesFromBorrowernumber &GetReservesForBranch &GetReservesToBranch + &GetReservesControlBranch &GetReserveCount &GetReserveFee &GetReserveInfo @@ -113,13 +118,16 @@ BEGIN { &CheckReserves &CanBookBeReserved &CanItemBeReserved - &CancelReserve - &CancelExpiredReserves + &CanHoldOnShelf + &CancelReserve + &CancelExpiredReserves - &IsAvailableForItemLevelRequest - - &AlterPriority - &ToggleLowestPriority + &IsAvailableForItemLevelRequest + + &AlterPriority + &ToggleLowestPriority + &CanHoldMultipleItems + &BorrowerHasReserve ); } @@ -142,11 +150,17 @@ sub AddReserve { my $const = lc substr( $constraint, 0, 1 ); $resdate = format_date_in_iso( $resdate ) if ( $resdate ); $resdate = C4::Dates->today( 'iso' ) unless ( $resdate ); + my $item = C4::Items::GetItem($checkitem); + my @maxPickupDate = GetMaxPickupDate($resdate, $borrowernumber, $item); + my $maxpickupdate = sprintf( "%d-%02d-%02d", @maxPickupDate ); if ($expdate) { $expdate = format_date_in_iso( $expdate ); + my @expdate = split("-", $expdate); + $expdate = $maxpickupdate if Delta_Days(@expdate[ 0 .. 2 ], @maxPickupDate[ 0 .. 2 ]) < 0; } else { - undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00' + $expdate = $maxpickupdate; } + undef $expdate if $resdate eq $maxpickupdate; # make reserves.expirationdate default to null rather than '0000-00-00' if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) { # Make room in reserves for this before those of a later reserve date $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority ); @@ -250,24 +264,27 @@ sub GetReservesFromBiblionumber { # Find the desired items in the reserves my $query = " - SELECT branchcode, - timestamp AS rtimestamp, - priority, - biblionumber, - borrowernumber, - reservedate, - constrainttype, - found, - itemnumber, - reservenotes, - expirationdate, - lowestPriority + SELECT reserves.reservenumber, + reserves.branchcode, + reserves.timestamp AS rtimestamp, + reserves.priority, + reserves.biblionumber, + reserves.borrowernumber, + reserves.reservedate, + reserves.constrainttype, + reserves.found, + reserves.itemnumber, + reserves.reservenotes, + reserves.expirationdate, + reserves.lowestPriority, + biblioitems.itemtype FROM reserves - WHERE biblionumber = ? "; + LEFT JOIN biblioitems ON biblioitems.biblionumber = reserves.biblionumber + WHERE reserves.biblionumber = ? "; unless ( $all_dates ) { $query .= "AND reservedate <= CURRENT_DATE()"; } - $query .= "ORDER BY priority"; + $query .= "ORDER BY reserves.priority"; my $sth = $dbh->prepare($query); $sth->execute($biblionumber); my @results; @@ -373,15 +390,45 @@ sub GetReservesFromBorrowernumber { return @$data; } #------------------------------------------------------------------------------------- +sub BorrowerHasReserve { + my ( $borrowernumber, $biblionumber, $itemnumber ) = @_; + my $dbh = C4::Context->dbh; + my $sth; + + if ( $biblionumber ) { + $sth = $dbh->prepare(" + SELECT COUNT(*) AS hasReserve + FROM reserves + WHERE borrowernumber = ? + AND biblionumber = ? + ORDER BY reservedate + "); + $sth->execute( $borrowernumber, $biblionumber ); + } elsif ( $itemnumber ) { + $sth = $dbh->prepare(" + SELECT COUNT(*) AS hasReserve + FROM reserves + WHERE borrowernumber = ? + AND itemnumber = ? + ORDER BY reservedate + "); + $sth->execute( $borrowernumber, $itemnumber ); + } else { + return -1; + } + + my $data = $sth->fetchrow_hashref(); + return $data->{'hasReserve'}; +} + =head2 CanBookBeReserved - $error = &CanBookBeReserved($borrowernumber, $biblionumber) +$error = &CanBookBeReserved($borrowernumber, $biblionumber) =cut sub CanBookBeReserved{ my ($borrowernumber, $biblionumber) = @_; - my @items = GetItemsInfo($biblionumber); foreach my $item (@items){ return 1 if CanItemBeReserved($borrowernumber, $item->{itemnumber}); @@ -399,7 +446,6 @@ This function return 1 if an item can be issued by this borrower. sub CanItemBeReserved{ my ($borrowernumber, $itemnumber) = @_; - my $dbh = C4::Context->dbh; my $allowedreserves = 0; @@ -407,7 +453,7 @@ sub CanItemBeReserved{ my $itype = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype"; # we retrieve borrowers and items informations # - my $item = GetItem($itemnumber); + my $item = C4::Items::GetItem($itemnumber); my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber); # we retrieve user rights on this itemtype and branchcode @@ -438,22 +484,31 @@ sub CanItemBeReserved{ my $branchfield = "reserves.branchcode"; if( $controlbranch eq "ItemHomeLibrary" ){ - $branchfield = "items.homebranch"; - $branchcode = $item->{homebranch}; + my $field=C4::Context->preference("HomeOrHoldingBranch")||"homebranch"; + $branchfield = "items.$field"; + $branchcode = $item->{$field}; }elsif( $controlbranch eq "PatronLibrary" ){ $branchfield = "borrowers.branchcode"; $branchcode = $borrower->{branchcode}; } - # we retrieve rights - $sth->execute($categorycode, $itemtype, $branchcode); - if(my $rights = $sth->fetchrow_hashref()){ - $itemtype = $rights->{itemtype}; - $allowedreserves = $rights->{reservesallowed}; + # we retrieve user rights on this itemtype and branchcode + my $issuingrule = GetIssuingRule($borrower->{categorycode}, $item->{$itype}, $branchcode); + return 0 if( defined $issuingrule->{reservesallowed} && not $issuingrule->{reservesallowed} ); + + # We retrieve the count of reserves allowed for this category code + $issuingrule = GetIssuingRule ($borrower->{categorycode}, "*", "*"); + + if($issuingrule){ + $itemtype = $issuingrule->{itemtype}; + $allowedreserves = $issuingrule->{reservesallowed}; }else{ $itemtype = '*'; } + return 0 if ($issuingrule->{reservesallowed}==0 || + ($issuingrule->{holdrestricted}== 1 && !($branchcode eq $borrower->{branchcode})) + ); # we retrieve count $querycount .= "AND $branchfield = ?"; @@ -479,6 +534,106 @@ sub CanItemBeReserved{ return 0; } } + +=item GetMaxPickupDelay + +$resallowed = &GetMaxPickupDelay($borrowernumber, $itemnumber) + +this function return the number of allowed reserves. + +=cut + +sub GetMaxPickupDelay { + my ($borrowernumber, $itemnumber) = @_; + + my $dbh = C4::Context->dbh; + my $allowedreserves = 0; + + my $itype = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype"; + + # we retrieve borrowers and items informations # + my $item = C4::Items::GetItem($itemnumber); + my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber); + my $controlbranch = GetReservesControlBranch($borrower,$item); + + # we retrieve user rights on this itemtype and branchcode + my $sth = $dbh->prepare("SELECT holdspickupdelay + FROM issuingrules + WHERE categorycode=? + AND itemtype=? + AND branchcode=? + AND holdspickupdelay IS NOT NULL" + ); + + my $itemtype = $item->{$itype}; + my $borrowertype = $borrower->{categorycode}; + my $branchcode = $controlbranch; + + $sth->execute( $borrowertype, $itemtype, $branchcode ); + my $pickupdelay = $sth->fetchrow_hashref; + return $pickupdelay->{holdspickupdelay} + if defined($pickupdelay) && $pickupdelay->{holdspickupdelay} ne 'NULL'; + + $sth->execute( $borrowertype, "*", $branchcode ); + $pickupdelay = $sth->fetchrow_hashref; + return $pickupdelay->{holdspickupdelay} + if defined($pickupdelay) && $pickupdelay->{holdspickupdelay} ne 'NULL'; + + $sth->execute( "*", $itemtype, $branchcode ); + $pickupdelay = $sth->fetchrow_hashref; + return $pickupdelay->{holdspickupdelay} + if defined($pickupdelay) && $pickupdelay->{holdspickupdelay} ne 'NULL'; + + $sth->execute( "*", "*", $branchcode ); + $pickupdelay = $sth->fetchrow_hashref; + return $pickupdelay->{holdspickupdelay} + if defined($pickupdelay) && $pickupdelay->{holdspickupdelay} ne 'NULL'; + + $sth->execute( $borrowertype, $itemtype, "*" ); + $pickupdelay = $sth->fetchrow_hashref; + return $pickupdelay->{holdspickupdelay} + if defined($pickupdelay) && $pickupdelay->{holdspickupdelay} ne 'NULL'; + + $sth->execute( $borrowertype, "*", "*" ); + $pickupdelay = $sth->fetchrow_hashref; + return $pickupdelay->{holdspickupdelay} + if defined($pickupdelay) && $pickupdelay->{holdspickupdelay} ne 'NULL'; + + $sth->execute( "*", $itemtype, "*" ); + $pickupdelay = $sth->fetchrow_hashref; + return $pickupdelay->{holdspickupdelay} + if defined($pickupdelay) && $pickupdelay->{holdspickupdelay} ne 'NULL'; + + $sth->execute( "*", "*", "*" ); + $pickupdelay = $sth->fetchrow_hashref; + return $pickupdelay->{holdspickupdelay} + if defined($pickupdelay) && $pickupdelay->{holdspickupdelay} ne 'NULL'; + + # if nothing found, return no rights + return 0; +} + +=item GetMaxPickupDate + +$maxpickupdate = &GetMaxPickupDate($date); + +this function return the max pickup date. +(e.g. : the date after which the hold will be considered cancelled) + +=cut + +sub GetMaxPickupDate{ + my $inputdate=shift; + my $borrowernumber=shift; + my $item=shift; + return unless $inputdate; + my @date = split(/-/,$inputdate); + my $delay = GetMaxPickupDelay($borrowernumber, $item->{'itemnumber'}); + ( @date ) = + Add_Delta_Days( @date[0..2], $delay); + return @date; +} + #-------------------------------------------------------------------------------- =head2 GetReserveCount @@ -518,21 +673,22 @@ sub GetOtherReserves { my $nextreservinfo; my ( $restype, $checkreserves ) = CheckReserves($itemnumber); if ($checkreserves) { - my $iteminfo = GetItem($itemnumber); + my $iteminfo = C4::Items::GetItem($itemnumber); if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) { $messages->{'transfert'} = $checkreserves->{'branchcode'}; #minus priorities of others reservs ModReserveMinusPriority( $itemnumber, $checkreserves->{'borrowernumber'}, - $iteminfo->{'biblionumber'} + $iteminfo->{'biblionumber'}, + $checkreserves->{'reservenumber'} ); #launch the subroutine dotransfer C4::Items::ModItemTransfer( $itemnumber, $iteminfo->{'holdingbranch'}, - $checkreserves->{'branchcode'} + $checkreserves->{'branchcode'}, ), ; } @@ -543,7 +699,8 @@ sub GetOtherReserves { ModReserveMinusPriority( $itemnumber, $checkreserves->{'borrowernumber'}, - $iteminfo->{'biblionumber'} + $iteminfo->{'biblionumber'}, + $checkreserves->{'reservenumber'} ); ModReserveStatus($itemnumber,'W'); } @@ -657,6 +814,26 @@ sub GetReserveFee { return $fee; } +=head2 GetReservesControlBranch + +$branchcode = &GetReservesControlBranch($borrower, $item) + +Returns the branchcode to consider to check hold rules against + +=cut +sub GetReservesControlBranch{ + my ($borrower, $item) = @_; + my $controlbranch = C4::Context->preference('ReservesControlBranch'); + my $hbr = C4::Context->preference('HomeOrHoldingBranch')||"homebranch"; + my $branchcode="*"; + if($controlbranch eq "ItemHomeLibrary"){ + $branchcode = $item->{$hbr}; + } elsif($controlbranch eq "PatronLibrary"){ + $branchcode = $borrower->{'branchcode'}; + } + return $branchcode; +} + =head2 GetReservesToBranch @transreserv = GetReservesToBranch( $frombranch ); @@ -804,12 +981,6 @@ sub CheckReserves { } else { # See if this item is more important than what we've got so far if ( $res->{'priority'} && $res->{'priority'} < $priority ) { - my $borrowerinfo=C4::Members::GetMemberDetails($res->{'borrowernumber'}); - my $iteminfo=C4::Items::GetItem($itemnumber); - my $branch=C4::Circulation::_GetCircControlBranch($iteminfo,$borrowerinfo); - my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'}); - next if ($branchitemrule->{'holdallowed'} == 0); - next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'})); $priority = $res->{'priority'}; $highest = $res; } @@ -846,23 +1017,18 @@ sub CancelExpiredReserves { $sth->execute(); while ( my $res = $sth->fetchrow_hashref() ) { - CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} ); + CancelReserve( $res->{'reservenumber'}, $res->{'biblionumber'} ); } } =head2 CancelReserve - &CancelReserve($biblionumber, $itemnumber, $borrowernumber); + &CancelReserve( $reservenumber, $biblionumber ); Cancels a reserve. -Use either C<$biblionumber> or C<$itemnumber> to specify the item to -cancel, but not both: if both are given, C<&CancelReserve> does -nothing. - -C<$borrowernumber> is the borrower number of the patron on whose -behalf the book was reserved. +C<$reservenumber> is the unique key for the reserve to be canceled. If C<$biblionumber> was given, C<&CancelReserve> also adjusts the priorities of the other people who are waiting on the book. @@ -870,9 +1036,14 @@ priorities of the other people who are waiting on the book. =cut sub CancelReserve { - my ( $biblio, $item, $borr ) = @_; + my ( $reservenumber, $biblio ) = @_; my $dbh = C4::Context->dbh; - if ( $item and $borr ) { + + my $sth = $dbh->prepare('SELECT * FROM reserves WHERE reservenumber = ?'); + $sth->execute( $reservenumber ); + my $reserve = $sth->fetchrow_hashref(); + + if ( $reserve->{'found'} eq 'W' ) { # removing a waiting reserve record.... # update the database... my $query = " @@ -880,27 +1051,46 @@ sub CancelReserve { SET cancellationdate = now(), found = Null, priority = 0 - WHERE itemnumber = ? - AND borrowernumber = ? + WHERE reservenumber = ? "; my $sth = $dbh->prepare($query); - $sth->execute( $item, $borr ); - $sth->finish; + $sth->execute( $reservenumber ); + + # get reserve information to place into old_reserves $query = " INSERT INTO old_reserves SELECT * FROM reserves - WHERE itemnumber = ? - AND borrowernumber = ? + WHERE reservenumber = ? "; $sth = $dbh->prepare($query); - $sth->execute( $item, $borr ); + $sth->execute( $reservenumber ); + my $holditem = $sth->fetchrow_hashref; + my $insert_fields = ''; + my $value_fields = ''; + foreach my $column ('borrowernumber','reservedate','biblionumber','constrainttype','branchcode','notificationdate','reminderdate','cancellationdate','reservenotes','priority','found','itemnumber','waitingdate','expirationdate') { + if (defined($holditem->{$column})) { + if (length($insert_fields)) { + $insert_fields .= ",$column"; + $value_fields .= ",\'$holditem->{$column}\'"; + } + else { + $insert_fields .= "$column"; + $value_fields .= "\'$holditem->{$column}\'"; + } + } + } + $query = qq/ + INSERT INTO old_reserves ($insert_fields) + VALUES ($value_fields) + /; + $sth = $dbh->prepare($query); + $sth->execute(); $query = " DELETE FROM reserves - WHERE itemnumber = ? - AND borrowernumber = ? + WHERE reservenumber = ? "; $sth = $dbh->prepare($query); - $sth->execute( $item, $borr ); + $sth->execute( $reservenumber ); } else { # removing a reserve record.... @@ -908,52 +1098,72 @@ sub CancelReserve { my $priority; my $query = qq/ SELECT priority FROM reserves - WHERE biblionumber = ? - AND borrowernumber = ? + WHERE reservenumber = ? AND cancellationdate IS NULL AND itemnumber IS NULL /; my $sth = $dbh->prepare($query); - $sth->execute( $biblio, $borr ); + $sth->execute( $reservenumber ); ($priority) = $sth->fetchrow_array; $sth->finish; $query = qq/ UPDATE reserves SET cancellationdate = now(), found = Null, - priority = 0 - WHERE biblionumber = ? - AND borrowernumber = ? + priority = 0, + expirationdate = NULL + WHERE reservenumber = ? /; # update the database, removing the record... $sth = $dbh->prepare($query); - $sth->execute( $biblio, $borr ); - $sth->finish; + $sth->execute( $reservenumber ); $query = qq/ INSERT INTO old_reserves SELECT * FROM reserves - WHERE biblionumber = ? - AND borrowernumber = ? + WHERE reservenumber = ? + /; + $sth = $dbh->prepare($query); + $sth->execute( $reservenumber ); + my $holditem = $sth->fetchrow_hashref; + + my $insert_fields = ''; + my $value_fields = ''; + foreach my $column ('borrowernumber','reservedate','biblionumber','constrainttype','branchcode','notificationdate','reminderdate','cancellationdate','reservenotes','priority','found','itemnumber','waitingdate','expirationdate') { + if (defined($holditem->{$column})) { + if (length($insert_fields)) { + $insert_fields .= ",$column"; + $value_fields .= ",\'$holditem->{$column}\'"; + } + else { + $insert_fields .= "$column"; + $value_fields .= "\'$holditem->{$column}\'"; + } + } + } + $query = qq/ + INSERT INTO old_reserves ($insert_fields) + VALUES ($value_fields) /; $sth = $dbh->prepare($query); - $sth->execute( $biblio, $borr ); + $sth->execute(); $query = qq/ DELETE FROM reserves - WHERE biblionumber = ? - AND borrowernumber = ? + WHERE reservenumber = ? /; $sth = $dbh->prepare($query); - $sth->execute( $biblio, $borr ); + $sth->execute( $reservenumber ); # now fix the priority on the others.... - _FixPriority( $biblio, $borr ); + _FixPriority( '', '', $priority,'', $reservenumber ); } } -=head2 ModReserve +=item ModReserve + +=over 4 ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber]) @@ -986,52 +1196,48 @@ itemnumber and supplying itemnumber. sub ModReserve { #subroutine to update a reserve - my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_; + my ( $rank, $biblio, $borrower, $branch , $itemnumber, $reservenumber) = @_; return if $rank eq "W"; return if $rank eq "n"; my $dbh = C4::Context->dbh; if ( $rank eq "del" ) { my $query = qq/ UPDATE reserves - SET cancellationdate=now() - WHERE biblionumber = ? - AND borrowernumber = ? + SET cancellationdate=now(), + expirationdate = NULL + WHERE reservenumber = ? /; my $sth = $dbh->prepare($query); - $sth->execute( $biblio, $borrower ); + $sth->execute( $reservenumber ); $sth->finish; $query = qq/ INSERT INTO old_reserves - SELECT * - FROM reserves - WHERE biblionumber = ? - AND borrowernumber = ? + SELECT * FROM reserves + WHERE reservenumber = ? /; $sth = $dbh->prepare($query); - $sth->execute( $biblio, $borrower ); + $sth->execute( $reservenumber ); $query = qq/ DELETE FROM reserves - WHERE biblionumber = ? - AND borrowernumber = ? + WHERE reservenumber = ? /; $sth = $dbh->prepare($query); - $sth->execute( $biblio, $borrower ); + $sth->execute( $reservenumber ); } elsif ($rank =~ /^\d+/ and $rank > 0) { my $query = qq/ UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL - WHERE biblionumber = ? - AND borrowernumber = ? + WHERE reservenumber = ? /; my $sth = $dbh->prepare($query); - $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower); + $sth->execute( $rank, $branch, $itemnumber, $reservenumber ); $sth->finish; - _FixPriority( $biblio, $borrower, $rank); + _FixPriority( $biblio, $borrower, $rank, '', $reservenumber); } } -=head2 ModReserveFill +=item ModReserveFill &ModReserveFill($reserve); @@ -1050,16 +1256,17 @@ sub ModReserveFill { my $biblionumber = $res->{'biblionumber'}; my $borrowernumber = $res->{'borrowernumber'}; my $resdate = $res->{'reservedate'}; + my $resnumber = $res->{'reservenumber'}; # get the priority on this record.... my $priority; my $query = "SELECT priority FROM reserves - WHERE biblionumber = ? - AND borrowernumber = ? + WHERE borrowernumber = ? + AND reservenumber = ? AND reservedate = ?"; my $sth = $dbh->prepare($query); - $sth->execute( $biblionumber, $borrowernumber, $resdate ); + $sth->execute( $resnumber, $borrowernumber, $resdate ); ($priority) = $sth->fetchrow_array; $sth->finish; @@ -1067,35 +1274,35 @@ sub ModReserveFill { $query = "UPDATE reserves SET found = 'F', priority = 0 - WHERE biblionumber = ? + WHERE reservenumber = ? AND reservedate = ? AND borrowernumber = ? "; $sth = $dbh->prepare($query); - $sth->execute( $biblionumber, $resdate, $borrowernumber ); + $sth->execute( $resnumber, $resdate, $borrowernumber ); $sth->finish; # move to old_reserves $query = "INSERT INTO old_reserves SELECT * FROM reserves - WHERE biblionumber = ? + WHERE reservenumber = ? AND reservedate = ? AND borrowernumber = ? "; $sth = $dbh->prepare($query); - $sth->execute( $biblionumber, $resdate, $borrowernumber ); + $sth->execute( $resnumber, $resdate, $borrowernumber ); $query = "DELETE FROM reserves - WHERE biblionumber = ? + WHERE reservenumber = ? AND reservedate = ? AND borrowernumber = ? "; $sth = $dbh->prepare($query); - $sth->execute( $biblionumber, $resdate, $borrowernumber ); + $sth->execute( $resnumber, $resdate, $borrowernumber ); # now fix the priority on the others (if the priority wasn't # already sorted!).... unless ( $priority == 0 ) { - _FixPriority( $biblionumber, $borrowernumber ); + _FixPriority( $priority, $biblionumber,undef,undef, $res->{reservenumber} ); } } @@ -1112,7 +1319,6 @@ $newstatus is the new status. =cut sub ModReserveStatus { - #first : check if we have a reservation for this item . my ($itemnumber, $newstatus) = @_; my $dbh = C4::Context->dbh; @@ -1146,9 +1352,8 @@ take care of the waiting status =cut sub ModReserveAffect { - my ( $itemnumber, $borrowernumber,$transferToDo ) = @_; + my ( $itemnumber, $borrowernumber,$transferToDo, $reservenumber ) = @_; my $dbh = C4::Context->dbh; - # we want to attach $itemnumber to $borrowernumber, find the biblionumber # attached to $itemnumber my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?"); @@ -1166,27 +1371,52 @@ sub ModReserveAffect { $query = " UPDATE reserves SET priority = 0, - itemnumber = ?, - found = 'T' - WHERE borrowernumber = ? - AND biblionumber = ? + itemnumber = ? + WHERE reservenumber = ? "; } else { # affect the reserve to Waiting as well. - $query = " - UPDATE reserves - SET priority = 0, - found = 'W', - waitingdate=now(), - itemnumber = ? - WHERE borrowernumber = ? - AND biblionumber = ? - "; + my $holdperiod = C4::Context->preference('ReservesMaxPickUpDelay'); + if ((!defined($holdperiod)) || ($holdperiod eq '') || ($holdperiod == 0)) { + $query = " + UPDATE reserves + SET priority = 0, + found = 'W', + waitingdate=now(), + itemnumber = ? + WHERE reservenumber = ? + "; + } + else { + my ($holdexpyear,$holdexpmonth,$holdexpday) = Today(); + my $holdstartdate = C4::Dates->new(sprintf "%02d/%02d/%04d",$holdexpmonth,$holdexpday,$holdexpyear, 'us'); + + # Grab branch for calendar purposes + $sth = $dbh->prepare("SELECT branchcode FROM reserves WHERE borrowernumber=? AND biblionumber=?"); + $sth->execute($borrowernumber,$biblionumber); + my ($branch) = $sth->fetchrow; + + # Check to see if hold expiration date falls on a closed library day. + # Note the useDaysMode syspref will need to be set to Calendar for + # the code to advance to the next non-closed day. + my $calendar = C4::Calendar->new( branchcode => $branch); + my $holdexpdate = $calendar->addDate($holdstartdate, $holdperiod); + my $sqlexpdate = $holdexpdate->output('iso'); + $query = " + UPDATE reserves + SET priority = 0, + found = 'W', + waitingdate=now(), + itemnumber = ?, + expirationdate='$sqlexpdate' + WHERE reservenumber = ? + "; + } } $sth = $dbh->prepare($query); - $sth->execute( $itemnumber, $borrowernumber,$biblionumber); - _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf ); + $sth->execute( $itemnumber, $reservenumber ); + _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber, $reservenumber ) if ( !$transferToDo && !$already_on_shelf ); if ( C4::Context->preference("ReturnToShelvingCart") ) { CartToShelf( $itemnumber ); @@ -1226,23 +1456,21 @@ Reduce the values of queuded list =cut sub ModReserveMinusPriority { - my ( $itemnumber, $borrowernumber, $biblionumber ) = @_; - + my ( $itemnumber, $borrowernumber, $biblionumber, $reservenumber ) = @_; #first step update the value of the first person on reserv my $dbh = C4::Context->dbh; my $query = " UPDATE reserves SET priority = 0 , itemnumber = ? - WHERE borrowernumber=? - AND biblionumber=? + WHERE reservenumber=? "; my $sth_upd = $dbh->prepare($query); - $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber ); + $sth_upd->execute( $itemnumber, $reservenumber ); # second step update all others reservs - _FixPriority($biblionumber, $borrowernumber, '0'); + _FixPriority($biblionumber, $borrowernumber, '0', '', $reservenumber); } -=head2 GetReserveInfo +=item GetReserveInfo &GetReserveInfo($borrowernumber,$biblionumber); @@ -1327,7 +1555,7 @@ and canreservefromotherbranches. sub IsAvailableForItemLevelRequest { my $itemnumber = shift; - my $item = GetItem($itemnumber); + my $item = C4::Items::GetItem($itemnumber); # must check the notforloan setting of the itemtype # FIXME - a lot of places in the code do this @@ -1362,10 +1590,10 @@ sub IsAvailableForItemLevelRequest { $notforloan_per_itemtype; - if (C4::Context->preference('AllowOnShelfHolds')) { + if (CanHoldOnShelf($itemnumber)) { return $available_per_item; } else { - return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W")); + return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W")); } } @@ -1393,15 +1621,15 @@ sub AlterPriority { my $priority = $reserve->{'priority'}; $priority = $where eq 'up' ? $priority - 1 : $priority + 1; - _FixPriority( $biblionumber, $borrowernumber, $priority ) + _FixPriority( $biblionumber, $borrowernumber, $priority ,undef,$reserve->{reservenumber}) } elsif ( $where eq 'top' ) { - _FixPriority( $biblionumber, $borrowernumber, '1' ) + _FixPriority( $biblionumber, $borrowernumber, '1',undef,$reserve->{reservenumber} ) } elsif ( $where eq 'bottom' ) { - _FixPriority( $biblionumber, $borrowernumber, '999999' ) + _FixPriority( $biblionumber, $borrowernumber, '999999',undef,$reserve->{reservenumber} ) } } @@ -1424,18 +1652,49 @@ sub ToggleLowestPriority { WHERE biblionumber = ? AND borrowernumber = ?" ); - $sth->execute( - $biblionumber, - $borrowernumber, - ); + $sth->execute( $biblionumber, $borrowernumber, ); $sth->finish; _FixPriority( $biblionumber, $borrowernumber, '999999' ); } +=head2 CanHoldOnShelf + +my $canhold = &CanHoldOnShelf($itemnumber); + + Check if a book can be hold on shelf. + +=cut + +sub CanHoldOnShelf { + my ($itemnumber) = @_; + + my $item = C4::Items::GetItem($itemnumber); + my $itemtype = C4::Context->preference('item-level_itypes'); + $itemtype = $itemtype ? $item->{itype} : $item->{itemtype} ; + my $branch = $item->{C4::Context->preference('HomeOrHoldingBranch')}; + + my $issuingrule = GetIssuingRule('*', $itemtype, $branch); + return $issuingrule->{allowonshelfholds}; + +} + +sub CanHoldMultipleItems { + my ( $itemtype ) = @_; + + my @multi_itemtypes = split( / /, C4::Context->preference('AllowMultipleHoldsPerBib') ); + for my $mtype ( @multi_itemtypes ) { + if ( $itemtype eq $mtype ) { + return 1; + } + } + + return 0; +} + =head2 _FixPriority - &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank); +&_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank,$reservenumber); Only used internally (so don't export it) Changed how this functions works # @@ -1447,7 +1706,7 @@ in new priority rank =cut sub _FixPriority { - my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_; + my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank, $reservenumber ) = @_; my $dbh = C4::Context->dbh; if ( $rank eq "del" ) { CancelReserve( $biblio, undef, $borrowernumber ); @@ -1458,12 +1717,11 @@ sub _FixPriority { my $query = qq/ UPDATE reserves SET priority = 0 - WHERE biblionumber = ? - AND borrowernumber = ? - AND found IN ('W', 'T') + WHERE reservenumber = ? + AND found IN ('W',"T") /; my $sth = $dbh->prepare($query); - $sth->execute( $biblio, $borrowernumber ); + $sth->execute( $reservenumber ); } my @priority; my @reservedates; @@ -1474,7 +1732,7 @@ sub _FixPriority { # This is wrong a waiting reserve has W set # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs my $query = qq/ - SELECT borrowernumber, reservedate, constrainttype + SELECT borrowernumber, reservedate, constrainttype, reservenumber FROM reserves WHERE biblionumber = ? AND ((found <> 'W' AND found <> 'T') or found is NULL) @@ -1509,16 +1767,15 @@ sub _FixPriority { $query = " UPDATE reserves SET priority = ? - WHERE biblionumber = ? - AND borrowernumber = ? + WHERE reservenumber = ? AND reservedate = ? AND found IS NULL "; $sth = $dbh->prepare($query); for ( my $j = 0 ; $j < @priority ; $j++ ) { $sth->execute( - $j + 1, $biblio, - $priority[$j]->{'borrowernumber'}, + $j + 1, + $priority[$j]->{'reservenumber'}, $priority[$j]->{'reservedate'} ); $sth->finish; @@ -1558,15 +1815,16 @@ sub _Findgroupreserve { # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var. # check for exact targetted match my $item_level_target_query = qq/ - SELECT reserves.biblionumber AS biblionumber, - reserves.borrowernumber AS borrowernumber, - reserves.reservedate AS reservedate, - reserves.branchcode AS branchcode, - reserves.cancellationdate AS cancellationdate, - reserves.found AS found, - reserves.reservenotes AS reservenotes, - reserves.priority AS priority, - reserves.timestamp AS timestamp, + SELECT reserves.reservenumber AS reservenumber, + reserves.biblionumber AS biblionumber, + reserves.borrowernumber AS borrowernumber, + reserves.reservedate AS reservedate, + reserves.branchcode AS branchcode, + reserves.cancellationdate AS cancellationdate, + reserves.found AS found, + reserves.reservenotes AS reservenotes, + reserves.priority AS priority, + reserves.timestamp AS timestamp, biblioitems.biblioitemnumber AS biblioitemnumber, reserves.itemnumber AS itemnumber FROM reserves @@ -1588,15 +1846,16 @@ sub _Findgroupreserve { # check for title-level targetted match my $title_level_target_query = qq/ - SELECT reserves.biblionumber AS biblionumber, - reserves.borrowernumber AS borrowernumber, - reserves.reservedate AS reservedate, - reserves.branchcode AS branchcode, - reserves.cancellationdate AS cancellationdate, - reserves.found AS found, - reserves.reservenotes AS reservenotes, - reserves.priority AS priority, - reserves.timestamp AS timestamp, + SELECT reserves.reservenumber AS reservenumber, + reserves.biblionumber AS biblionumber, + reserves.borrowernumber AS borrowernumber, + reserves.reservedate AS reservedate, + reserves.branchcode AS branchcode, + reserves.cancellationdate AS cancellationdate, + reserves.found AS found, + reserves.reservenotes AS reservenotes, + reserves.priority AS priority, + reserves.timestamp AS timestamp, biblioitems.biblioitemnumber AS biblioitemnumber, reserves.itemnumber AS itemnumber FROM reserves @@ -1617,16 +1876,16 @@ sub _Findgroupreserve { return @results if @results; my $query = qq/ - SELECT reserves.biblionumber AS biblionumber, - reserves.borrowernumber AS borrowernumber, - reserves.reservedate AS reservedate, - reserves.waitingdate AS waitingdate, - reserves.branchcode AS branchcode, - reserves.cancellationdate AS cancellationdate, - reserves.found AS found, - reserves.reservenotes AS reservenotes, - reserves.priority AS priority, - reserves.timestamp AS timestamp, + SELECT reserves.reservenumber AS reservenumber, + reserves.biblionumber AS biblionumber, + reserves.borrowernumber AS borrowernumber, + reserves.reservedate AS reservedate, + reserves.branchcode AS branchcode, + reserves.cancellationdate AS cancellationdate, + reserves.found AS found, + reserves.reservenotes AS reservenotes, + reserves.priority AS priority, + reserves.timestamp AS timestamp, reserveconstraints.biblioitemnumber AS biblioitemnumber, reserves.itemnumber AS itemnumber FROM reserves @@ -1658,7 +1917,7 @@ ModReserveAffect, _not_ ModReserveFill) =cut sub _koha_notify_reserve { - my ($itemnumber, $borrowernumber, $biblionumber) = @_; + my ($itemnumber, $borrowernumber, $biblionumber, $reservenumber) = @_; my $dbh = C4::Context->dbh; my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber); @@ -1678,21 +1937,19 @@ sub _koha_notify_reserve { my $messagingprefs; if ( $to_address || $borrower->{'smsalertnumber'} ) { $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold_Filled' } ); - return if ( !defined( $messagingprefs->{'letter_code'} ) ); $letter_code = $messagingprefs->{'letter_code'}; } else { - $letter_code = 'HOLD_PRINT'; + $letter_code = 'HOLD'; $print_mode = 1; } my $sth = $dbh->prepare(" SELECT * FROM reserves - WHERE borrowernumber = ? - AND biblionumber = ? + WHERE reservenumber = ? "); - $sth->execute( $borrowernumber, $biblionumber ); + $sth->execute( $reservenumber ); my $reserve = $sth->fetchrow_hashref; my $branch_details = GetBranchDetail( $reserve->{'branchcode'} ); @@ -1705,6 +1962,7 @@ sub _koha_notify_reserve { C4::Letters::parseletter( $letter, 'borrowers', $borrowernumber ); C4::Letters::parseletter( $letter, 'biblio', $biblionumber ); C4::Letters::parseletter( $letter, 'reserves', $borrowernumber, $biblionumber ); + C4::Letters::parseletter( $letter, 'items', $itemnumber ); if ( $reserve->{'itemnumber'} ) { C4::Letters::parseletter( $letter, 'items', $reserve->{'itemnumber'} ); --- a/admin/smart-rules-service.pl +++ a/admin/smart-rules-service.pl @@ -0,0 +1,82 @@ +#!/usr/bin/perl + +# 2009 BibLibre + +# 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., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA +# + +use CGI; +use C4::Auth; +use C4::IssuingRules qw(ModIssuingRule GetIssuingRule); + +my $cgi = CGI->new; + +# get the status of the user, this will check his credentials and rights +my ($status, $cookie, $sessionId) = C4::Auth::check_api_auth($cgi, undef); + +my $result; + +if ($status eq 'ok') { # if authentication is ok + + # Get the POST data + my $branchcode = $cgi->param('branchcode'); + my $categorycode = $cgi->param('categorycode'); + my $itemtype = $cgi->param('itemtype'); + my $varname = $cgi->param('varname'); + my $inputvalue = $cgi->param('inputvalue') eq '' ? undef : $cgi->param('inputvalue'); + + # Modify the existing rule + ModIssuingRule({ + branchcode => $branchcode, + categorycode => $categorycode, + itemtype => $itemtype, + $varname => $inputvalue, + }); + + # Compute inheritance, and return the new value; + my $rule = GetIssuingRule($categorycode, $itemtype, $branchcode); + + $result = $rule->{$varname}; + $result = C4::Dates->new($rule->{$varname},'iso')->output() if ($varname eq 'hardduedate'); + if ($varname eq 'fine') { + my $num_formatter; + my $cur_format = C4::Context->preference("CurrencyFormat"); + use Number::Format; + if ( $cur_format eq 'FR' ) { + $num_formatter = Number::Format->new( + 'decimal_fill' => '2', + 'decimal_point' => ',', + 'int_curr_symbol' => '', + 'mon_thousands_sep' => ' ', + 'thousands_sep' => ' ', + 'mon_decimal_point' => ',' + ); + } else { # US by default.. + $num_formatter = Number::Format->new( + 'int_curr_symbol' => '', + 'mon_thousands_sep' => ',', + 'mon_decimal_point' => '.' + ); + } + $result = $num_formatter->format_price($result); + } +} else { + $result = 'Fail'; +} + +print CGI::header('-type'=>'text/plain', '-charset'=>'utf-8'); +print $result; + --- a/admin/smart-rules.pl +++ a/admin/smart-rules.pl @@ -27,505 +27,132 @@ use C4::Koha; use C4::Debug; use C4::Branch; # GetBranches use C4::Dates qw/format_date format_date_in_iso/; +use C4::IssuingRules; +use C4::Circulation; my $input = new CGI; my $dbh = C4::Context->dbh; -# my $flagsrequired; -# $flagsrequired->{circulation}=1; -my ($template, $loggedinuser, $cookie) - = get_template_and_user({template_name => "admin/smart-rules.tmpl", - query => $input, - type => "intranet", - authnotrequired => 0, - flagsrequired => {parameters => 1}, - debug => 1, - }); - -my $type=$input->param('type'); -my $branch = $input->param('branch') || ( C4::Branch::onlymine() ? ( C4::Branch::mybranch() || '*' ) : '*' ); -my $op = $input->param('op'); - -if ($op eq 'delete') { - my $itemtype = $input->param('itemtype'); - my $categorycode = $input->param('categorycode'); - $debug and warn "deleting $1 $2 $branch"; - - my $sth_Idelete = $dbh->prepare("delete from issuingrules where branchcode=? and categorycode=? and itemtype=?"); - $sth_Idelete->execute($branch, $categorycode, $itemtype); -} -elsif ($op eq 'delete-branch-cat') { - my $categorycode = $input->param('categorycode'); - if ($branch eq "*") { - if ($categorycode eq "*") { - my $sth_delete = $dbh->prepare("DELETE FROM default_circ_rules"); - $sth_delete->execute(); - } else { - my $sth_delete = $dbh->prepare("DELETE FROM default_borrower_circ_rules - WHERE categorycode = ?"); - $sth_delete->execute($categorycode); - } - } elsif ($categorycode eq "*") { - my $sth_delete = $dbh->prepare("DELETE FROM default_branch_circ_rules - WHERE branchcode = ?"); - $sth_delete->execute($branch); - } else { - my $sth_delete = $dbh->prepare("DELETE FROM branch_borrower_circ_rules - WHERE branchcode = ? - AND categorycode = ?"); - $sth_delete->execute($branch, $categorycode); - } -} -elsif ($op eq 'delete-branch-item') { - my $itemtype = $input->param('itemtype'); - if ($branch eq "*") { - if ($itemtype eq "*") { - my $sth_delete = $dbh->prepare("DELETE FROM default_circ_rules"); - $sth_delete->execute(); - } else { - my $sth_delete = $dbh->prepare("DELETE FROM default_branch_item_rules - WHERE itemtype = ?"); - $sth_delete->execute($itemtype); - } - } elsif ($itemtype eq "*") { - my $sth_delete = $dbh->prepare("DELETE FROM default_branch_circ_rules - WHERE branchcode = ?"); - $sth_delete->execute($branch); - } else { - my $sth_delete = $dbh->prepare("DELETE FROM branch_item_rules - WHERE branchcode = ? - AND itemtype = ?"); - $sth_delete->execute($branch, $itemtype); - } -} -# save the values entered -elsif ($op eq 'add') { - my $sth_search = $dbh->prepare("SELECT COUNT(*) AS total FROM issuingrules WHERE branchcode=? AND categorycode=? AND itemtype=?"); - my $sth_insert = $dbh->prepare("INSERT INTO issuingrules (branchcode, categorycode, itemtype, maxissueqty, renewalsallowed, reservesallowed, issuelength, hardduedate, hardduedatecompare, fine, finedays, firstremind, chargeperiod,rentaldiscount) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); - my $sth_update=$dbh->prepare("UPDATE issuingrules SET fine=?, finedays=?, firstremind=?, chargeperiod=?, maxissueqty=?, renewalsallowed=?, reservesallowed=?, issuelength=?, hardduedate=?, hardduedatecompare=?, rentaldiscount=? WHERE branchcode=? AND categorycode=? AND itemtype=?"); - - my $br = $branch; # branch - my $bor = $input->param('categorycode'); # borrower category - my $cat = $input->param('itemtype'); # item type - my $fine = $input->param('fine'); - my $finedays = $input->param('finedays'); - my $firstremind = $input->param('firstremind'); - my $chargeperiod = $input->param('chargeperiod'); - my $maxissueqty = $input->param('maxissueqty'); - my $renewalsallowed = $input->param('renewalsallowed'); - my $reservesallowed = $input->param('reservesallowed'); - $maxissueqty =~ s/\s//g; - $maxissueqty = undef if $maxissueqty !~ /^\d+/; - my $issuelength = $input->param('issuelength'); - my $hardduedate = $input->param('hardduedate'); - $hardduedate = format_date_in_iso($hardduedate); - my $hardduedatecompare = $input->param('hardduedatecompare'); - my $rentaldiscount = $input->param('rentaldiscount'); - $debug and warn "Adding $br, $bor, $cat, $fine, $maxissueqty"; - - $sth_search->execute($br,$bor,$cat); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($fine, $finedays,$firstremind, $chargeperiod, $maxissueqty, $renewalsallowed,$reservesallowed, $issuelength,$hardduedate,$hardduedatecompare,$rentaldiscount, $br,$bor,$cat); - } else { - $sth_insert->execute($br,$bor,$cat,$maxissueqty,$renewalsallowed,$reservesallowed,$issuelength,$hardduedate,$hardduedatecompare,$fine,$finedays,$firstremind,$chargeperiod,$rentaldiscount); - } -} -elsif ($op eq "set-branch-defaults") { - my $categorycode = $input->param('categorycode'); - my $maxissueqty = $input->param('maxissueqty'); - my $holdallowed = $input->param('holdallowed'); - $maxissueqty =~ s/\s//g; - $maxissueqty = undef if $maxissueqty !~ /^\d+/; - $holdallowed =~ s/\s//g; - $holdallowed = undef if $holdallowed !~ /^\d+/; - - if ($branch eq "*") { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM default_circ_rules"); - my $sth_insert = $dbh->prepare("INSERT INTO default_circ_rules - (maxissueqty, holdallowed) - VALUES (?, ?)"); - my $sth_update = $dbh->prepare("UPDATE default_circ_rules - SET maxissueqty = ?, holdallowed = ?"); - - $sth_search->execute(); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($maxissueqty, $holdallowed); - } else { - $sth_insert->execute($maxissueqty, $holdallowed); - } - } else { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM default_branch_circ_rules - WHERE branchcode = ?"); - my $sth_insert = $dbh->prepare("INSERT INTO default_branch_circ_rules - (branchcode, maxissueqty, holdallowed) - VALUES (?, ?, ?)"); - my $sth_update = $dbh->prepare("UPDATE default_branch_circ_rules - SET maxissueqty = ?, holdallowed = ? - WHERE branchcode = ?"); - $sth_search->execute($branch); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($maxissueqty, $holdallowed, $branch); - } else { - $sth_insert->execute($branch, $maxissueqty, $holdallowed); - } - } -} -elsif ($op eq "add-branch-cat") { - my $categorycode = $input->param('categorycode'); - my $maxissueqty = $input->param('maxissueqty'); - $maxissueqty =~ s/\s//g; - $maxissueqty = undef if $maxissueqty !~ /^\d+/; - - if ($branch eq "*") { - if ($categorycode eq "*") { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM default_circ_rules"); - my $sth_insert = $dbh->prepare("INSERT INTO default_circ_rules - (maxissueqty) - VALUES (?)"); - my $sth_update = $dbh->prepare("UPDATE default_circ_rules - SET maxissueqty = ?"); - - $sth_search->execute(); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($maxissueqty); - } else { - $sth_insert->execute($maxissueqty); - } - } else { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM default_borrower_circ_rules - WHERE categorycode = ?"); - my $sth_insert = $dbh->prepare("INSERT INTO default_borrower_circ_rules - (categorycode, maxissueqty) - VALUES (?, ?)"); - my $sth_update = $dbh->prepare("UPDATE default_borrower_circ_rules - SET maxissueqty = ? - WHERE categorycode = ?"); - $sth_search->execute($branch); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($maxissueqty, $categorycode); - } else { - $sth_insert->execute($categorycode, $maxissueqty); - } - } - } elsif ($categorycode eq "*") { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM default_branch_circ_rules - WHERE branchcode = ?"); - my $sth_insert = $dbh->prepare("INSERT INTO default_branch_circ_rules - (branchcode, maxissueqty) - VALUES (?, ?)"); - my $sth_update = $dbh->prepare("UPDATE default_branch_circ_rules - SET maxissueqty = ? - WHERE branchcode = ?"); - $sth_search->execute($branch); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($maxissueqty, $branch); - } else { - $sth_insert->execute($branch, $maxissueqty); - } - } else { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM branch_borrower_circ_rules - WHERE branchcode = ? - AND categorycode = ?"); - my $sth_insert = $dbh->prepare("INSERT INTO branch_borrower_circ_rules - (branchcode, categorycode, maxissueqty) - VALUES (?, ?, ?)"); - my $sth_update = $dbh->prepare("UPDATE branch_borrower_circ_rules - SET maxissueqty = ? - WHERE branchcode = ? - AND categorycode = ?"); - - $sth_search->execute($branch, $categorycode); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($maxissueqty, $branch, $categorycode); - } else { - $sth_insert->execute($branch, $categorycode, $maxissueqty); - } - } -} -elsif ($op eq "add-branch-item") { - my $itemtype = $input->param('itemtype'); - my $holdallowed = $input->param('holdallowed'); - $holdallowed =~ s/\s//g; - $holdallowed = undef if $holdallowed !~ /^\d+/; - - if ($branch eq "*") { - if ($itemtype eq "*") { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM default_circ_rules"); - my $sth_insert = $dbh->prepare("INSERT INTO default_circ_rules - (holdallowed) - VALUES (?)"); - my $sth_update = $dbh->prepare("UPDATE default_circ_rules - SET holdallowed = ?"); - - $sth_search->execute(); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($holdallowed); - } else { - $sth_insert->execute($holdallowed); - } - } else { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM default_branch_item_rules - WHERE itemtype = ?"); - my $sth_insert = $dbh->prepare("INSERT INTO default_branch_item_rules - (itemtype, holdallowed) - VALUES (?, ?)"); - my $sth_update = $dbh->prepare("UPDATE default_branch_item_rules - SET holdallowed = ? - WHERE itemtype = ?"); - $sth_search->execute($itemtype); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($holdallowed, $itemtype); - } else { - $sth_insert->execute($itemtype, $holdallowed); - } - } - } elsif ($itemtype eq "*") { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM default_branch_circ_rules - WHERE branchcode = ?"); - my $sth_insert = $dbh->prepare("INSERT INTO default_branch_circ_rules - (branchcode, holdallowed) - VALUES (?, ?)"); - my $sth_update = $dbh->prepare("UPDATE default_branch_circ_rules - SET holdallowed = ? - WHERE branchcode = ?"); - $sth_search->execute($branch); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($holdallowed, $branch); - } else { - $sth_insert->execute($branch, $holdallowed); - } - } else { - my $sth_search = $dbh->prepare("SELECT count(*) AS total - FROM branch_item_rules - WHERE branchcode = ? - AND itemtype = ?"); - my $sth_insert = $dbh->prepare("INSERT INTO branch_item_rules - (branchcode, itemtype, holdallowed) - VALUES (?, ?, ?)"); - my $sth_update = $dbh->prepare("UPDATE branch_item_rules - SET holdallowed = ? - WHERE branchcode = ? - AND itemtype = ?"); - - $sth_search->execute($branch, $itemtype); - my $res = $sth_search->fetchrow_hashref(); - if ($res->{total}) { - $sth_update->execute($holdallowed, $branch, $itemtype); - } else { - $sth_insert->execute($branch, $itemtype, $holdallowed); - } - } -} - +my ($template, $loggedinuser, $cookie) = get_template_and_user({ + template_name => "admin/smart-rules.tmpl", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => {parameters => 1}, + debug => 1, +}); + +my $type = $input->param('type'); +my $branchcode = $input->param('branchcode') || ( C4::Branch::onlymine() ? ( C4::Branch::mybranch() || '*' ) : '*' ); +my $op = $input->param('op'); +my $confirm = $input->param('confirm'); + +# This block builds the branch list my $branches = GetBranches(); my @branchloop; for my $thisbranch (sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} } keys %$branches) { push @branchloop, { value => $thisbranch, - selected => $thisbranch eq $branch, + selected => $thisbranch eq $branchcode, branchname => $branches->{$thisbranch}->{'branchname'}, }; } -my $sth=$dbh->prepare("SELECT description,categorycode FROM categories ORDER BY description"); -$sth->execute; -my @category_loop; -while (my $data=$sth->fetchrow_hashref){ - push @category_loop,$data; -} +# Get the patron category list +my @category_loop = C4::Category->all; -$sth->finish; -$sth=$dbh->prepare("SELECT description,itemtype FROM itemtypes ORDER BY description"); -$sth->execute; -# $i=0; -my @row_loop; -my @itemtypes; -while (my $row=$sth->fetchrow_hashref){ - push @itemtypes,$row; +# Get the item types list +my @itemtypes = C4::ItemType->all; + +if ( $op eq 'delete' ) { + DelIssuingRule({ + branchcode => $branchcode, + categorycode => $input->param('categorycode'), + itemtype => $input->param('itemtype'), + }); } +# save the values entered +elsif ( $op eq 'add' ) { -my $sth2 = $dbh->prepare(" - SELECT issuingrules.*, itemtypes.description AS humanitemtype, categories.description AS humancategorycode - FROM issuingrules - LEFT JOIN itemtypes - ON (itemtypes.itemtype = issuingrules.itemtype) - LEFT JOIN categories - ON (categories.categorycode = issuingrules.categorycode) - WHERE issuingrules.branchcode = ? -"); -$sth2->execute($branch); + # Converts '' to undef, so we can have NULL in database fields + my $issuingrule; + for ( $input->param ) { + my $v = $input->param($_); + $issuingrule->{$_} = length $v ? $v : undef; + } -while (my $row = $sth2->fetchrow_hashref) { - $row->{'current_branch'} ||= $row->{'branchcode'}; - $row->{'humanitemtype'} ||= $row->{'itemtype'}; - $row->{'default_humanitemtype'} = 1 if $row->{'humanitemtype'} eq '*'; - $row->{'humancategorycode'} ||= $row->{'categorycode'}; - $row->{'default_humancategorycode'} = 1 if $row->{'humancategorycode'} eq '*'; - $row->{'fine'} = sprintf('%.2f', $row->{'fine'}); - if ($row->{'hardduedate'} ne '0000-00-00') { - $row->{'hardduedate'} = format_date( $row->{'hardduedate'}); - $row->{'hardduedatebefore'} = 1 if ($row->{'hardduedatecompare'} == -1); - $row->{'hardduedateexact'} = 1 if ($row->{'hardduedatecompare'} == 0); - $row->{'hardduedateafter'} = 1 if ($row->{'hardduedatecompare'} == 1); + # We don't want op to be passed to the API + delete $issuingrule->{'op'}; + + # If the (branchcode,categorycode,itemtype) combination already exists... + my @issuingrules = GetIssuingRules({ + branchcode => $issuingrule->{'branchcode'}, + categorycode => $issuingrule->{'categorycode'}, + itemtype => $issuingrule->{'itemtype'}, + }); + + # ...we modify the existing rule... + if ( @issuingrules ) { + ModIssuingRule( $issuingrule ); +# } elsif (@issuingrules){ +# $template->param(confirm=>1); +# $template->param(%$issuingrule); +# foreach (@category_loop) { +# $_->{selected}="selected" if ($_->{categorycode} eq $issuingrule->{categorycode}); +# } +# foreach (@itemtypes) { +# $_->{selected}="selected" if ($_->{itemtype} eq $issuingrule->{itemtype}); +# } + # ...else we add a new rule. } else { - $row->{'hardduedate'} = 0; + AddIssuingRule( $issuingrule ); } - push @row_loop, $row; -} -$sth->finish; - -my @sorted_row_loop = sort by_category_and_itemtype @row_loop; - -my $sth_branch_cat; -if ($branch eq "*") { - $sth_branch_cat = $dbh->prepare(" - SELECT default_borrower_circ_rules.*, categories.description AS humancategorycode - FROM default_borrower_circ_rules - JOIN categories USING (categorycode) - - "); - $sth_branch_cat->execute(); -} else { - $sth_branch_cat = $dbh->prepare(" - SELECT branch_borrower_circ_rules.*, categories.description AS humancategorycode - FROM branch_borrower_circ_rules - JOIN categories USING (categorycode) - WHERE branch_borrower_circ_rules.branchcode = ? - "); - $sth_branch_cat->execute($branch); -} - -my @branch_cat_rules = (); -while (my $row = $sth_branch_cat->fetchrow_hashref) { - push @branch_cat_rules, $row; -} -my @sorted_branch_cat_rules = sort { $a->{'humancategorycode'} cmp $b->{'humancategorycode'} } @branch_cat_rules; - -# note undef maxissueqty so that template can deal with them -foreach my $entry (@sorted_branch_cat_rules, @sorted_row_loop) { - $entry->{unlimited_maxissueqty} = 1 unless defined($entry->{maxissueqty}); -} - -@sorted_row_loop = sort by_category_and_itemtype @row_loop; - -my $sth_branch_item; -if ($branch eq "*") { - $sth_branch_item = $dbh->prepare(" - SELECT default_branch_item_rules.*, itemtypes.description AS humanitemtype - FROM default_branch_item_rules - JOIN itemtypes USING (itemtype) - "); - $sth_branch_item->execute(); -} else { - $sth_branch_item = $dbh->prepare(" - SELECT branch_item_rules.*, itemtypes.description AS humanitemtype - FROM branch_item_rules - JOIN itemtypes USING (itemtype) - WHERE branch_item_rules.branchcode = ? - "); - $sth_branch_item->execute($branch); -} - -my @branch_item_rules = (); -while (my $row = $sth_branch_item->fetchrow_hashref) { - push @branch_item_rules, $row; -} -my @sorted_branch_item_rules = sort { $a->{'humanitemtype'} cmp $b->{'humanitemtype'} } @branch_item_rules; - -# note undef holdallowed so that template can deal with them -foreach my $entry (@sorted_branch_item_rules) { - $entry->{holdallowed_any} = 1 if($entry->{holdallowed} == 2); - $entry->{holdallowed_same} = 1 if($entry->{holdallowed} == 1); } -$template->param(show_branch_cat_rule_form => 1); -$template->param(branch_item_rule_loop => \@sorted_branch_item_rules); -$template->param(branch_cat_rule_loop => \@sorted_branch_cat_rules); - -my $sth_defaults; -if ($branch eq "*") { - $sth_defaults = $dbh->prepare(" - SELECT * - FROM default_circ_rules - "); - $sth_defaults->execute(); -} else { - $sth_defaults = $dbh->prepare(" - SELECT * - FROM default_branch_circ_rules - WHERE branchcode = ? - "); - $sth_defaults->execute($branch); +# Get the issuing rules list... +my @issuingrules = GetIssuingRulesByBranchCode($branchcode); + +# ...and refine its data, row by row. +for my $rule ( @issuingrules ) { + $rule->{'humanitemtype'} ||= $rule->{'itemtype'}; + $rule->{'default_humanitemtype'} = $rule->{'humanitemtype'} eq '*'; + $rule->{'humancategorycode'} ||= $rule->{'categorycode'}; + $rule->{'default_humancategorycode'} = $rule->{'humancategorycode'} eq '*'; + if ($rule->{'hardduedate'} ne '0000-00-00') { + $rule->{'hardduedate'} = format_date( $rule->{'hardduedate'}); + $rule->{'hardduedatebefore'} = 1 if ($rule->{'hardduedatecompare'} == -1); + $rule->{'hardduedateexact'} = 1 if ($rule->{'hardduedatecompare'} == 0); + $rule->{'hardduedateafter'} = 1 if ($rule->{'hardduedatecompare'} == 1); + } else { + $rule->{'hardduedate'} = 0; + } + $rule->{'fine'} = sprintf('%.2f', $rule->{'fine'}); + + # This block is to show herited values in grey. + # We juste compare keys from our raw rule, with keys from the computed rule. + my $computedrule = GetIssuingRule($rule->{'categorycode'}, $rule->{'itemtype'}, $rule->{'branchcode'}); + for ( keys %$rule ) { + if ( not defined $rule->{$_} ) { + $rule->{$_} = $computedrule->{$_}; + $rule->{"herited_$_"} = 1; + } + } } -my $defaults = $sth_defaults->fetchrow_hashref; -if ($defaults) { - $template->param(default_holdallowed_none => 1) if($defaults->{holdallowed} == 0); - $template->param(default_holdallowed_same => 1) if($defaults->{holdallowed} == 1); - $template->param(default_holdallowed_any => 1) if($defaults->{holdallowed} == 2); - $template->param(default_maxissueqty => $defaults->{maxissueqty}); -} +$template->param(default_rules => ($branchcode eq '*' ? 1 : 0)); -$template->param(default_rules => ($defaults ? 1 : 0)); +$template->param( + categoryloop => \@category_loop, + itemtypeloop => \@itemtypes, + rules => \@issuingrules, + branchloop => \@branchloop, + humanbranch => ($branchcode ne '*' ? $branches->{$branchcode}->{branchname} : ''), + branchcode => $branchcode, + definedbranch => scalar(@issuingrules) > 0, +); -$template->param(categoryloop => \@category_loop, - itemtypeloop => \@itemtypes, - rules => \@sorted_row_loop, - branchloop => \@branchloop, - humanbranch => ($branch ne '*' ? $branches->{$branch}->{branchname} : ''), - current_branch => $branch, - definedbranch => scalar(@sorted_row_loop)>0 - ); output_html_with_http_headers $input, $cookie, $template->output; exit 0; -# sort by patron category, then item type, putting -# default entries at the bottom -sub by_category_and_itemtype { - unless (by_category($a, $b)) { - return by_itemtype($a, $b); - } -} - -sub by_category { - my ($a, $b) = @_; - if ($a->{'default_humancategorycode'}) { - return ($b->{'default_humancategorycode'} ? 0 : 1); - } elsif ($b->{'default_humancategorycode'}) { - return -1; - } else { - return $a->{'humancategorycode'} cmp $b->{'humancategorycode'}; - } -} - -sub by_itemtype { - my ($a, $b) = @_; - if ($a->{'default_humanitemtype'}) { - return ($b->{'default_humanitemtype'} ? 0 : 1); - } elsif ($b->{'default_humanitemtype'}) { - return -1; - } else { - return $a->{'humanitemtype'} cmp $b->{'humanitemtype'}; - } -} --- a/circ/branchtransfers.pl +++ a/circ/branchtransfers.pl @@ -86,7 +86,9 @@ if ( $request eq "KillWaiting" ) { } elsif ( $request eq "SetWaiting" ) { my $item = $query->param('itemnumber'); - ModReserveAffect( $item, $borrowernumber ); + my $itemhash=C4::Items::GetItem($item); + my ( $reservetype, $reserve ) = C4::Reserves::CheckReserves( undef, $itemhash->{barcode} ); + ModReserveAffect( $item, $borrowernumber,$tobranchcd ne C4::Context->userenv->{'branch'}, $reserve->{reservenumber}); $ignoreRs = 1; $setwaiting = 1; $reqmessage = 1; --- a/circ/circulation.pl +++ a/circ/circulation.pl @@ -30,12 +30,16 @@ use C4::Dates qw/format_date/; use C4::Branch; # GetBranches use C4::Koha; # GetPrinter use C4::Circulation; +use C4::Overdues qw/CheckBorrowerDebarred/; use C4::Members; use C4::Biblio; use C4::Reserves; use C4::Context; +use C4::Debug; use CGI::Session; - +use C4::Items; +use JSON; +use YAML; use Date::Calc qw( Today Add_Delta_YM @@ -49,9 +53,10 @@ use List::MoreUtils qw/uniq/; # PARAMETERS READING # my $query = new CGI; - -my $sessionID = $query->cookie("CGISESSID") ; -my $session = get_session($sessionID); +my $dbh = C4::Context->dbh; +my $remotehost = $query->remote_host(); +my $sessionID = $query->cookie("CGISESSID"); +my $session = get_session($sessionID); # branch and printer are now defined by the userenv # but first we have to check if someone has tried to change them @@ -89,9 +94,14 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user ( my $branches = GetBranches(); -my @failedrenews = $query->param('failedrenew'); # expected to be itemnumbers +my @failedrenews = $query->param('failedrenew'); # expected to be itemnumbers +my @failedreturns = $query->param('failedreturn'); # expected to be barcodes +my @renewerrors = $query->param('renewerror'); # expected to be json +my @returnerrors = $query->param('returnerror'); # expected to be json my %renew_failed; -for (@failedrenews) { $renew_failed{$_} = 1; } +my %return_failed; +for (@failedrenews) { $renew_failed{$_} = decode_json(shift @renewerrors); } +for (@failedreturns) { $return_failed{GetItemnumberFromBarcode($_)} = decode_json(shift @returnerrors); } my $findborrower = $query->param('findborrower'); $findborrower =~ s|,| |g; @@ -126,14 +136,13 @@ my $organisation = $query->param('organisations'); my $print = $query->param('print'); my $newexpiry = $query->param('dateexpiry'); my $debt_confirmed = $query->param('debt_confirmed') || 0; # Don't show the debt error dialog twice - +$debug && warn $newexpiry; # Check if stickyduedate is turned off if ( $barcode ) { # was stickyduedate loaded from session? if ( $stickyduedate && ! $query->param("stickyduedate") ) { $session->clear( 'stickyduedate' ); $stickyduedate = $query->param('stickyduedate'); - $duedatespec = $query->param('duedatespec'); } } @@ -158,7 +167,7 @@ if($duedatespec_allow){ } } -my $todaysdate = C4::Dates->new->output('iso'); +my $todaysdate = C4::Dates->new->output( 'iso' ); # check and see if we should print if ( $barcode eq '' && $print eq 'maybe' ) { @@ -238,7 +247,8 @@ if ($borrowernumber) { $template->param( flagged => "1", noissues => "1", - expired => "1", + expired => format_date($borrower->{dateexpiry}), + warning => 1, renewaldate => format_date("$renew_year-$renew_month-$renew_day") ); } @@ -249,9 +259,9 @@ if ($borrowernumber) { { # borrower card soon to expire warn librarian $template->param("warndeparture" => format_date($borrower->{dateexpiry}), - flagged => "1",); + flagged => "1", "warning" => 1); if (C4::Context->preference('ReturnBeforeExpiry')){ - $template->param("returnbeforeexpiry" => 1); + $template->param("returnbeforeexpiry" => 1, "warning => 1"); } } $template->param( @@ -259,12 +269,22 @@ if ($borrowernumber) { issuecount => $issue, finetotal => $fines ); + + my $debar = CheckBorrowerDebarred($borrowernumber); + if($debar){ + $template->param(userdebarred => 1); + $template->param(debarredcomment => $borrower->{debarredcomment}); + if( $debar ne "9999-12-31"){ + $template->param(userdebarreddate => C4::Dates::format_date($debar)); + } + } } # # STEP 3 : ISSUING # # +my $confirm_required = 0; if ($barcode) { # always check for blockers on issuing my ( $error, $question ) = @@ -306,8 +326,33 @@ if ($barcode) { # FIXME If the issue is confirmed, we launch another time GetMemberIssuesAndFines, now display the issue count after issue my ( $od, $issue, $fines ) = GetMemberIssuesAndFines( $borrowernumber ); $template->param( issuecount => $issue ); + + # Is there a circulation note? + my $itemnumber = GetItemnumberFromBarcode($barcode); + my $biblionumber = GetBiblionumberFromItemnumber($itemnumber); + my $record = GetMarcBiblio($biblionumber); + my $frameworkcode = GetFrameworkCode($biblionumber); + my $circnotefield = GetRecordValue('circnote', $record, $frameworkcode); + if (defined @$circnotefield[0]) { + $template->param(circnote => @$circnotefield[0]->{'subfield'}); + } } + +# Setting the right status if an hold has been confirmed +#This should never be used in fact +my $resbarcode = $query->param("resbarcode"); +if ($resbarcode) { + if ( my ( $reservetype, $reserve ) = C4::Reserves::CheckReserves( undef, $resbarcode ) ) { + if ( $reservetype eq "Waiting" || $reservetype eq "Reserved" ) { + my $transfer = C4::Context->userenv->{branch} ne $reserve->{branchcode}; + ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber}, $transfer, $reserve->{"reservenumber"} ); + } + } +} + + + # reload the borrower info for the sake of reseting the flags..... if ($borrowernumber) { $borrower = GetMemberDetails( $borrowernumber, 0 ); @@ -345,11 +390,18 @@ if ($borrowernumber) { $getreserv{itemcallnumber} = $getiteminfo->{'itemcallnumber'}; $getreserv{biblionumber} = $getiteminfo->{'biblionumber'}; $getreserv{waitingat} = GetBranchName( $num_res->{'branchcode'} ); + my $materials = $getiteminfo->{'materials'}; + $template->param(materials => $materials); # check if we have a waiting status for reservations if ( $num_res->{'found'} eq 'W' ) { $getreserv{color} = 'reserved'; $getreserv{waiting} = 1; # genarate information displaying only waiting reserves + my @maxpickupdate = $num_res->{'waitingdate'} ? GetMaxPickupDate( $num_res->{'waitingdate'}, $borrowernumber, $num_res ) : ''; + $getreserv{'maxpickupdate'} = sprintf( "%d-%02d-%02d", @maxpickupdate ); + $getWaitingReserveInfo{'formattedwaitingdate'} = format_date( $getreserv{'maxpickupdate'} ); + $getreserv{'formattedwaitingdate'} = format_date( $getreserv{'maxpickupdate'} ); + $getWaitingReserveInfo{title} = $getiteminfo->{'title'}; $getWaitingReserveInfo{biblionumber} = $getiteminfo->{'biblionumber'}; $getWaitingReserveInfo{itemtype} = $itemtypeinfo->{'description'}; @@ -408,51 +460,75 @@ my @relissues; my @relprevissues; my $displayrelissues; +my @borrowers_with_issues; +my @relissues; +my @relprevissues; +my $displayrelissues; +## ADDED BY JF: new itemtype issuingrules counter stuff +my $issued_itemtypes_count; +my @issued_itemtypes_count_loop; + my $totalprice = 0; sub build_issue_data { my $issueslist = shift; my $relatives = shift; - # split in 2 arrays for today & previous - foreach my $it ( @$issueslist ) { - my $itemtypeinfo = getitemtypeinfo( (C4::Context->preference('item-level_itypes')) ? $it->{'itype'} : $it->{'itemtype'} ); + # split in 2 arrays for today & previous + foreach my $it (@$issueslist) { + my $itemtypeinfo = getitemtypeinfo( ( C4::Context->preference('item-level_itypes') ) ? $it->{'itype'} : $it->{'itemtype'} ); # Getting borrower details my $memberdetails = GetMemberDetails($it->{'borrowernumber'}); $it->{'borrowername'} = $memberdetails->{'firstname'} . " " . $memberdetails->{'surname'}; $it->{'cardnumber'} = $memberdetails->{'cardnumber'}; # set itemtype per item-level_itype syspref - FIXME this is an ugly hack - $it->{'itemtype'} = ( C4::Context->preference( 'item-level_itypes' ) ) ? $it->{'itype'} : $it->{'itemtype'}; - - ($it->{'charge'}, $it->{'itemtype_charge'}) = GetIssuingCharges( - $it->{'itemnumber'}, $borrower->{'borrowernumber'} - ); - $it->{'charge'} = sprintf("%.2f", $it->{'charge'}); - my ($can_renew, $can_renew_error) = CanBookBeRenewed( - $borrower->{'borrowernumber'},$it->{'itemnumber'} - ); - $it->{"renew_error_${can_renew_error}"} = 1 if defined $can_renew_error; + $it->{'itemtype'} = ( C4::Context->preference('item-level_itypes') ) ? $it->{'itype'} : $it->{'itemtype'}; + + ( $it->{'charge'}, $it->{'itemtype_charge'} ) = GetIssuingCharges( $it->{'itemnumber'}, $borrower->{'borrowernumber'} ); + $it->{'charge'} = sprintf( "%.2f", $it->{'charge'} ); + my ( $can_renew, $can_renew_error ) = CanBookBeRenewed( $borrower->{'borrowernumber'}, $it->{'itemnumber'} ); + if ( defined $can_renew_error->{message} ) { + $it->{ "renew_error_" . $can_renew_error->{message} } = 1; + $it->{'renew_error'} = 1; + } + $it->{renewals} ||= 0; + $it->{$_} = $can_renew_error->{$_} for (qw(renewalsallowed reserves)); my ( $restype, $reserves ) = CheckReserves( $it->{'itemnumber'} ); - $it->{'can_renew'} = $can_renew; - $it->{'can_confirm'} = !$can_renew && !$restype; - $it->{'renew_error'} = $restype; - $it->{'checkoutdate'} = C4::Dates->new($it->{'issuedate'},'iso')->output('syspref'); + if ($restype) { + $it->{'reserved'} = 1; + $it->{$restype} = 1; + } + $it->{'can_renew'} = $can_renew; + $it->{'can_confirm'} = !$can_renew && !$restype; + $it->{'renew_error'} = $restype; + $it->{'checkoutdate'} = $it->{'issuedate'}; $totalprice += $it->{'replacementprice'}; - $it->{'itemtype'} = $itemtypeinfo->{'description'}; + $it->{'itemtype'} = $itemtypeinfo->{'description'}; $it->{'itemtype_image'} = $itemtypeinfo->{'imageurl'}; - $it->{'dd'} = format_date($it->{'date_due'}); - $it->{'displaydate'} = format_date($it->{'issuedate'}); $it->{'od'} = ( $it->{'date_due'} lt $todaysdate ) ? 1 : 0 ; - ($it->{'author'} eq '') and $it->{'author'} = ' '; - $it->{'renew_failed'} = $renew_failed{$it->{'itemnumber'}}; - - if ( $todaysdate eq $it->{'issuedate'} or $todaysdate eq $it->{'lastreneweddate'} ) { - (!$relatives) ? push @todaysissues, $it : push @relissues, $it; - } else { - (!$relatives) ? push @previousissues, $it : push @relprevissues, $it; + $it->{'dd'} = format_date( $it->{'date_due'} ); + $it->{'displaydate'} = format_date( $it->{'issuedate'} ); + ( $it->{'author'} eq '' ) and $it->{'author'} = ' '; + if ( defined( $return_failed{ $it->{'itemnumber'} } ) ) { + $it->{ 'return_error_' . $return_failed{ $it->{'itemnumber'} }->{message} } = 1; + delete $return_failed{ $it->{'itemnumber'} }; + } + if ( defined( $renew_failed{ $it->{'itemnumber'} } ) ) { + $it->{ 'renew_error_' . $renew_failed{ $it->{'itemnumber'} }->{message} } = 1; } + $it->{'return_failed'} = defined( $return_failed{ $it->{'itemnumber'} } ); + $it->{'branchdisplay'} = GetBranchName( ( C4::Context->preference('HomeOrHoldingBranch') eq 'holdingbranch' ) ? $it->{'holdingbranch'} : $it->{'homebranch'} ); + + # ADDED BY JF: NEW ITEMTYPE COUNT DISPLAY + $issued_itemtypes_count->{ $it->{'itemtype'} }++; + if ( $todaysdate eq $it->{'checkoutdate'} or $todaysdate eq $it->{'lastreneweddate'} ) { + (!$relatives) ? push @todaysissues, $it : push @relissues, $it; + } else { + (!$relatives) ? push @previousissues, $it : push @relprevissues, $it; + } + } } @@ -484,7 +560,48 @@ if ($borrower) { @previousissues = sort { $b->{'date_due'} cmp $a->{'date_due'} } @previousissues; } } +my @reserveswaiting; +foreach my $itemnumber (keys %return_failed){ + next unless $return_failed{$itemnumber}->{'reservesdata'}; + my $hashdata=$return_failed{$itemnumber}->{'reservesdata'}; + $hashdata->{circborrowernumber}=$borrowernumber; + $hashdata->{script_name}=$query->script_name(); + push @reserveswaiting, $hashdata if (%$hashdata); +} + +$template->param(reserves_waiting=>\@reserveswaiting); + +#### ADDED BY JF FOR COUNTS BY ITEMTYPE RULES +# FIXME: This should utilize all the issuingrules options rather than just the defaults +# and it should be moved to a module + +# how many of each is allowed? +my $issueqty_sth = $dbh->prepare( + 'SELECT itemtypes.description AS description,issuingrules.itemtype,maxissueqty ' . + 'FROM issuingrules LEFT JOIN itemtypes ON (itemtypes.itemtype=issuingrules.itemtype) ' . + 'WHERE categorycode=?' +); +$issueqty_sth->execute(q{*}); # This is a literal asterisk, not a wildcard. +while ( my $data = $issueqty_sth->fetchrow_hashref() ) { + + # subtract how many of each this borrower has + $data->{'count'} = $issued_itemtypes_count->{ $data->{'description'} }; + $data->{'left'} = + ( $data->{'maxissueqty'} - + $issued_itemtypes_count->{ $data->{'description'} } ); + + # can't have a negative number of remaining + if ( $data->{'left'} < 0 ) { $data->{'left'} = '0' } + if ( $data->{maxissueqty} <= $data->{count} ) { + $data->{flag} = 1; + } + if ( $data->{maxissueqty} > 0 && $data->{itemtype} !~m/^(\*|CIRC)$/ ) { + push @issued_itemtypes_count_loop, $data; + } +} + +#### / JF my @values; my %labels; @@ -514,6 +631,7 @@ if ($borrowerslist) { #title my $flags = $borrower->{'flags'}; + $debug && warn Dump($flags); foreach my $flag ( sort keys %$flags ) { $template->param( flagged=> 1); $flags->{$flag}->{'message'} =~ s#\n#
#g; @@ -523,20 +641,19 @@ foreach my $flag ( sort keys %$flags ) { noissues => 'true', ); if ( $flag eq 'GNA' ) { - $template->param( gna => 'true' ); + $template->param( gna => 'true', gonenoaddresscomment => $borrower->{'gonenoaddresscomment'}, warning => 1 ); } elsif ( $flag eq 'LOST' ) { - $template->param( lost => 'true' ); - } - elsif ( $flag eq 'DBARRED' ) { - $template->param( dbarred => 'true' ); - } - elsif ( $flag eq 'CHARGES' ) { + $template->param( lost => 'true', lostcomment => $borrower->{'lostcomment'}, warning => 1 ); + } elsif ( $flag eq 'DEBARRED' ) { + $template->param( userdebarred => 'true', warning => 1, userdebarreddate=> format_date($flags->{DEBARRED}->{dateend}),debardebarredcomment=>$borrower->{'debarredcomment'}); + } elsif ( $flag eq 'CHARGES' ) { $template->param( charges => 'true', chargesmsg => $flags->{'CHARGES'}->{'message'}, chargesamount => $flags->{'CHARGES'}->{'amount'}, - charges_is_blocker => 1 + charges_is_blocker => 1, + warning => 1 ); } elsif ( $flag eq 'CREDITS' ) { @@ -544,6 +661,7 @@ foreach my $flag ( sort keys %$flags ) { credits => 'true', creditsmsg => $flags->{'CREDITS'}->{'message'}, creditsamount => sprintf("%.02f", -($flags->{'CREDITS'}->{'amount'})), # from patron's pov + warning => 1 ); } } @@ -554,6 +672,7 @@ foreach my $flag ( sort keys %$flags ) { flagged => 1, chargesmsg => $flags->{'CHARGES'}->{'message'}, chargesamount => $flags->{'CHARGES'}->{'amount'}, + warning => 1 ); } elsif ( $flag eq 'CREDITS' ) { @@ -561,13 +680,15 @@ foreach my $flag ( sort keys %$flags ) { credits => 'true', creditsmsg => $flags->{'CREDITS'}->{'message'}, creditsamount => sprintf("%.02f", -($flags->{'CREDITS'}->{'amount'})), # from patron's pov + warning => 1 ); } elsif ( $flag eq 'ODUES' ) { $template->param( odues => 'true', flagged => 1, - oduesmsg => $flags->{'ODUES'}->{'message'} + oduesmsg => $flags->{'ODUES'}->{'message'}, + warning => 1 ); my $items = $flags->{$flag}->{'itemlist'}; @@ -624,7 +745,13 @@ if($bor_messages_loop){ $template->param(flagged => 1 ); } # Computes full borrower address my (undef, $roadttype_hashref) = &GetRoadTypes(); -my $address = $borrower->{'streetnumber'}.' '.$roadttype_hashref->{$borrower->{'streettype'}}.' '.$borrower->{'address'}; + +my $address = $borrower->{'streetnumber'}.' ' if ($borrower->{'streetnumber'}); +$address .= $roadttype_hashref->{$borrower->{'streettype'}}.' ' if ( $roadttype_hashref->{$borrower->{'streettype'}}); +$address .= $borrower->{'address'}; + +$duedatespec = "" if not ($stickyduedate or scalar $confirm_required); +my @categories = C4::Category->all; my $fast_cataloging = 0; if (defined getframeworkinfo('FA')) { @@ -644,7 +771,8 @@ $template->param( printername => $printer, firstname => $borrower->{'firstname'}, surname => $borrower->{'surname'}, - dateexpiry => format_date($newexpiry), + printer => $printer, + printername => $printer, expiry => format_date($borrower->{'dateexpiry'}), categorycode => $borrower->{'categorycode'}, categoryname => $borrower->{description}, @@ -679,8 +807,11 @@ $template->param( circview => 1, soundon => C4::Context->preference("SoundOn"), fast_cataloging => $fast_cataloging, + categoryloop => \@categories, ); +$template->param(newexpiry => format_date($newexpiry)) if (defined $newexpiry); + # save stickyduedate to session if ($stickyduedate) { $session->param( 'stickyduedate', $duedatespec ); --- a/circ/returns.pl +++ a/circ/returns.pl @@ -37,14 +37,17 @@ use C4::Circulation; use C4::Dates qw/format_date/; use Date::Calc qw/Add_Delta_Days/; use C4::Calendar; +use C4::Budgets qw/GetCurrency/; use C4::Print; use C4::Reserves; use C4::Biblio; use C4::Items; use C4::Members; +use C4::Overdues qw/CheckBorrowerDebarred/; use C4::Branch; # GetBranches GetBranchName use C4::Koha; # FIXME : is it still useful ? use C4::RotatingCollections; +use C4::Debug; my $query = new CGI; @@ -134,6 +137,7 @@ if ( $query->param('resbarcode') ) { my $item = $query->param('itemnumber'); my $borrowernumber = $query->param('borrowernumber'); my $resbarcode = $query->param('resbarcode'); + my $reservenumber = $query->param('reservenumber'); my $diffBranchReturned = $query->param('diffBranch'); my $iteminfo = GetBiblioFromItemNumber($item); # fix up item type for display @@ -141,18 +145,19 @@ if ( $query->param('resbarcode') ) { my $diffBranchSend = ($userenv_branch ne $diffBranchReturned) ? $diffBranchReturned : undef; # diffBranchSend tells ModReserveAffect whether document is expected in this library or not, # i.e., whether to apply waiting status - ModReserveAffect( $item, $borrowernumber, $diffBranchSend); + ModReserveAffect( $item, $borrowernumber, $diffBranchSend, $reservenumber ); + # check if we have other reserves for this document, if we have a return send the message of transfer - my ( $messages, $nextreservinfo ) = GetOtherReserves($item); + my ( $reservemessages, $nextreservinfo ) = GetOtherReserves($item); my ($borr) = GetMemberDetails( $nextreservinfo, 0 ); my $name = $borr->{'surname'} . ", " . $borr->{'title'} . " " . $borr->{'firstname'}; - if ( $messages->{'transfert'} ) { + if ( $reservemessages->{'transfert'} ) { $template->param( itemtitle => $iteminfo->{'title'}, itembiblionumber => $iteminfo->{'biblionumber'}, iteminfo => $iteminfo->{'author'}, - tobranchname => GetBranchName($messages->{'transfert'}), + tobranchname => GetBranchName($reservemessages->{'transfert'}), name => $name, borrowernumber => $borrowernumber, borcnum => $borr->{'cardnumber'}, @@ -168,12 +173,14 @@ my $returned = 0; my $messages; my $issueinformation; my $itemnumber; +my $biblio; my $barcode = $query->param('barcode'); my $exemptfine = $query->param('exemptfine'); my $dropboxmode = $query->param('dropboxmode'); my $dotransfer = $query->param('dotransfer'); my $canceltransfer = $query->param('canceltransfer'); my $dest = $query->param('dest'); +my $override = $query->param('override'); my $calendar = C4::Calendar->new( branchcode => $userenv_branch ); #dropbox: get last open day (today - 1) my $today = C4::Dates->new(); @@ -198,7 +205,7 @@ if ($canceltransfer){ } # actually return book and prepare item table..... -if ($barcode) { +if ($barcode and not $query->param('cancel')) { $barcode =~ s/^\s*|\s*$//g; # remove leading/trailing whitespace $barcode = barcodedecode($barcode) if C4::Context->preference('itemBarcodeInputFilter'); $itemnumber = GetItemnumberFromBarcode($barcode); @@ -221,11 +228,11 @@ if ($barcode) { # save the return # ( $returned, $messages, $issueinformation, $borrower ) = - AddReturn( $barcode, $userenv_branch, $exemptfine, $dropboxmode); # do the return + AddReturn( $barcode, $userenv_branch, $exemptfine, $dropboxmode,$override); # do the return my $homeorholdingbranchreturn = C4::Context->preference('HomeOrHoldingBranchReturn') or 'homebranch'; # get biblio description - my $biblio = GetBiblioFromItemNumber($itemnumber); + $biblio = GetBiblioFromItemNumber($itemnumber); # fix up item type for display $biblio->{'itemtype'} = C4::Context->preference('item-level_itypes') ? $biblio->{'itype'} : $biblio->{'itemtype'}; @@ -283,7 +290,7 @@ if ($barcode) { } } } - elsif ( !$messages->{'BadBarcode'} ) { + elsif ( !$messages->{'BadBarcode'} and ! $messages->{'Wrongbranch'} ) { $input{duedate} = 0; $returneditems{0} = $barcode; $riduedate{0} = 0; @@ -301,6 +308,18 @@ if ($barcode) { } $template->param( inputloop => \@inputloop ); + +# Is there a circulation note? +my $itemnumber = GetItemnumberFromBarcode($barcode); +my $biblionumber = GetBiblionumberFromItemnumber($itemnumber); +my $record = GetMarcBiblio($biblionumber); +my $frameworkcode = GetFrameworkCode($biblionumber); +my $circnotefield = GetRecordValue('circnote', $record, $frameworkcode); +if (defined @$circnotefield[0]) { + $template->param(circnote => @$circnotefield[0]->{'subfield'}); +} + + my $found = 0; my $waiting = 0; my $reserved = 0; @@ -324,9 +343,13 @@ if ( $messages->{'NeedsTransfer'} ){ } if ( $messages->{'Wrongbranch'} ){ - $template->param( - wrongbranch => 1, - ); + $template->param( + wrongbranch => $branches->{$messages->{'Wrongbranch'}->{'Wrongbranch'}}->{'branchname'}, + rightbranch => $branches->{$messages->{'Wrongbranch'}->{'Rightbranch'}}->{'branchname'}, + barcode => $barcode, + exemptfine => $exemptfine, + dropboxmode => $dropboxmode, + ); } # case of wrong transfert, if the document wasn't transfered to the right library (according to branchtransfer (tobranch) BDD) @@ -382,7 +405,7 @@ if ( $messages->{'ResFound'}) { reserved => 1, ); } - + my $debarred = CheckBorrowerDebarred($reserve->{borrowernumber}); # same params for Waiting or Reserved $template->param( found => 1, @@ -399,17 +422,18 @@ if ( $messages->{'ResFound'}) { borcity => $borr->{'city'}, borzip => $borr->{'zipcode'}, borcnum => $borr->{'cardnumber'}, - debarred => $borr->{'debarred'}, + debarred => $debarred, gonenoaddress => $borr->{'gonenoaddress'}, barcode => $barcode, - destbranch => $reserve->{'branchcode'}, + reservenumber => $reserve->{'reservenumber'}, + destbranch => $reserve->{'branchcode'}, borrowernumber => $reserve->{'borrowernumber'}, itemnumber => $reserve->{'itemnumber'}, reservenotes => $reserve->{'reservenotes'}, ); } # else { ; } # error? } - +$borrower->{'flags'}= C4::Members::patronflags($borrower); # Error Messages my @errmsgloop; foreach my $code ( keys %$messages ) { @@ -435,6 +459,35 @@ foreach my $code ( keys %$messages ) { elsif ( $code eq 'WasTransfered' ) { ; # FIXME... anything to do here? } + elsif ( $code eq 'NotForLoan' ) { + my $fw = GetFrameworkCode($biblio->{'biblionumber'}); + my $category = GetAuthValCode('items.notforloan',$fw); + my $authorizedvalues = GetAuthorisedValues($category, $messages->{$code}); + + foreach my $authvalue (@$authorizedvalues){ + $err{notforloan} = $authvalue->{lib} if $authvalue->{'authorised_value'} eq $messages->{$code}; + } + } + elsif ( $code eq 'Damaged' ) { + my $fw = GetFrameworkCode($biblio->{'biblionumber'}); + my $category = GetAuthValCode('items.damaged',$fw); + my $authorizedvalues = GetAuthorisedValues($category, $messages->{$code}); + + foreach my $authvalue (@$authorizedvalues){ + $err{damaged} = $authvalue->{lib} if $authvalue->{'authorised_value'} eq $messages->{$code}; + } + } + elsif( $code eq 'Debarred' ){ + $err{debarred} = format_date($messages->{'Debarred'}); + $err{debarcardnumber} = $borrower->{cardnumber}; + $err{debarborrowernumber} = $borrower->{borrowernumber}; + $err{debarname} = "$borrower->{firstname} $borrower->{surname}"; + } + elsif( $code eq 'HaveFines' ){ + $err{havefines} = $borrower->{'flags'}->{'CHARGES'}->{'amount'} . %{GetCurrency()}->{symbol}; + $err{finecardnumber} = $borrower->{cardnumber}; + $err{finename} = "$borrower->{firstname} $borrower->{surname}"; + } elsif ( $code eq 'wthdrawn' ) { $err{withdrawn} = 1; $exit_required_p = 1; @@ -456,7 +509,6 @@ foreach my $code ( keys %$messages ) { } elsif ( $code eq 'Wrongbranch' ) { } - else { die "Unknown error code $code"; # note we need all the (empty) elsif's above, or we die. # This forces the issue of staying in sync w/ Circulation.pm @@ -470,14 +522,10 @@ $template->param( errmsgloop => \@errmsgloop ); # patrontable .... if ($borrower) { - my $flags = $borrower->{'flags'}; my @flagloop; - my $flagset; + my $flags=$borrower->{flags}; foreach my $flag ( sort keys %$flags ) { my %flaginfo; - unless ($flagset) { $flagset = 1; } - $flaginfo{redfont} = ( $flags->{$flag}->{'noissues'} ); - $flaginfo{flag} = $flag; if ( $flag eq 'CHARGES' ) { $flaginfo{msg} = $flag; $flaginfo{charges} = 1; @@ -492,14 +540,29 @@ if ($borrower) { foreach my $item (@$items) { my $biblio = GetBiblioFromItemNumber( $item->{'itemnumber'}); push @waitingitemloop, { - biblionum => $biblio->{'biblionumber'}, - barcode => $biblio->{'barcode'}, - title => $biblio->{'title'}, - brname => $branches->{ $biblio->{'holdingbranch'} }->{'branchname'}, + biblionumber=> $biblio->{'biblionumber'}, + author => $biblio->{'author'}, + itemtype => $item->{'ccode'}, + reservedate => format_date($item->{'reservedate'}), + waitingdate => format_date($item->{'waitingdate'}), + barcode => $item->{'barcode'}, + maxpickupdate=> format_date( sprintf("%d-%02d-%02d",GetMaxPickupDate($item->{'waitingdate'}, $borrower->{borrowernumber},$item ))), + title => $biblio->{'title'}, + brname => $branches->{ $item->{'branchcode'} }->{'branchname'}, + waitinghere => ($item->{'branchcode'} eq $userenv_branch) }; } $flaginfo{itemloop} = \@waitingitemloop; } + elsif ( $flag =~ /DEBARRED|LOST|GNA/ ) { + if ($flag ne "GNA"){ + $flag=lc($flag); + } + else{ + $flag="gonenoaddress"; + } + %flaginfo=( $flag =>1, $flag."comment"=>$borrower->{$flag."comment"},dateend=>$flags->{uc($flag)}->{dateend}); + } elsif ( $flag eq 'ODUES' ) { my $items = $flags->{$flag}->{'itemlist'}; my @itemloop; @@ -525,7 +588,6 @@ if ($borrower) { push( @flagloop, \%flaginfo ); } $template->param( - flagset => $flagset, flagloop => \@flagloop, riborrowernumber => $borrower->{'borrowernumber'}, riborcnum => $borrower->{'cardnumber'}, @@ -536,7 +598,7 @@ if ($borrower) { } #set up so only the last 8 returned items display (make for faster loading pages) -my $returned_counter = ( C4::Context->preference('numReturnedItemsToShow') ) ? C4::Context->preference('numReturnedItemsToShow') : 8; +my $returned_counter = ( C4::Context->preference('numReturnedItemsToShow') ) ? C4::Context->preference('numReturnedItemsToShow') : 10; my $count = 0; my @riloop; my $shelflocations = GetKohaAuthorisedValues('items.location',''); @@ -564,9 +626,9 @@ foreach ( sort { $a <=> $b } keys %returneditems ) { else { $ri{borrowernumber} = $riborrowernumber{$_}; } - # my %ri; my $biblio = GetBiblioFromItemNumber(GetItemnumberFromBarcode($bar_code)); + my $item = GetItem(GetItemnumberFromBarcode($bar_code)); # fix up item type for display $biblio->{'itemtype'} = C4::Context->preference('item-level_itypes') ? $biblio->{'itype'} : $biblio->{'itemtype'}; $ri{itembiblionumber} = $biblio->{'biblionumber'}; @@ -575,14 +637,16 @@ foreach ( sort { $a <=> $b } keys %returneditems ) { $ri{itemcallnumber} = $biblio->{'itemcallnumber'}; $ri{itemtype} = $biblio->{'itemtype'}; $ri{itemnote} = $biblio->{'itemnotes'}; - $ri{ccode} = $biblio->{'ccode'}; $ri{itemnumber} = $biblio->{'itemnumber'}; + $ri{itemtype} = $item->{'itype'}; + $ri{ccode} = $item->{'ccode'}; + $ri{itemcallnumber} = $item->{'itemcallnumber'}; + $ri{homebranch} = $item->{'homebranch'}; + $ri{holdingbranch} = $item->{'holdingbranch'}; $ri{barcode} = $bar_code; - $ri{location} = $biblio->{'location'}; - my $shelfcode = $ri{'location'}; - $ri{'location'} = $shelflocations->{$shelfcode} if ( defined( $shelfcode ) && defined($shelflocations) && exists( $shelflocations->{$shelfcode} ) ); - + my $shelflocations = GetKohaAuthorisedValues('items.location',''); + $ri{itemlocation} = $shelflocations->{$biblio->{'location'}}; } else { last; --- a/installer/data/mysql/de-DE/mandatory/sysprefs.sql +++ a/installer/data/mysql/de-DE/mandatory/sysprefs.sql @@ -317,4 +317,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn | a|a d', NULL, NULL, 'Textarea'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0, 'If ON Openlibrary book covers will be show',NULL,'YesNo'); - +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ReservesControlBranch','PatronLibrary','Which branch to use do check holds limits','ItemHomeLibrary|PatronLibrary','Choice'); --- a/installer/data/mysql/en/mandatory/sysprefs.sql +++ a/installer/data/mysql/en/mandatory/sysprefs.sql @@ -317,3 +317,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn | a|a d', NULL, NULL, 'Textarea'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ReservesControlBranch','PatronLibrary','Which branch to use do check holds limits','ItemHomeLibrary|PatronLibrary','Choice'); --- a/installer/data/mysql/fr-FR/1-Obligatoire/unimarc_standard_systemprefs.sql +++ a/installer/data/mysql/fr-FR/1-Obligatoire/unimarc_standard_systemprefs.sql @@ -318,4 +318,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn | a|a d', NULL, NULL, 'Textarea'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo'); - +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ReservesControlBranch','PatronLibrary','Which branch to use do check holds limits','ItemHomeLibrary|PatronLibrary','Choice'); --- a/installer/data/mysql/it-IT/necessari/sysprefs.sql +++ a/installer/data/mysql/it-IT/necessari/sysprefs.sql @@ -305,3 +305,4 @@ INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn | a|a d', NULL, NULL, 'Textarea'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ReservesControlBranch','PatronLibrary','Which branch to use do check holds limits','ItemHomeLibrary|PatronLibrary','Choice'); --- a/installer/data/mysql/kohastructure.sql +++ a/installer/data/mysql/kohastructure.sql @@ -976,9 +976,13 @@ CREATE TABLE `issuingrules` ( `maxissueqty` int(4) default NULL, `issuelength` int(4) default NULL, `hardduedate` date default NULL, - `hardduedatecompare` tinyint NOT NULL default "0", - `renewalsallowed` smallint(6) NOT NULL default "0", - `reservesallowed` smallint(6) NOT NULL default "0", + `holdspickupdelay` int(11) default NULL, + `renewalperiod` int(11) default NULL, + `allowonshelfholds` tinyint(1) default NULL, + `holdrestricted` tinyint(1) default NULL, + `hardduedatecompare` tinyint NULL default "0", + `renewalsallowed` smallint(6) NULL default "0", + `reservesallowed` smallint(6) NULL default "0", `branchcode` varchar(10) NOT NULL default '', PRIMARY KEY (`branchcode`,`categorycode`,`itemtype`), KEY `categorycode` (`categorycode`), @@ -1364,6 +1368,7 @@ CREATE TABLE `old_issues` ( -- DROP TABLE IF EXISTS `old_reserves`; CREATE TABLE `old_reserves` ( + `reservenumber` int(11) NOT NULL AUTO_INCREMENT, `borrowernumber` int(11) default NULL, `reservedate` date default NULL, `biblionumber` int(11) default NULL, @@ -1380,6 +1385,7 @@ CREATE TABLE `old_reserves` ( `waitingdate` date default NULL, `expirationdate` DATE DEFAULT NULL, `lowestPriority` tinyint(1) NOT NULL, + PRIMARY KEY (`reservenumber`), KEY `old_reserves_borrowernumber` (`borrowernumber`), KEY `old_reserves_biblionumber` (`biblionumber`), KEY `old_reserves_itemnumber` (`itemnumber`), @@ -1539,6 +1545,7 @@ CREATE TABLE `reserveconstraints` ( DROP TABLE IF EXISTS `reserves`; CREATE TABLE `reserves` ( + `reservenumber` int(11) NOT NULL AUTO_INCREMENT, `borrowernumber` int(11) NOT NULL default 0, `reservedate` date default NULL, `biblionumber` int(11) NOT NULL default 0, @@ -1555,6 +1562,7 @@ CREATE TABLE `reserves` ( `waitingdate` date default NULL, `expirationdate` DATE DEFAULT NULL, `lowestPriority` tinyint(1) NOT NULL, + PRIMARY KEY (`reservenumber`), KEY `borrowernumber` (`borrowernumber`), KEY `biblionumber` (`biblionumber`), KEY `itemnumber` (`itemnumber`), --- a/installer/data/mysql/nb-NO/1-Obligatorisk/sysprefs.sql +++ a/installer/data/mysql/nb-NO/1-Obligatorisk/sysprefs.sql @@ -324,3 +324,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn | a|a d', NULL, NULL, 'Textarea'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ReservesControlBranch','PatronLibrary','Which branch to use do check holds limits','ItemHomeLibrary|PatronLibrary','Choice'); --- a/installer/data/mysql/pl-PL/mandatory/sysprefs.sql +++ a/installer/data/mysql/pl-PL/mandatory/sysprefs.sql @@ -316,3 +316,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn | a|a d', NULL, NULL, 'Textarea'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0, 'If ON Openlibrary book covers will be show',NULL,'YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ReservesControlBranch','PatronLibrary','Which branch to use do check holds limits','ItemHomeLibrary|PatronLibrary','Choice'); --- a/installer/data/mysql/ru-RU/mandatory/system_preferences_full_optimal_for_install_only.sql +++ a/installer/data/mysql/ru-RU/mandatory/system_preferences_full_optimal_for_install_only.sql @@ -371,3 +371,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn | a|a d', NULL, NULL, 'Textarea'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo'); +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ReservesControlBranch','PatronLibrary','Which branch to use do check holds limits','ItemHomeLibrary|PatronLibrary','Choice'); --- a/installer/data/mysql/uk-UA/mandatory/system_preferences_full_optimal_for_install_only.sql +++ a/installer/data/mysql/uk-UA/mandatory/system_preferences_full_optimal_for_install_only.sql @@ -396,4 +396,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn | a|a d', NULL, NULL, 'Textarea'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0, 'If ON Openlibrary book covers will be show',NULL,'YesNo'); - +INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('ReservesControlBranch','PatronLibrary','Which branch to use do check holds limits','ItemHomeLibrary|PatronLibrary','Choice'); --- a/installer/data/mysql/updatedatabase.pl +++ a/installer/data/mysql/updatedatabase.pl @@ -4398,6 +4398,59 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) { print "Add state and country to cities table corresponding to new columns in borrowers\n"; SetVersion($DBversion); } +$DBversion = '3.05.00.005'; +if (C4::Context->preference("Version") < TransformToNum($DBversion)) { + $dbh->do("ALTER TABLE issuingrules ADD holdspickupdelay int(11) default NULL"); + $dbh->do("ALTER TABLE issuingrules ADD renewalperiod int(11) default NULL"); + $dbh->do("ALTER TABLE issuingrules ADD allowonshelfholds tinyint(1) default NULL"); + $dbh->do("ALTER TABLE issuingrules ADD holdrestricted tinyint(1) default NULL"); + + my $sth = $dbh->prepare("SELECT value from systempreferences where variable = 'AllowOnShelfHolds';"); + $sth->execute(); + my $data = $sth->fetchrow_hashref(); + + if ($data->{value}){ + my $updsth = $dbh->prepare("UPDATE issuingrules SET allowonshelfholds = ?"); + $updsth->execute( $data->{value} ); + } + $dbh->do( + 'INSERT IGNORE INTO issuingrules (branchcode, categorycode, itemtype,holdrestricted,maxissueqty) + SELECT "*","*","*",holdallowed,maxissueqty + FROM default_circ_rules defaults + ON DUPLICATE KEY update maxissueqty=defaults.maxissueqty, holdrestricted=defaults.holdallowed' + ); + $dbh->do( + 'INSERT IGNORE INTO issuingrules (branchcode, itemtype, categorycode,maxissueqty) + SELECT "*","*",categorycode,maxissueqty from default_borrower_circ_rules defaults + ON DUPLICATE KEY update maxissueqty=defaults.maxissueqty' + ); + $dbh->do( + 'INSERT IGNORE INTO issuingrules (branchcode, categorycode, itemtype,holdrestricted,maxissueqty) + SELECT branchcode,"*","*",holdallowed,maxissueqty from default_branch_circ_rules defaults + ON DUPLICATE KEY update maxissueqty=defaults.maxissueqty, holdrestricted=defaults.holdallowed' + ); + $dbh->do( + 'INSERT IGNORE INTO issuingrules (branchcode, categorycode, itemtype,holdrestricted) + SELECT "*","*",itemtype,holdallowed from default_branch_item_rules defaults + ON DUPLICATE KEY update holdrestricted=defaults.holdallowed' + ); + $dbh->do('ALTER TABLE issuingrules CHANGE reservesallowed reservesallowed smallint(6) NULL'); + $dbh->do('ALTER TABLE issuingrules CHANGE renewalsallowed renewalsallowed smallint(6) NULL'); +# for my $tablename qw(default_circ_rules default_branch_circ_rules default_branch_item_rules default_borrower_circ_rules) { +# $dbh->do("DROP TABLE $tablename"); +# } + + $dbh->do("INSERT INTO systempreferences (variable,value, options, explanation, type) VALUES('ReservesControlBranch','PatronLibrary','ItemHomeLibrary|PatronLibrary','Which branch to use do check holds limits','Choice')"); + $dbh->do(q{ + ALTER TABLE reserves ADD `reservenumber` int(11) NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`reservenumber`); +}); + $dbh->do(q{ + ALTER TABLE old_reserves ADD `reservenumber` int(11) NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`reservenumber`); +}); + + print "Upgrade to $DBversion done (Improved circulation rules : holds and renewals at granular level)\n"; + SetVersion ($DBversion); +} =head1 FUNCTIONS --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -2081,3 +2081,6 @@ fieldset.rows+h3 {clear:both;padding-top:.5em;} color : #cc0000; } +.herited { + color: #ccc; +} --- a/koha-tmpl/intranet-tmpl/prog/en/lib/jquery/plugins/jeditable +++ a/koha-tmpl/intranet-tmpl/prog/en/lib/jquery/plugins/jeditable @@ -0,0 +1, @@ +Subproject commit 29b70035bbd60fc9412df3a5186367034114c9b2 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt @@ -2,13 +2,135 @@ Koha › Administration › Circulation and Fine Rules [% INCLUDE 'doc-head-close.inc' %] + + + + + + + + + + + + @@ -37,24 +159,10 @@ $(document).ready(function() { Defining circulation and fine rules for all libraries [% END %] -
-

The rules are applied from most specific to less specific, using the first found in this order:

-
    -
  • same library, same patron type, same item type
  • -
  • same library, same patron type, all item types
  • -
  • same library, all patron types, same item type
  • -
  • same library, all patron types, all item types
  • -
  • all libraries, same patron type, same item type
  • -
  • all libraries, same patron type, all item types
  • -
  • all libraries, all patron types, same item type
  • -
  • all libraries, all patron types, all item types
  • -
-

To modify a rule, create a new one with the same patron type and item type.

-
-
+
Select a library : - [% FOREACH branchloo IN branchloop %] [% IF ( branchloo.selected ) %][% ELSE %][% END %] @@ -64,304 +172,243 @@ $(document).ready(function() { [% IF ( definedbranch ) %]
[% END %] - -
- - - - - - - - - - - - - - - - - - [% FOREACH rule IN rules %] - [% UNLESS ( loop.odd ) %] - - [% ELSE %] - - [% END %] - - - - - - [% ELSE %][% IF ( rule.hardduedateexact ) %]on [% rule.hardduedate %] - [% ELSE %][% IF ( rule.hardduedateafter ) %]after [% rule.hardduedate %][% END %] - [% END %] - [% END %] - [% ELSE %]None defined[% END %] - - - - - - - - - - [% END %] - - - - - - - - - - - - - - - -
Patron CategoryItem TypeCurrent Checkouts AllowedLoan Period (day)Hard Due DateFine AmountFine Charging IntervalFine Grace period (day)Suspension in Days (day)Renewals Allowed (count)Holds Allowed (count)Rental Discount (%) 
[% IF ( rule.default_humancategorycode ) %] - All - [% ELSE %] - [% rule.humancategorycode %] - [% END %] - [% IF ( rule.default_humanitemtype ) %] - All - [% ELSE %] - [% rule.humanitemtype %] - [% END %] - [% IF ( rule.unlimited_maxissueqty ) %] - Unlimited - [% ELSE %] - [% rule.maxissueqty %] - [% END %] - [% rule.issuelength %][% IF ( rule.hardduedate ) %] - [% IF ( rule.hardduedatebefore ) %]before [% rule.hardduedate %][% rule.fine %][% rule.chargeperiod %][% rule.firstremind %][% rule.finedays %][% rule.renewalsallowed %][% rule.reservesallowed %][% rule.rentaldiscount %] - Delete -
- - - - - - [% INCLUDE 'date-format.inc' %] - Show Calendar - -
-
-
-
-

Default checkout and hold policy for [% IF ( humanbranch ) %][% humanbranch %][% ELSE %]all libraries[% END %]

-

You can set a default maximum number of checkouts and hold policy that will be used if none is defined below for a particular item type or category.

-
- - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + +
 Total Current Checkouts AllowedHold Policy  
Defaults[% UNLESS ( default_rules ) %] (not set)[% END %] - + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + [% FOREACH rule IN rules %] + + + + [% IF ( rule.herited_maxissueqty ) %] + + [% IF ( rule.herited_issuelength ) %] + + [% IF ( rule.herited_hardduedatecompare ) %] + + [% IF (rule.herited_hardduedate ) %] + + [% IF (rule.herited_fine ) %] + + [% IF ( rule.herited_chargeperiod ) %] + + [% IF ( rule.herited_firstremind ) %] + + [% IF ( rule.herited_finedays ) %] + + [% IF ( rule.herited_renewalsallowed ) %] + + [% IF ( rule.herited_renewalperiod ) %] + + [% IF ( rule.herited_reservesallowed ) %] + + [% IF ( rule.herited_holdspickupdelay ) %] + + [% IF ( rule.herited_allowonshelfholds ) %] + + [% IF ( rule.herited_holdrestricted ) %] + + [% IF ( rule.herited_rentaldiscount ) %] + + + [% END %] - From Any Library - - [% IF ( default_holdallowed_same ) %] - - [% IF ( default_holdallowed_none ) %] - - - - - - -
Patron categoryItem typeCurrent checkouts allowedLoan period (day)Hard due date compareHard due dateFine amountFine charging intervalGrace period (day)Suspension duration (day)Renewals allowed (count)Renewals period (days)Holds allowed (count)Holds pickup delay (day)Allow on shelf holdsHolds restricted to libraryRental discount (%) 
+ [% rule.categorycode %] + [% IF ( rule.default_humancategorycode ) %] + Default + [% ELSE %] + [% rule.humancategorycode %] + [% END %] + + [% rule.itemtype %] + [% IF ( rule.default_humanitemtype ) %] + Default + [% ELSE %] + [% rule.humanitemtype %] + [% END %] + + [% ELSE %] + + [% END %] + [% IF ( rule.unlimited_maxissueqty ) %] + Unlimited + [% ELSE %] + [% rule.maxissueqty %] + [% END %] + + [% ELSE %] + + [% END %][% rule.issuelength %] + + [% ELSE %] + + [% END %] + [% rule.hardduedatecompare %] + + [% ELSE %] + + [% END %] + [% rule.hardduedate %] + + [% ELSE %] + + [% END %] + [% fine %] + + [% ELSE %] + + [% END %] + [% rule.chargeperiod %] + + [% ELSE %] + + [% END %] + [% rule.firstremind %] + + [% ELSE %] + + [% END %] + [% rule.finedays %] + + [% ELSE %] + + [% END %] + [% rule.renewalsallowed %] + + [% ELSE %] + + [% END %] + [% rule.renewalperiod %] + + [% ELSE %] + + [% END %] + [% rule.reservesallowed %] + + [% ELSE %] + + [% END %] + [% rule.holdspickupdelay %] + + [% ELSE %] + + [% END %] + [% IF ( rule.allowonshelfholds ) %] + Yes + [% ELSE %] + No + [% END %] + + [% ELSE %] + + [% END %] + [% IF ( rule.holdrestricted ) %] + Yes + [% ELSE %] + No + [% END %] + + [% ELSE %] + + [% END %] + [% rule.rentaldiscount %] + + Delete +
- Unset -
- -
- [% IF ( show_branch_cat_rule_form ) %] -
-

Checkout limit by patron category for [% IF ( humanbranch ) %][% humanbranch %][% ELSE %]all libraries[% END %]

-

For this library, you can specify the maximum number of loans that - a patron of a given category can make, regardless of the item type. -

-

If the total amount loanable for a given patron category is left blank, - no limit applies, except possibly for a limit you define for a specific item type. -

-
- - - - - - - - - [% FOREACH branch_cat_rule_loo IN branch_cat_rule_loop %] - [% UNLESS ( loop.odd ) %] - - [% ELSE %] - - [% END %] - - - - - [% END %] - - - - - -
Patron CategoryTotal Current Checkouts Allowed 
[% IF ( branch_cat_rule_loo.default_humancategorycode ) %] - Default - [% ELSE %] - [% branch_cat_rule_loo.humancategorycode %] - [% END %] - [% IF ( branch_cat_rule_loo.unlimited_maxissueqty ) %] - Unlimited - [% ELSE %] - [% branch_cat_rule_loo.maxissueqty %] - [% END %] - - Delete -
- -
-
-
- [% END %] -
-

Holds policy by item type for [% IF ( humanbranch ) %][% humanbranch %][% ELSE %]all libraries[% END %]

-

- For this library, you can edit rules for given itemtypes, regardless - of the patron's category. -

-

- Currently, this means hold policies. - The various policies have the following effects: -

-
    -
  • From Any Library: Patrons from any library may put this item on hold. (default if none is defined)
  • -
  • From Home Library: Only patrons from the item's home library may put this book on hold.
  • -
  • No Holds Allowed: No patron may put this book on hold.
  • -
-

- Note that if the system preference - AllowHoldPolicyOverride is enabled, these policies can - be overridden by your circulation staff. Also, these policies are - based on the patron's home library, not the library where the hold is being placed.. -

- -
- - - - - - - - - [% FOREACH branch_item_rule_loo IN branch_item_rule_loop %] - [% UNLESS ( loop.odd ) %] - - [% ELSE %] - - [% END %] - - - - - [% END %] - - - - - -
Item TypeHold Policy 
[% IF ( branch_item_rule_loo.default_humanitemtype ) %] - Default - [% ELSE %] - [% branch_item_rule_loo.humanitemtype %] - [% END %] - [% IF ( branch_item_rule_loo.holdallowed_any ) %] - From Any Library - [% ELSIF ( branch_item_rule_loo.holdallowed_same ) %] - From Home Library - [% ELSE %] - No Holds Allowed - [% END %] - - Delete -
- - - -
-
+
+ + + + + + + + + [% IF ( holdrestricted ) %] [% ELSE %] [% END %]
+
+
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt @@ -144,6 +144,7 @@ function Dopop(link) { + [% END %] @@ -180,6 +181,7 @@ function Dopop(link) { + @@ -281,6 +283,7 @@ function Dopop(link) { + [% END %] --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt @@ -23,10 +23,14 @@ function check() { var count_reserv = 0; var alreadyreserved = 0; + var hold_type=""; // check if we have checkitem form if (document.form.checkitem){ for (i=0;i= "2"){ + if (count_reserv >= "2" && hold_type != "multiple"){ msg += (_("- You may only place a hold on one item at a time") + "\n"); } - if (alreadyreserved > "0"){ + if (alreadyreserved > "0" && hold_type != "multiple"){ msg += (_("- This patron had already placed a hold on this item") + "\n" + _("Please cancel the previous hold first") + "\n"); } @@ -395,14 +399,33 @@ function checkMultiHold() { [% UNLESS ( itemloo.hide ) %] - [% IF ( itemloo.available ) %] - + [% IF ( itemloo.canholdmultiple ) %] + + [% ELSIF ( itemloo.available ) %] + [% ELSIF ( itemloo.override ) %] - - Requires override of hold policy + + [% END %] + [% IF ( itemloo.cannothold ) %] + + Cannot be put on hold [% ELSE %] - - Cannot be put on hold + [% IF ( itemloo.canholdmultiple ) %] + + [% ELSIF ( itemloo.available ) %] + [% IF ( itemloo.available ) %] + + [% ELSIF ( itemloo.override ) %] + + Requires override of hold policy + [% ELSE %] + + Cannot be put on hold + [% END %] + [% ELSE %] + + Cannot be put on hold + [% END %] [% END %] [% IF ( item_level_itypes ) %] @@ -713,7 +736,7 @@ function checkMultiHold() { [% END %] - + Cancel --- a/kohaversion.pl +++ a/kohaversion.pl @@ -16,7 +16,7 @@ the kohaversion is divided in 4 parts : use strict; sub kohaversion { - our $VERSION = '3.05.00.008'; + our $VERSION = '3.05.00.XXX'; # version needs to be set this way # so that it can be picked up by Makefile.PL # during install --- a/opac/opac-modrequest.pl +++ a/opac/opac-modrequest.pl @@ -42,7 +42,9 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user( ); my $biblionumber = $query->param('biblionumber'); +my $reservenumber = $query->param('reservenumber'); + if ($biblionumber and $borrowernumber) { - CancelReserve($biblionumber, '', $borrowernumber); + CancelReserve( $reservenumber, $biblionumber ); } print $query->redirect("/cgi-bin/koha/opac-user.pl#opac-user-holds"); --- a/opac/opac-reserve.pl +++ a/opac/opac-reserve.pl @@ -1,5 +1,7 @@ #!/usr/bin/perl +# Parts Copyright Biblibre 2010 + # This file is part of Koha. # # Koha is free software; you can redistribute it and/or modify it under the @@ -28,11 +30,12 @@ use C4::Output; use C4::Dates qw/format_date/; use C4::Context; use C4::Members; +use C4::Overdues; use C4::Branch; # GetBranches use C4::Debug; +use C4::Items; # use Data::Dumper; -my $MAXIMUM_NUMBER_OF_RESERVES = C4::Context->preference("maxreserves"); my $query = new CGI; my ( $template, $borrowernumber, $cookie ) = get_template_and_user( @@ -77,7 +80,9 @@ if ((! $biblionumbers) && (! $query->param('place_reserve'))) { # Pass the numbers to the page so they can be fed back # when the hold is confirmed. TODO: Not necessary? -$template->param( biblionumbers => $biblionumbers ); +$template->param( biblionumbers => $biblionumbers, + OPACPickupLocation => "" . C4::Context->preference("OPACPickupLocation"), + ); # Each biblio number is suffixed with '/', e.g. "1/2/3/" my @biblionumbers = split /\//, $biblionumbers; @@ -91,6 +96,7 @@ if (($#biblionumbers < 0) && (! $query->param('place_reserve'))) { my $branch = $query->param('branch') || $borr->{'branchcode'} || C4::Context->userenv->{branch} || '' ; ($branches->{$branch}) or $branch = ""; # Confirm branch is real $template->param( branch => $branch ); +$template->param( branchname => $branches->{$branch}->{branchname} ); # make branch selection options... my $CGIbranchloop = GetBranchesLoop($branch); @@ -252,7 +258,7 @@ if ( $borr->{lost} && ($borr->{lost} eq 1) ) { lost => 1 ); } -if ( $borr->{debarred} && ($borr->{debarred} eq 1) ) { +if ( CheckBorrowerDebarred($borrowernumber) ) { $noreserves = 1; $template->param( message => 1, @@ -262,11 +268,6 @@ if ( $borr->{debarred} && ($borr->{debarred} eq 1) ) { my @reserves = GetReservesFromBorrowernumber( $borrowernumber ); $template->param( RESERVES => \@reserves ); -if ( $MAXIMUM_NUMBER_OF_RESERVES && (scalar(@reserves) >= $MAXIMUM_NUMBER_OF_RESERVES) ) { - $template->param( message => 1 ); - $noreserves = 1; - $template->param( too_many_reserves => scalar(@reserves)); -} foreach my $res (@reserves) { foreach my $biblionumber (@biblionumbers) { if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) { @@ -342,10 +343,20 @@ foreach my $biblioNum (@biblionumbers) { } } + $biblioLoopIter{itemtype} = $biblioData->{itemtype}; + $biblioLoopIter{itemTypeDescription} = $itemTypes->{ $biblioData->{itemtype} }{description}; $biblioLoopIter{itemLoop} = []; my $numCopiesAvailable = 0; - foreach my $itemInfo (@{$biblioData->{itemInfos}}) { - my $itemNum = $itemInfo->{itemnumber}; + + # $canReserveMultiple is set to "yes" ( >0 ) if multiple items can be reserved + # it is set to 0 otherwise. depends on item-level_itypes syspref + # and the list of itypes that can be multiple reserved + my $canReserveMultiple = 0; + unless ( C4::Context->preference("item-level_itypes") ) { + $canReserveMultiple = CanHoldMultipleItems( $biblioLoopIter{itemtype} ); + } + foreach my $itemInfo ( @{ $biblioData->{itemInfos} } ) { + my $itemNum = $itemInfo->{itemnumber}; my $itemLoopIter = {}; $itemLoopIter->{itemnumber} = $itemNum; @@ -359,6 +370,16 @@ foreach my $biblioNum (@biblionumbers) { $itemLoopIter->{imageurl} = $itemInfo->{imageurl}; } + # check if the itype is one that can be multiple reserved + if ( C4::Context->preference("item-level_itypes") ) { + + # sum canReserveMultiple : if at least one item can be multiple reserved, then the flag will be >0 + # FIXME : there can be complex & strange cases, where some items can be multiple reserved, and some can't + # this case is not managed. Note it may be only theoric, and have no real case + $itemLoopIter->{canholdmultiple} = CanHoldMultipleItems( $itemInfo->{itype} ); + $canReserveMultiple = $canReserveMultiple + CanHoldMultipleItems( $itemInfo->{itype} ); + } + # If the holdingbranch is different than the homebranch, we show the # holdingbranch of the document too. if ( $itemInfo->{homebranch} ne $itemInfo->{holdingbranch} ) { @@ -375,9 +396,8 @@ foreach my $biblioNum (@biblionumbers) { } # checking reserve - my ($reservedate,$reservedfor,$expectedAt) = GetReservesFromItemnumber($itemNum); - my $ItemBorrowerReserveInfo = GetMemberDetails( $reservedfor, 0); - + my ( $reservedate, $reservedfor, $expectedAt ) = GetReservesFromItemnumber($itemNum); + my $ItemBorrowerReserveInfo = GetMemberDetails( $reservedfor, 0 ); if ( defined $reservedate ) { $itemLoopIter->{backgroundcolor} = 'reserved'; $itemLoopIter->{reservedate} = format_date($reservedate); @@ -385,6 +405,8 @@ foreach my $biblioNum (@biblionumbers) { $itemLoopIter->{ReservedForSurname} = $ItemBorrowerReserveInfo->{'surname'}; $itemLoopIter->{ReservedForFirstname} = $ItemBorrowerReserveInfo->{'firstname'}; $itemLoopIter->{ExpectedAtLibrary} = $expectedAt; + $itemLoopIter->{ReservedForThisBorrower} = ( $reservedfor eq $borrowernumber ); + warn "ReservedForThisBorrower: " . $itemLoopIter->{ReservedForThisBorrower}; } $itemLoopIter->{notforloan} = $itemInfo->{notforloan}; @@ -422,17 +444,7 @@ foreach my $biblioNum (@biblionumbers) { # If there is no loan, return and transfer, we show a checkbox. $itemLoopIter->{notforloan} = $itemLoopIter->{notforloan} || 0; - my $branch = C4::Circulation::_GetCircControlBranch($itemLoopIter, $borr); - - my $branchitemrule = GetBranchItemRule( $branch, $itemInfo->{'itype'} ); - my $policy_holdallowed = 1; - - if ( $branchitemrule->{'holdallowed'} == 0 || - ( $branchitemrule->{'holdallowed'} == 1 && $borr->{'branchcode'} ne $itemInfo->{'homebranch'} ) ) { - $policy_holdallowed = 0; - } - - if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum)) { + if (IsAvailableForItemLevelRequest($itemNum) and CanItemBeReserved($borrowernumber,$itemNum)) { $itemLoopIter->{available} = 1; $numCopiesAvailable++; } @@ -461,13 +473,16 @@ foreach my $biblioNum (@biblionumbers) { $biblioLoopIter{holdable} = 1; $anyholdable = 1; } - if ($biblioLoopIter{already_reserved}) { +# $biblioLoopIter{multi} = $canReserveMultiple; + if ( $biblioLoopIter{already_reserved} && !$canReserveMultiple ) { $biblioLoopIter{holdable} = undef; $anyholdable = undef; } if(not CanBookBeReserved($borrowernumber,$biblioNum)){ + $template->param( message => 1 ); + $noreserves = 1; + $template->param( too_many_reserves => scalar(@reserves) ); $biblioLoopIter{holdable} = undef; - $anyholdable = undef; } push @$biblioLoop, \%biblioLoopIter; --- a/reserve/modrequest.pl +++ a/reserve/modrequest.pl @@ -41,12 +41,13 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( } ); -my @rank=$query->param('rank-request'); -my @biblionumber=$query->param('biblionumber'); -my @borrower=$query->param('borrowernumber'); -my @branch=$query->param('pickup'); -my @itemnumber=$query->param('itemnumber'); -my $multi_hold = $query->param('multi_hold'); +my @rank = $query->param('rank-request'); +my @reservenumber = $query->param('reservenumber'); +my @biblionumber = $query->param('biblionumber'); +my @borrower = $query->param('borrowernumber'); +my @branch = $query->param('pickup'); +my @itemnumber = $query->param('itemnumber'); +my $multi_hold = $query->param('multi_hold'); my $biblionumbers = $query->param('biblionumbers'); my $count=@rank; @@ -54,9 +55,9 @@ my $CancelBiblioNumber=$query->param('CancelBiblioNumber'); my $CancelBorrowerNumber=$query->param('CancelBorrowerNumber'); my $CancelItemnumber=$query->param('CancelItemnumber'); -# 2 possibilitys : cancel an item reservation, or modify or cancel the queded list +# 2 possibilitys : cancel an item reservation, or modify or cancel the queued list -# 1) cancel an item reservation by fonction ModReserveCancelAll (in reserves.pm) +# 1) cancel an item reservation by function ModReserveCancelAll (in reserves.pm) if ($CancelBorrowerNumber) { ModReserveCancelAll($CancelItemnumber, $CancelBorrowerNumber); $biblionumber[0] = $CancelBiblioNumber, @@ -66,7 +67,7 @@ if ($CancelBorrowerNumber) { else { for (my $i=0;$i<$count;$i++){ undef $itemnumber[$i] unless $itemnumber[$i] ne ''; - ModReserve($rank[$i],$biblionumber[$i],$borrower[$i],$branch[$i],$itemnumber[$i]); #from C4::Reserves + ModReserve($rank[$i],$biblionumber[$i],$borrower[$i],$branch[$i],$itemnumber[$i],$reservenumber[$i]); #from C4::Reserves } } my $from=$query->param('from'); --- a/reserve/placerequest.pl +++ a/reserve/placerequest.pl @@ -3,7 +3,6 @@ #script to place reserves/requests #writen 2/1/00 by chris@katipo.oc.nz - # Copyright 2000-2002 Katipo Communications # # This file is part of Koha. @@ -37,34 +36,34 @@ my $input = CGI->new(); my ($user, $cookie, $sesion_id, $flags) = checkauth($input, 0, { reserveforothers => 'place_holds' }, 'intranet'); -my @bibitems=$input->param('biblioitem'); +my @bibitems = $input->param('biblioitem'); # FIXME I think reqbib does not exist anymore, it's used in line 82, to AddReserve of contraint type 'o' # I bet it's a 2.x feature, reserving a given biblioitem, that is useless in Koha 3.0 # we can remove this line, the AddReserve of constrainttype 'o', # and probably remove the reserveconstraint table as well, I never could fill anything in this table. -my @reqbib=$input->param('reqbib'); -my $biblionumber=$input->param('biblionumber'); -my $borrowernumber=$input->param('borrowernumber'); -my $notes=$input->param('notes'); -my $branch=$input->param('pickup'); -my $startdate=$input->param('reserve_date') || ''; -my @rank=$input->param('rank-request'); -my $type=$input->param('type'); -my $title=$input->param('title'); -my $borrower=GetMember('borrowernumber'=>$borrowernumber); -my $checkitem=$input->param('checkitem'); +#my @reqbib = $input->param('reqbib'); +my $biblionumber = $input->param('biblionumber'); +my $borrowernumber = $input->param('borrowernumber'); +my $notes = $input->param('notes'); +my $branch = $input->param('pickup'); +my $startdate = $input->param('reserve_date') || ''; +my @rank = $input->param('rank-request'); +my $type = $input->param('type'); +my $title = $input->param('title'); +my $borrower = GetMember( 'borrowernumber' => $borrowernumber ); +my @checkitems = $input->param('checkitem'); my $expirationdate = $input->param('expiration_date'); -my $multi_hold = $input->param('multi_hold'); -my $biblionumbers = $multi_hold ? $input->param('biblionumbers') : ($biblionumber . '/'); -my $bad_bibs = $input->param('bad_bibs'); +my $multi_hold = $input->param('multi_hold'); +my $biblionumbers = $multi_hold ? $input->param('biblionumbers') : ( $biblionumber . '/' ); +my $bad_bibs = $input->param('bad_bibs'); my %bibinfos = (); my @biblionumbers = split '/', $biblionumbers; foreach my $bibnum (@biblionumbers) { my %bibinfo = (); - $bibinfo{title} = $input->param("title_$bibnum"); - $bibinfo{rank} = $input->param("rank_$bibnum"); + $bibinfo{title} = $input->param("title_$bibnum"); + $bibinfo{rank} = $input->param("rank_$bibnum"); $bibinfos{$bibnum} = \%bibinfo; } @@ -72,27 +71,27 @@ my $found; # if we have an item selectionned, and the pickup branch is the same as the holdingbranch # of the document, we force the value $rank and $found . -if ($checkitem ne ''){ - $rank[0] = '0' unless C4::Context->preference('ReservesNeedReturns'); - my $item = $checkitem; - $item = GetItem($item); - if ( $item->{'holdingbranch'} eq $branch ){ - $found = 'W' unless C4::Context->preference('ReservesNeedReturns'); +for my $item (@checkitems){ + if ( $item ne '' ) { + $rank[0] = '0' unless C4::Context->preference('ReservesNeedReturns'); + my $itemdata = GetItem($item); + if ( $itemdata->{'holdingbranch'} eq $branch ) { + $found = 'W' unless C4::Context->preference('ReservesNeedReturns'); + } } } -if ($type eq 'str8' && $borrower){ - - foreach my $biblionumber (keys %bibinfos) { - my $count=@bibitems; - @bibitems=sort @bibitems; - my $i2=1; +if ( $type eq 'str8' && $borrowernumber ne '' ) { + foreach my $biblionumber ( keys %bibinfos ) { + my $count = @bibitems; + @bibitems = sort @bibitems; + my $i2 = 1; my @realbi; - $realbi[0]=$bibitems[0]; - for (my $i=1;$i<$count;$i++) { - my $i3=$i2-1; - if ($realbi[$i3] ne $bibitems[$i]) { - $realbi[$i2]=$bibitems[$i]; + $realbi[0] = $bibitems[0]; + for ( my $i = 1 ; $i < $count ; $i++ ) { + my $i3 = $i2 - 1; + if ( $realbi[$i3] ne $bibitems[$i] ) { + $realbi[$i2] = $bibitems[$i]; $i2++; } } @@ -100,18 +99,19 @@ if ($type eq 'str8' && $borrower){ if ($multi_hold) { my $bibinfo = $bibinfos{$biblionumber}; + AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,'a',[$biblionumber], - $bibinfo->{rank},$startdate,$expirationdate,$notes,$bibinfo->{title},$checkitem,$found); + $bibinfo->{rank},$startdate,$expirationdate,$notes,$bibinfo->{title},$checkitems[0],$found); } else { - if ($input->param('request') eq 'any'){ + if ( lc($input->param('request')) eq 'any' ) { # place a request on 1st available - AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,'a',\@realbi,$rank[0],$startdate,$expirationdate,$notes,$title,$checkitem,$found); - } elsif ($reqbib[0] ne ''){ - # FIXME : elsif probably never reached, (see top of the script) - # place a request on a given item - AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,'o',\@reqbib,$rank[0],$startdate,$expirationdate,$notes,$title,$checkitem, $found); + AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,'a',\@realbi,$rank[0],$startdate,$expirationdate,$notes,$title,undef,$found); } else { - AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,'a',\@realbi,$rank[0],$startdate,$expirationdate,$notes,$title,$checkitem, $found); + for my $item (@checkitems){ + AddReserve( $branch, $borrower->{'borrowernumber'}, + $biblionumber, 'a', \@realbi, $rank[0], $startdate, $expirationdate, $notes, $title, $item, $found ); + $rank[0]++; + } } } } @@ -124,10 +124,8 @@ if ($type eq 'str8' && $borrower){ } else { print $input->redirect("request.pl?biblionumber=$biblionumber"); } -} elsif ($borrower eq ''){ - print $input->header(); - print "Invalid borrower number please try again"; -# Not sure that Dump() does HTML escaping. Use firebug or something to trace -# instead. -# print $input->Dump; +} elsif ( $borrowernumber eq '' ) { + print $input->header(); + print "Invalid card number please try again"; + print $input->Dump; } --- a/reserve/renewscript.pl +++ a/reserve/renewscript.pl @@ -28,6 +28,13 @@ use C4::Circulation; use C4::Auth; use URI::Escape; use C4::Dates qw/format_date_in_iso/; +use C4::Context; +use C4::Overdues; #CheckBorrowerDebarred +use C4::Members; #GetMemberDetail +use C4::Items; +use C4::Branch; +use JSON; +use C4::Reserves; my $input = new CGI; #Set Up User_env @@ -48,6 +55,7 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( # find items to renew, all items or a selection of items # +my $branches = GetBranches(); my @data; if ($input->param('renew_all')) { @data = $input->param('all_items[]'); @@ -80,12 +88,12 @@ my $override_limit = $input->param("override_limit") || 0; my $failedrenews = q{}; foreach my $itemno (@data) { # check status before renewing issue - my ($renewokay,$error) = CanBookBeRenewed($borrowernumber,$itemno,$override_limit); - if ($renewokay){ + my ($renewokay,$error) = CanBookBeRenewed($borrowernumber,$itemno); + if ($renewokay||$override_limit){ AddRenewal($borrowernumber,$itemno,$branch,$datedue); } else { - $failedrenews.="&failedrenew=$itemno"; + $failedrenews.="&failedrenew=$itemno&renewerror=".encode_json($error); } } my $failedreturn = q{}; @@ -93,7 +101,48 @@ foreach my $barcode (@barcodes) { # check status before renewing issue my ( $returned, $messages, $issueinformation, $borrower ) = AddReturn($barcode, $branch, $exemptfine); - $failedreturn.="&failedreturn=$barcode" unless ($returned); + my $itemnumber=GetItemnumberFromBarcode($barcode); + if ($returned){ + if (my ($reservetype,$reserve)=C4::Reserves::CheckReserves(undef,$barcode)){ + if ($reservetype eq "Waiting" || $reservetype eq "Reserved"){ + my $transfer=C4::Context->userenv->{branch} ne $reserve->{branchcode}; + ModReserveAffect( $itemnumber, $reserve->{borrowernumber}, $transfer, $reserve->{"reservenumber"} ); + my ( $message_reserve, $nextreservinfo ) = GetOtherReserves($itemnumber); + + my ($borr) = GetMemberDetails( $nextreservinfo, 0 ); + $messages->{reservesdata}={ + found => 1, + currentbranch => $branches->{C4::Context->userenv->{branch}}->{'branchname'}, + destbranchname => $branches->{ $reserve->{'branchcode'} }->{'branchname'}, + name => $borr->{'surname'} . ", " . $borr->{'title'} . " " . $borr->{'firstname'}, + borfirstname => $borr->{'firstname'}, + borsurname => $borr->{'surname'}, + bortitle => $borr->{'title'}, + borphone => $borr->{'phone'}, + boremail => $borr->{'email'}, + boraddress => $borr->{'address'}, + boraddress2 => $borr->{'address2'}, + borcity => $borr->{'city'}, + borzip => $borr->{'zipcode'}, + borcnum => $borr->{'cardnumber'}, + debarred => CheckBorrowerDebarred($borr->{borrowernumber}), + gonenoaddress => $borr->{'gonenoaddress'}, + barcode => $barcode, + transfertodo => $transfer, + destbranch => $reserve->{'branchcode'}, + reservenumber => $reserve->{'reservenumber'}, + borrowernumber => $reserve->{'borrowernumber'}, + itemnumber => $reserve->{'itemnumber'}, + biblionumber => $reserve->{'biblionumber'}, + reservenotes => $reserve->{'reservenotes'}, + }; + + } + } + } + if (!$returned ||$messages){ + $failedreturn.="&failedreturn=$barcode&returnerror=".encode_json($messages); + } } # --- a/reserve/request.pl +++ a/reserve/request.pl @@ -38,6 +38,7 @@ use C4::Biblio; use C4::Items; use C4::Koha; use C4::Circulation; +use C4::IssuingRules; use C4::Dates qw/format_date/; use C4::Members; use C4::Search; # enabled_staff_search_views @@ -92,7 +93,7 @@ my $warnings; my $messages; my $date = C4::Dates->today('iso'); -my $action = $input->param('action'); +my $action = $input->param('action') || ''; if ( $action eq 'move' ) { my $where = $input->param('where'); @@ -102,9 +103,9 @@ if ( $action eq 'move' ) { AlterPriority( $where, $borrowernumber, $biblionumber ); } elsif ( $action eq 'cancel' ) { - my $borrowernumber = $input->param('borrowernumber'); + my $reservenumber = $input->param('reservenumber'); my $biblionumber = $input->param('biblionumber'); - CancelReserve( $biblionumber, '', $borrowernumber ); + CancelReserve( $reservenumber, $biblionumber ); } elsif ( $action eq 'setLowestPriority' ) { my $borrowernumber = $input->param('borrowernumber'); my $biblionumber = $input->param('biblionumber'); @@ -138,19 +139,21 @@ if ($borrowernumber_hold) { # we check the reserves of the borrower, and if he can reserv a document # FIXME At this time we have a simple count of reservs, but, later, we could improve the infos "title" ... - my $number_reserves = - GetReserveCount( $borrowerinfo->{'borrowernumber'} ); + my $number_reserves = GetReserveCount( $borrowerinfo->{'borrowernumber'} ); - if ( C4::Context->preference('maxreserves') && ($number_reserves >= C4::Context->preference('maxreserves')) ) { - $warnings = 1; + if ( not CanBookBeReserved( $borrowerinfo->{borrowernumber}, $input->param('biblionumber') ) ) { + $warnings = 1; $maxreserves = 1; } + if ( not CanBookBeReserved( $borrowerinfo->{borrowernumber}, $input->param('biblionumber') ) ) { + $warnings = 1; + } # we check the date expiry of the borrower (only if there is an expiry date, otherwise, set to 1 (warn) my $expiry_date = $borrowerinfo->{dateexpiry}; my $expiry = 0; # flag set if patron account has expired if ($expiry_date and $expiry_date ne '0000-00-00' and - Date_to_Days(split /-/,$date) > Date_to_Days(split /-/,$expiry_date)) { + Date_to_Days(split (/-/,$date)) > Date_to_Days(split (/-/,$expiry_date))) { $messages = $expiry = 1; } @@ -224,7 +227,7 @@ my $borrowerinfo = GetMemberDetails( $borrowernumber_hold ); my @biblionumbers = (); my $biblionumbers = $input->param('biblionumbers'); if ($multihold) { - @biblionumbers = split '/', $biblionumbers; + @biblionumbers = split ('/', $biblionumbers); } else { push @biblionumbers, $input->param('biblionumber'); } @@ -236,7 +239,6 @@ foreach my $biblionumber (@biblionumbers) { my $maxreserves; my $dat = GetBiblioData($biblionumber); - unless ( CanBookBeReserved($borrowerinfo->{borrowernumber}, $biblionumber) ) { $warnings = 1; $maxreserves = 1; @@ -250,8 +252,11 @@ foreach my $biblionumber (@biblionumbers) { if ( defined $res->{found} && $res->{found} eq 'W' ) { $count--; } - - if ( defined $borrowerinfo && ($borrowerinfo->{borrowernumber} eq $res->{borrowernumber}) ) { + if ($res->{itemnumber} && not $res->{itemtype}){ + $res->{itemtype}=GetItem($res->{itemnumber},$res->{biblionumber})->{itype}; + } + next if CanHoldMultipleItems($res->{itemtype}); + if ( defined $borrowerinfo && ($borrowerinfo->{borrowernumber} eq $res->{borrowernumber}) && !CanHoldMultipleItems($res->{itemtype}) ) { $warnings = 1; $alreadyreserved = 1; $biblioloopiter{warn} = 1; @@ -419,24 +424,16 @@ foreach my $biblionumber (@biblionumbers) { } } } + my $branchitemrule = GetIssuingRule( $borrowerinfo->{categorycode}, $item->{'itype'}, GetReservesControlBranch( $borrowerinfo, $item ) ); + my $policy_holdrestricted = $branchitemrule->{'reservesallowed'}; + # prevent item to be reserved if holdrestricted & branch not the librarian branch & syspref to override not set + $item->{'holdrestricted'} = $branchitemrule->{'holdrestricted'}; - my $branch = C4::Circulation::_GetCircControlBranch($item, $borrowerinfo); - - my $branchitemrule = GetBranchItemRule( $branch, $item->{'itype'} ); - my $policy_holdallowed = 1; - - $item->{'holdallowed'} = $branchitemrule->{'holdallowed'}; - - if ( $branchitemrule->{'holdallowed'} == 0 || - ( $branchitemrule->{'holdallowed'} == 1 && - $borrowerinfo->{'branchcode'} ne $item->{'homebranch'} ) ) { - $policy_holdallowed = 0; - } - - if (IsAvailableForItemLevelRequest($itemnumber) and - not $item->{cantreserve} and - CanItemBeReserved($borrowerinfo->{borrowernumber}, $itemnumber) ) { - if ( $policy_holdallowed ) { + if (IsAvailableForItemLevelRequest($itemnumber) and not $item->{cantreserve} and CanItemBeReserved($borrowerinfo->{borrowernumber}, $itemnumber) ) { + if ( not $policy_holdrestricted and C4::Context->preference( 'AllowHoldPolicyOverride' ) ) { + $item->{override} = 1; + $num_override++; + } elsif ( $policy_holdrestricted ) { $item->{available} = 1; $num_available++; } @@ -458,10 +455,16 @@ foreach my $biblionumber (@biblionumbers) { while (my $wait_hashref = $sth2->fetchrow_hashref) { $item->{waitingdate} = format_date($wait_hashref->{waitingdate}); } + + if ( BorrowerHasReserve( $borrowerinfo->{'borrowernumber'}, '', $itemnumber ) ) { + $item->{available} = 0; + $item->{override} = 0; + } + $item->{canholdmultiple}=CanHoldMultipleItems($item->{itype}) && $item->{available}; + push @{ $biblioitem->{itemloop} }, $item; } - - if ( $num_override == scalar( @{ $biblioitem->{itemloop} } ) ) { # That is, if all items require an override + if ( $num_override > 0) { $template->param( override_required => 1 ); } elsif ( $num_available == 0 ) { $template->param( none_available => 1 ); @@ -483,6 +486,9 @@ foreach my $biblionumber (@biblionumbers) { $a_found cmp $b_found; } @$reserves ) { my %reserve; + + $reserve{'reservenumber'} = $res->{'reservenumber'}; + my @optionloop; for ( my $i = 1 ; $i <= $totalcount ; $i++ ) { push( --- a/t/db_dependent/lib/KohaTest/Circulation.pm +++ a/t/db_dependent/lib/KohaTest/Circulation.pm @@ -20,8 +20,6 @@ sub methods : Test( 1 ) { CanBookBeIssued AddIssue GetLoanLength - GetIssuingRule - GetBranchBorrowerCircRule AddReturn MarkIssueReturned _FixOverduesOnReturn --- a/t/db_dependent/lib/KohaTest/Members/GetMemberDetails.pm +++ a/t/db_dependent/lib/KohaTest/Members/GetMemberDetails.pm @@ -50,12 +50,10 @@ sub startup_create_detailed_borrower : Test( startup => 2 ) { $description = 'Test account'; $type = 'M'; $amount = 5.00; - $user = ''; + $note = ''; my $acct_added = - C4::Accounts::manualinvoice( $borrowernumber, undef, $description, $type, $amount, - $user ); - + C4::Accounts::manualinvoice( $borrowernumber, undef, $description, $type, $amount, $note ); ok( $acct_added == 0, 'added account for borrower' ); $self->{amountoutstanding} = $amount; --- a/t/db_dependent/lib/KohaTest/Overdues.pm +++ a/t/db_dependent/lib/KohaTest/Overdues.pm @@ -23,7 +23,6 @@ sub methods : Test( 1 ) { BorType ReplacementCost GetFine - GetIssuingRules ReplacementCost2 GetNextIdNotify NumberNotifyId --