1 package C4::Circulation;
3 # Copyright 2000-2002 Katipo Communications
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA 02111-1307 USA
42 use POSIX qw(strftime);
43 use C4::Branch; # GetBranches
44 use C4::Log; # logaction
48 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
51 # set the version for version checking
55 # FIXME subs that should probably be elsewhere
61 # subs to deal with issuing a book
76 &GetBranchBorrowerCircRule
78 &AnonymiseIssueHistory
81 # subs to deal with returns
88 # subs to deal with transfers
100 C4::Circulation - Koha circulation module
108 The functions in this module deal with circulation, issues, and
109 returns, as well as general information about the library.
110 Also deals with stocktaking.
116 =head3 $str = &barcodedecode($barcode);
120 =item Generic filter function for barcode string.
121 Called on every circ if the System Pref itemBarcodeInputFilter is set.
122 Will do some manipulation of the barcode for systems that deliver a barcode
123 to circulation.pl that differs from the barcode stored for the item.
124 For proper functioning of this filter, calling the function on the
125 correct barcode string (items.barcode) should return an unaltered barcode.
131 # FIXME -- the &decode fcn below should be wrapped into this one.
132 # FIXME -- these plugins should be moved out of Circulation.pm
136 my $filter = C4::Context->preference('itemBarcodeInputFilter');
137 if($filter eq 'whitespace') {
140 } elsif($filter eq 'cuecat') {
142 my @fields = split( /\./, $barcode );
143 my @results = map( decode($_), @fields[ 1 .. $#fields ] );
144 if ( $#results == 2 ) {
150 } elsif($filter eq 'T-prefix') {
151 if ( $barcode =~ /^[Tt]/) {
152 if (substr($barcode,1,1) eq '0') {
155 $barcode = substr($barcode,2) + 0 ;
158 return sprintf( "T%07d",$barcode);
164 =head3 $str = &decode($chunk);
168 =item Decodes a segment of a string emitted by a CueCat barcode scanner and
178 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-';
179 my @s = map { index( $seq, $_ ); } split( //, $encoded );
180 my $l = ( $#s + 1 ) % 4;
191 my $n = ( ( $s[0] << 6 | $s[1] ) << 6 | $s[2] ) << 6 | $s[3];
193 chr( ( $n >> 16 ) ^ 67 )
194 .chr( ( $n >> 8 & 255 ) ^ 67 )
195 .chr( ( $n & 255 ) ^ 67 );
198 $r = substr( $r, 0, length($r) - $l );
204 ($dotransfer, $messages, $iteminformation) = &transferbook($newbranch, $barcode, $ignore_reserves);
206 Transfers an item to a new branch. If the item is currently on loan, it is automatically returned before the actual transfer.
208 C<$newbranch> is the code for the branch to which the item should be transferred.
210 C<$barcode> is the barcode of the item to be transferred.
212 If C<$ignore_reserves> is true, C<&transferbook> ignores reserves.
213 Otherwise, if an item is reserved, the transfer fails.
215 Returns three values:
219 is true if the transfer was successful.
223 is a reference-to-hash which may have any of the following keys:
229 There is no item in the catalog with the given barcode. The value is C<$barcode>.
233 The item's home branch is permanent. This doesn't prevent the item from being transferred, though. The value is the code of the item's home branch.
235 =item C<DestinationEqualsHolding>
237 The item is already at the branch to which it is being transferred. The transfer is nonetheless considered to have failed. The value should be ignored.
241 The item was on loan, and C<&transferbook> automatically returned it before transferring it. The value is the borrower number of the patron who had the item.
245 The item was reserved. The value is a reference-to-hash whose keys are fields from the reserves table of the Koha database, and C<biblioitemnumber>. It also has the key C<ResFound>, whose value is either C<Waiting> or C<Reserved>.
247 =item C<WasTransferred>
249 The item was eligible to be transferred. Barring problems communicating with the database, the transfer should indeed have succeeded. The value should be ignored.
256 my ( $tbr, $barcode, $ignoreRs ) = @_;
259 my $branches = GetBranches();
260 my $itemnumber = GetItemnumberFromBarcode( $barcode );
261 my $issue = GetItemIssue($itemnumber);
262 my $biblio = GetBiblioFromItemNumber($itemnumber);
265 if ( not $itemnumber ) {
266 $messages->{'BadBarcode'} = $barcode;
270 # get branches of book...
271 my $hbr = $biblio->{'homebranch'};
272 my $fbr = $biblio->{'holdingbranch'};
275 if ( $hbr && $branches->{$hbr}->{'PE'} ) {
276 $messages->{'IsPermanent'} = $hbr;
279 # can't transfer book if is already there....
280 if ( $fbr eq $tbr ) {
281 $messages->{'DestinationEqualsHolding'} = 1;
285 # check if it is still issued to someone, return it...
286 if ($issue->{borrowernumber}) {
287 AddReturn( $barcode, $fbr );
288 $messages->{'WasReturned'} = $issue->{borrowernumber};
292 # That'll save a database query.
293 my ( $resfound, $resrec ) =
294 CheckReserves( $itemnumber );
295 if ( $resfound and not $ignoreRs ) {
296 $resrec->{'ResFound'} = $resfound;
298 # $messages->{'ResFound'} = $resrec;
302 #actually do the transfer....
304 ModItemTransfer( $itemnumber, $fbr, $tbr );
306 # don't need to update MARC anymore, we do it in batch now
307 $messages->{'WasTransfered'} = 1;
308 ModDateLastSeen( $itemnumber );
310 return ( $dotransfer, $messages, $biblio );
315 my $borrower = shift;
316 my $biblionumber = shift;
318 my $cat_borrower = $borrower->{'categorycode'};
319 my $dbh = C4::Context->dbh;
321 # Get which branchcode we need
322 if (C4::Context->preference('CircControl') eq 'PickupLibrary'){
323 $branch = C4::Context->userenv->{'branch'};
325 elsif (C4::Context->preference('CircControl') eq 'PatronLibrary'){
326 $branch = $borrower->{'branchcode'};
330 $branch = $item->{'homebranch'};
332 my $type = (C4::Context->preference('item-level_itypes'))
333 ? $item->{'itype'} # item-level
334 : $item->{'itemtype'}; # biblio-level
336 # given branch, patron category, and item type, determine
337 # applicable issuing rule
338 my $issuing_rule = GetIssuingRule($cat_borrower, $type, $branch);
340 # if a rule is found and has a loan limit set, count
341 # how many loans the patron already has that meet that
343 if (defined($issuing_rule) and defined($issuing_rule->{'maxissueqty'})) {
345 my $count_query = "SELECT COUNT(*) FROM issues
346 JOIN items USING (itemnumber) ";
348 my $rule_itemtype = $issuing_rule->{itemtype};
349 if ($rule_itemtype eq "*") {
350 # matching rule has the default item type, so count only
351 # those existing loans that don't fall under a more
353 if (C4::Context->preference('item-level_itypes')) {
354 $count_query .= " WHERE items.itype NOT IN (
355 SELECT itemtype FROM issuingrules
357 AND (categorycode = ? OR categorycode = ?)
361 $count_query .= " JOIN biblioitems USING (biblionumber)
362 WHERE biblioitems.itemtype NOT IN (
363 SELECT itemtype FROM issuingrules
365 AND (categorycode = ? OR categorycode = ?)
369 push @bind_params, $issuing_rule->{branchcode};
370 push @bind_params, $issuing_rule->{categorycode};
371 push @bind_params, $cat_borrower;
373 # rule has specific item type, so count loans of that
375 if (C4::Context->preference('item-level_itypes')) {
376 $count_query .= " WHERE items.itype = ? ";
378 $count_query .= " JOIN biblioitems USING (biblionumber)
379 WHERE biblioitems.itemtype= ? ";
381 push @bind_params, $type;
384 $count_query .= " AND borrowernumber = ? ";
385 push @bind_params, $borrower->{'borrowernumber'};
386 my $rule_branch = $issuing_rule->{branchcode};
387 if ($rule_branch ne "*") {
388 if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
389 $count_query .= " AND issues.branchcode = ? ";
390 push @bind_params, $branch;
391 } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
392 ; # if branch is the patron's home branch, then count all loans by patron
394 $count_query .= " AND items.homebranch = ? ";
395 push @bind_params, $branch;
399 my $count_sth = $dbh->prepare($count_query);
400 $count_sth->execute(@bind_params);
401 my ($current_loan_count) = $count_sth->fetchrow_array;
403 my $max_loans_allowed = $issuing_rule->{'maxissueqty'};
404 if ($current_loan_count >= $max_loans_allowed) {
405 return "$current_loan_count / $max_loans_allowed";
409 # Now count total loans against the limit for the branch
410 my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower);
411 if (defined($branch_borrower_circ_rule->{maxissueqty})) {
412 my @bind_params = ();
413 my $branch_count_query = "SELECT COUNT(*) FROM issues
414 JOIN items USING (itemnumber)
415 WHERE borrowernumber = ? ";
416 push @bind_params, $borrower->{borrowernumber};
418 if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
419 $branch_count_query .= " AND issues.branchcode = ? ";
420 push @bind_params, $branch;
421 } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
422 ; # if branch is the patron's home branch, then count all loans by patron
424 $branch_count_query .= " AND items.homebranch = ? ";
425 push @bind_params, $branch;
427 my $branch_count_sth = $dbh->prepare($branch_count_query);
428 $branch_count_sth->execute(@bind_params);
429 my ($current_loan_count) = $branch_count_sth->fetchrow_array;
431 my $max_loans_allowed = $branch_borrower_circ_rule->{maxissueqty};
432 if ($current_loan_count >= $max_loans_allowed) {
433 return "$current_loan_count / $max_loans_allowed";
437 # OK, the patron can issue !!!
443 @issues = &itemissues($biblioitemnumber, $biblio);
445 Looks up information about who has borrowed the bookZ<>(s) with the
446 given biblioitemnumber.
448 C<$biblio> is ignored.
450 C<&itemissues> returns an array of references-to-hash. The keys
451 include the fields from the C<items> table in the Koha database.
452 Additional keys include:
458 If the item is currently on loan, this gives the due date.
460 If the item is not on loan, then this is either "Available" or
461 "Cancelled", if the item has been withdrawn.
465 If the item is currently on loan, this gives the card number of the
466 patron who currently has the item.
468 =item C<timestamp0>, C<timestamp1>, C<timestamp2>
470 These give the timestamp for the last three times the item was
473 =item C<card0>, C<card1>, C<card2>
475 The card number of the last three patrons who borrowed this item.
477 =item C<borrower0>, C<borrower1>, C<borrower2>
479 The borrower number of the last three patrons who borrowed this item.
487 my ( $bibitem, $biblio ) = @_;
488 my $dbh = C4::Context->dbh;
490 $dbh->prepare("Select * from items where items.biblioitemnumber = ?")
495 $sth->execute($bibitem) || die $sth->errstr;
497 while ( my $data = $sth->fetchrow_hashref ) {
499 # Find out who currently has this item.
500 # FIXME - Wouldn't it be better to do this as a left join of
501 # some sort? Currently, this code assumes that if
502 # fetchrow_hashref() fails, then the book is on the shelf.
503 # fetchrow_hashref() can fail for any number of reasons (e.g.,
504 # database server crash), not just because no items match the
506 my $sth2 = $dbh->prepare(
507 "SELECT * FROM issues
508 LEFT JOIN borrowers ON issues.borrowernumber = borrowers.borrowernumber
513 $sth2->execute( $data->{'itemnumber'} );
514 if ( my $data2 = $sth2->fetchrow_hashref ) {
515 $data->{'date_due'} = $data2->{'date_due'};
516 $data->{'card'} = $data2->{'cardnumber'};
517 $data->{'borrower'} = $data2->{'borrowernumber'};
520 $data->{'date_due'} = ($data->{'wthdrawn'} eq '1') ? 'Cancelled' : 'Available';
525 # Find the last 3 people who borrowed this item.
526 $sth2 = $dbh->prepare(
527 "SELECT * FROM old_issues
528 LEFT JOIN borrowers ON issues.borrowernumber = borrowers.borrowernumber
530 ORDER BY returndate DESC,timestamp DESC"
533 $sth2->execute( $data->{'itemnumber'} );
534 for ( my $i2 = 0 ; $i2 < 2 ; $i2++ )
535 { # FIXME : error if there is less than 3 pple borrowing this item
536 if ( my $data2 = $sth2->fetchrow_hashref ) {
537 $data->{"timestamp$i2"} = $data2->{'timestamp'};
538 $data->{"card$i2"} = $data2->{'cardnumber'};
539 $data->{"borrower$i2"} = $data2->{'borrowernumber'};
544 $results[$i] = $data;
552 =head2 CanBookBeIssued
554 Check if a book can be issued.
556 ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $borrower, $barcode, $duedatespec, $inprocess );
558 C<$issuingimpossible> and C<$needsconfirmation> are some hashref.
562 =item C<$borrower> hash with borrower informations (from GetMemberDetails)
564 =item C<$barcode> is the bar code of the book being issued.
566 =item C<$duedatespec> is a C4::Dates object.
576 =item C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible.
577 Possible values are :
583 sticky due date is invalid
587 borrower gone with no address
591 borrower declared it's card lost
597 =head3 UNKNOWN_BARCODE
611 item is restricted (set by ??)
613 C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible.
614 Possible values are :
622 renewing, not issuing
624 =head3 ISSUED_TO_ANOTHER
626 issued to someone else.
630 reserved for someone else.
634 sticky due date is invalid
638 if the borrower borrows to much things
642 sub CanBookBeIssued {
643 my ( $borrower, $barcode, $duedate, $inprocess ) = @_;
644 my %needsconfirmation; # filled with problems that needs confirmations
645 my %issuingimpossible; # filled with problems that causes the issue to be IMPOSSIBLE
646 my $item = GetItem(GetItemnumberFromBarcode( $barcode ));
647 my $issue = GetItemIssue($item->{itemnumber});
648 my $biblioitem = GetBiblioItemData($item->{biblioitemnumber});
649 $item->{'itemtype'}=$item->{'itype'};
650 my $dbh = C4::Context->dbh;
653 # DUE DATE is OK ? -- should already have checked.
655 #$issuingimpossible{INVALID_DATE} = 1 unless ($duedate);
660 if ( $borrower->{'category_type'} eq 'X' && ( $item->{barcode} )) {
661 # stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1 .
662 &UpdateStats(C4::Context->userenv->{'branch'},'localuse','','',$item->{'itemnumber'},$item->{'itemtype'},$borrower->{'borrowernumber'});
663 return( { STATS => 1 }, {});
665 if ( $borrower->{flags}->{GNA} ) {
666 $issuingimpossible{GNA} = 1;
668 if ( $borrower->{flags}->{'LOST'} ) {
669 $issuingimpossible{CARD_LOST} = 1;
671 if ( $borrower->{flags}->{'DBARRED'} ) {
672 $issuingimpossible{DEBARRED} = 1;
674 if ( $borrower->{'dateexpiry'} eq '0000-00-00') {
675 $issuingimpossible{EXPIRED} = 1;
677 my @expirydate= split /-/,$borrower->{'dateexpiry'};
678 if($expirydate[0]==0 || $expirydate[1]==0|| $expirydate[2]==0 ||
679 Date_to_Days(Today) > Date_to_Days( @expirydate )) {
680 $issuingimpossible{EXPIRED} = 1;
689 C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->output('iso') );
690 if ( C4::Context->preference("IssuingInProcess") ) {
691 my $amountlimit = C4::Context->preference("noissuescharge");
692 if ( $amount > $amountlimit && !$inprocess ) {
693 $issuingimpossible{DEBT} = sprintf( "%.2f", $amount );
695 elsif ( $amount <= $amountlimit && !$inprocess ) {
696 $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
701 $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
706 # JB34 CHECKS IF BORROWERS DONT HAVE ISSUE TOO MANY BOOKS
708 my $toomany = TooMany( $borrower, $item->{biblionumber}, $item );
709 $needsconfirmation{TOO_MANY} = $toomany if $toomany;
714 unless ( $item->{barcode} ) {
715 $issuingimpossible{UNKNOWN_BARCODE} = 1;
717 if ( $item->{'notforloan'}
718 && $item->{'notforloan'} > 0 )
720 $issuingimpossible{NOT_FOR_LOAN} = 1;
722 elsif ( !$item->{'notforloan'} ){
723 # we have to check itemtypes.notforloan also
724 if (C4::Context->preference('item-level_itypes')){
725 # this should probably be a subroutine
726 my $sth = $dbh->prepare("SELECT notforloan FROM itemtypes WHERE itemtype = ?");
727 $sth->execute($item->{'itemtype'});
728 my $notforloan=$sth->fetchrow_hashref();
730 if ($notforloan->{'notforloan'} == 1){
731 $issuingimpossible{NOT_FOR_LOAN} = 1;
734 elsif ($biblioitem->{'notforloan'} == 1){
735 $issuingimpossible{NOT_FOR_LOAN} = 1;
738 if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} == 1 )
740 $issuingimpossible{WTHDRAWN} = 1;
742 if ( $item->{'restricted'}
743 && $item->{'restricted'} == 1 )
745 $issuingimpossible{RESTRICTED} = 1;
747 if ( C4::Context->preference("IndependantBranches") ) {
748 my $userenv = C4::Context->userenv;
749 if ( ($userenv) && ( $userenv->{flags} != 1 ) ) {
750 $issuingimpossible{NOTSAMEBRANCH} = 1
751 if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} );
756 # CHECK IF BOOK ALREADY ISSUED TO THIS BORROWER
758 if ( $issue->{borrowernumber} && $issue->{borrowernumber} eq $borrower->{'borrowernumber'} )
761 # Already issued to current borrower. Ask whether the loan should
763 my ($CanBookBeRenewed,$renewerror) = CanBookBeRenewed(
764 $borrower->{'borrowernumber'},
765 $item->{'itemnumber'}
767 if ( $CanBookBeRenewed == 0 ) { # no more renewals allowed
768 $issuingimpossible{NO_MORE_RENEWALS} = 1;
771 $needsconfirmation{RENEW_ISSUE} = 1;
774 elsif ($issue->{borrowernumber}) {
776 # issued to someone else
777 my $currborinfo = GetMemberDetails( $issue->{borrowernumber} );
779 # warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
780 $needsconfirmation{ISSUED_TO_ANOTHER} =
781 "$currborinfo->{'reservedate'} : $currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
784 # See if the item is on reserve.
785 my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
787 my $resbor = $res->{'borrowernumber'};
788 my ( $resborrower ) = GetMemberDetails( $resbor, 0 );
789 my $branches = GetBranches();
790 my $branchname = $branches->{ $res->{'branchcode'} }->{'branchname'};
791 if ( $resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting" )
793 # The item is on reserve and waiting, but has been
794 # reserved by some other patron.
795 $needsconfirmation{RESERVE_WAITING} =
796 "$resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'}, $branchname)";
798 elsif ( $restype eq "Reserved" ) {
799 # The item is on reserve for someone else.
800 $needsconfirmation{RESERVED} =
801 "$res->{'reservedate'} : $resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'})";
804 if ( C4::Context->preference("LibraryName") eq "Horowhenua Library Trust" ) {
805 if ( $borrower->{'categorycode'} eq 'W' ) {
807 return ( \%emptyhash, \%needsconfirmation );
810 return ( \%issuingimpossible, \%needsconfirmation );
815 Issue a book. Does no check, they are done in CanBookBeIssued. If we reach this sub, it means the user confirmed if needed.
817 &AddIssue($borrower,$barcode,$date)
821 =item C<$borrower> hash with borrower informations (from GetMemberDetails)
823 =item C<$barcode> is the bar code of the book being issued.
825 =item C<$date> contains the max date of return. calculated if empty.
827 =item C<$cancelreserve>
829 =item C<$issuedate> the date to issue the item in iso format (YYYY-MM-DD). Defaults to today.
831 AddIssue does the following things :
832 - step 01: check that there is a borrowernumber & a barcode provided
833 - check for RENEWAL (book issued & being issued to the same patron)
834 - renewal YES = Calculate Charge & renew
836 * BOOK ACTUALLY ISSUED ? do a return if book is actually issued (but to someone else)
838 - fill reserve if reserve to this patron
839 - cancel reserve or not, otherwise
840 * TRANSFERT PENDING ?
841 - complete the transfert
849 my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate ) = @_;
850 my $dbh = C4::Context->dbh;
851 my $barcodecheck=CheckValidBarcode($barcode);
853 # $issuedate defaults to today.
854 if ( ! defined $issuedate ) {
855 $issuedate = strftime( "%Y-%m-%d", localtime );
857 if ($borrower and $barcode and $barcodecheck ne '0'){
858 # find which item we issue
859 my $item = GetItem('', $barcode) or return undef; # if we don't get an Item, abort.
861 # Get which branchcode we need
862 if (C4::Context->preference('CircControl') eq 'PickupLibrary'){
863 $branch = C4::Context->userenv->{'branch'};
865 elsif (C4::Context->preference('CircControl') eq 'PatronLibrary'){
866 $branch = $borrower->{'branchcode'};
870 $branch = $item->{'homebranch'};
873 # get actual issuing if there is one
874 my $actualissue = GetItemIssue( $item->{itemnumber});
876 # get biblioinformation for this item
877 my $biblio = GetBiblioFromItemNumber($item->{itemnumber});
880 # check if we just renew the issue.
882 if ( $actualissue->{borrowernumber} eq $borrower->{'borrowernumber'} ) {
884 $borrower->{'borrowernumber'},
885 $item->{'itemnumber'},
894 if ( $actualissue->{borrowernumber}) {
895 # This book is currently on loan, but not to the person
896 # who wants to borrow it now. mark it returned before issuing to the new borrower
899 C4::Context->userenv->{'branch'}
903 # See if the item is on reserve.
904 my ( $restype, $res ) =
905 C4::Reserves::CheckReserves( $item->{'itemnumber'} );
907 my $resbor = $res->{'borrowernumber'};
908 if ( $resbor eq $borrower->{'borrowernumber'} ) {
910 # The item is reserved by the current patron
911 ModReserveFill($res);
913 elsif ( $restype eq "Waiting" ) {
916 # The item is on reserve and waiting, but has been
917 # reserved by some other patron.
919 elsif ( $restype eq "Reserved" ) {
922 # The item is reserved by someone else.
923 if ($cancelreserve) { # cancel reserves on this item
924 CancelReserve( 0, $res->{'itemnumber'},
925 $res->{'borrowernumber'} );
928 if ($cancelreserve) {
929 CancelReserve( $res->{'biblionumber'}, 0,
930 $res->{'borrowernumber'} );
933 # set waiting reserve to first in reserve queue as book isn't waiting now
935 $res->{'biblionumber'},
936 $res->{'borrowernumber'},
942 # Starting process for transfer job (checking transfert and validate it if we have one)
943 my ($datesent) = GetTransfers($item->{'itemnumber'});
945 # updating line of branchtranfert to finish it, and changing the to branch value, implement a comment for lisibility of this case (maybe for stats ....)
948 "UPDATE branchtransfers
949 SET datearrived = now(),
951 comments = 'Forced branchtransfer'
952 WHERE itemnumber= ? AND datearrived IS NULL"
954 $sth->execute(C4::Context->userenv->{'branch'},$item->{'itemnumber'});
958 # Record in the database the fact that the book was issued.
962 (borrowernumber, itemnumber,issuedate, date_due, branchcode)
967 $dateduef = $datedue;
969 my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'};
970 my $loanlength = GetLoanLength( $borrower->{'categorycode'}, $itype, $branch );
971 $dateduef = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $loanlength, $branch );
973 # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
974 if ( C4::Context->preference('ReturnBeforeExpiry') && $dateduef->output('iso') gt $borrower->{dateexpiry} ) {
975 $dateduef = C4::Dates->new( $borrower->{dateexpiry}, 'iso' );
979 $borrower->{'borrowernumber'}, # borrowernumber
980 $item->{'itemnumber'}, # itemnumber
981 $issuedate, # issuedate
982 $dateduef->output('iso'), # date_due
983 C4::Context->userenv->{'branch'} # branchcode
987 ModItem({ issues => $item->{'issues'},
988 holdingbranch => C4::Context->userenv->{'branch'},
990 datelastborrowed => C4::Dates->new()->output('iso'),
991 onloan => $dateduef->output('iso'),
992 }, $item->{'biblionumber'}, $item->{'itemnumber'});
993 ModDateLastSeen( $item->{'itemnumber'} );
995 # If it costs to borrow this book, charge it to the patron's account.
996 my ( $charge, $itemtype ) = GetIssuingCharges(
997 $item->{'itemnumber'},
998 $borrower->{'borrowernumber'}
1000 if ( $charge > 0 ) {
1002 $item->{'itemnumber'},
1003 $borrower->{'borrowernumber'}, $charge
1005 $item->{'charge'} = $charge;
1008 # Record the fact that this book was issued.
1010 C4::Context->userenv->{'branch'},
1012 '', $item->{'itemnumber'},
1013 $item->{'itype'}, $borrower->{'borrowernumber'}
1017 logaction("CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $biblio->{'biblionumber'})
1018 if C4::Context->preference("IssueLog");
1027 Issues an item to a member, ignoring any problems that would normally dissallow the issue.
1032 my ( $borrowernumber, $itemnumber, $date_due, $branchcode, $date ) = @_;
1033 warn "ForceIssue( $borrowernumber, $itemnumber, $date_due, $branchcode, $date );";
1034 my $dbh = C4::Context->dbh;
1035 my $sth = $dbh->prepare( "INSERT INTO `issues` ( `borrowernumber`, `itemnumber`, `date_due`, `branchcode`, `issuingbranch`, `returndate`, `lastreneweddate`, `return`, `renewals`, `timestamp`, `issuedate` )
1036 VALUES ( ?, ?, ?, ?, ?, NULL, NULL, NULL, NULL, NOW(), ? )" );
1037 $sth->execute( $borrowernumber, $itemnumber, $date_due, $branchcode, $branchcode, $date );
1040 my $item = GetBiblioFromItemNumber( $itemnumber );
1042 UpdateStats( $branchcode, 'issue', undef, undef, $itemnumber, $item->{ 'itemtype' }, $borrowernumber );
1046 =head2 GetLoanLength
1048 Get loan length for an itemtype, a borrower type and a branch
1050 my $loanlength = &GetLoanLength($borrowertype,$itemtype,branchcode)
1055 my ( $borrowertype, $itemtype, $branchcode ) = @_;
1056 my $dbh = C4::Context->dbh;
1059 "select issuelength from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null"
1061 # warn "in get loan lenght $borrowertype $itemtype $branchcode ";
1062 # try to find issuelength & return the 1st available.
1063 # check with borrowertype, itemtype and branchcode, then without one of those parameters
1064 $sth->execute( $borrowertype, $itemtype, $branchcode );
1065 my $loanlength = $sth->fetchrow_hashref;
1066 return $loanlength->{issuelength}
1067 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1069 $sth->execute( $borrowertype, "*", $branchcode );
1070 $loanlength = $sth->fetchrow_hashref;
1071 return $loanlength->{issuelength}
1072 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1074 $sth->execute( "*", $itemtype, $branchcode );
1075 $loanlength = $sth->fetchrow_hashref;
1076 return $loanlength->{issuelength}
1077 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1079 $sth->execute( "*", "*", $branchcode );
1080 $loanlength = $sth->fetchrow_hashref;
1081 return $loanlength->{issuelength}
1082 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1084 $sth->execute( $borrowertype, $itemtype, "*" );
1085 $loanlength = $sth->fetchrow_hashref;
1086 return $loanlength->{issuelength}
1087 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1089 $sth->execute( $borrowertype, "*", "*" );
1090 $loanlength = $sth->fetchrow_hashref;
1091 return $loanlength->{issuelength}
1092 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1094 $sth->execute( "*", $itemtype, "*" );
1095 $loanlength = $sth->fetchrow_hashref;
1096 return $loanlength->{issuelength}
1097 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1099 $sth->execute( "*", "*", "*" );
1100 $loanlength = $sth->fetchrow_hashref;
1101 return $loanlength->{issuelength}
1102 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1104 # if no rule is set => 21 days (hardcoded)
1108 =head2 GetIssuingRule
1110 FIXME - This is a copy-paste of GetLoanLength
1111 as a stop-gap. Do not wish to change API for GetLoanLength
1112 this close to release, however, Overdues::GetIssuingRules is broken.
1114 Get the issuing rule for an itemtype, a borrower type and a branch
1115 Returns a hashref from the issuingrules table.
1117 my $irule = &GetIssuingRule($borrowertype,$itemtype,branchcode)
1121 sub GetIssuingRule {
1122 my ( $borrowertype, $itemtype, $branchcode ) = @_;
1123 my $dbh = C4::Context->dbh;
1124 my $sth = $dbh->prepare( "select * from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null" );
1127 $sth->execute( $borrowertype, $itemtype, $branchcode );
1128 $irule = $sth->fetchrow_hashref;
1129 return $irule if defined($irule) ;
1131 $sth->execute( $borrowertype, "*", $branchcode );
1132 $irule = $sth->fetchrow_hashref;
1133 return $irule if defined($irule) ;
1135 $sth->execute( "*", $itemtype, $branchcode );
1136 $irule = $sth->fetchrow_hashref;
1137 return $irule if defined($irule) ;
1139 $sth->execute( "*", "*", $branchcode );
1140 $irule = $sth->fetchrow_hashref;
1141 return $irule if defined($irule) ;
1143 $sth->execute( $borrowertype, $itemtype, "*" );
1144 $irule = $sth->fetchrow_hashref;
1145 return $irule if defined($irule) ;
1147 $sth->execute( $borrowertype, "*", "*" );
1148 $irule = $sth->fetchrow_hashref;
1149 return $irule if defined($irule) ;
1151 $sth->execute( "*", $itemtype, "*" );
1152 $irule = $sth->fetchrow_hashref;
1153 return $irule if defined($irule) ;
1155 $sth->execute( "*", "*", "*" );
1156 $irule = $sth->fetchrow_hashref;
1157 return $irule if defined($irule) ;
1159 # if no rule matches,
1163 =head2 GetBranchBorrowerCircRule
1167 my $branch_cat_rule = GetBranchBorrowerCircRule($branchcode, $categorycode);
1171 Retrieves circulation rule attributes that apply to the given
1172 branch and patron category, regardless of item type.
1173 The return value is a hashref containing the following key:
1175 maxissueqty - maximum number of loans that a
1176 patron of the given category can have at the given
1177 branch. If the value is undef, no limit.
1179 This will first check for a specific branch and
1180 category match from branch_borrower_circ_rules.
1182 If no rule is found, it will then check default_branch_circ_rules
1183 (same branch, default category). If no rule is found,
1184 it will then check default_borrower_circ_rules (default
1185 branch, same category), then failing that, default_circ_rules
1186 (default branch, default category).
1188 If no rule has been found in the database, it will default to
1193 C<$branchcode> and C<$categorycode> should contain the
1194 literal branch code and patron category code, respectively - no
1199 sub GetBranchBorrowerCircRule {
1200 my $branchcode = shift;
1201 my $categorycode = shift;
1203 my $branch_cat_query = "SELECT maxissueqty
1204 FROM branch_borrower_circ_rules
1205 WHERE branchcode = ?
1206 AND categorycode = ?";
1207 my $dbh = C4::Context->dbh();
1208 my $sth = $dbh->prepare($branch_cat_query);
1209 $sth->execute($branchcode, $categorycode);
1211 if ($result = $sth->fetchrow_hashref()) {
1215 # try same branch, default borrower category
1216 my $branch_query = "SELECT maxissueqty
1217 FROM default_branch_circ_rules
1218 WHERE branchcode = ?";
1219 $sth = $dbh->prepare($branch_query);
1220 $sth->execute($branchcode);
1221 if ($result = $sth->fetchrow_hashref()) {
1225 # try default branch, same borrower category
1226 my $category_query = "SELECT maxissueqty
1227 FROM default_borrower_circ_rules
1228 WHERE categorycode = ?";
1229 $sth = $dbh->prepare($category_query);
1230 $sth->execute($categorycode);
1231 if ($result = $sth->fetchrow_hashref()) {
1235 # try default branch, default borrower category
1236 my $default_query = "SELECT maxissueqty
1237 FROM default_circ_rules";
1238 $sth = $dbh->prepare($default_query);
1240 if ($result = $sth->fetchrow_hashref()) {
1244 # built-in default circulation rule
1246 maxissueqty => undef,
1252 ($doreturn, $messages, $iteminformation, $borrower) =
1253 &AddReturn($barcode, $branch, $exemptfine, $dropbox);
1259 =item C<$barcode> is the bar code of the book being returned.
1261 =item C<$branch> is the code of the branch where the book is being returned.
1263 =item C<$exemptfine> indicates that overdue charges for the item will be
1266 =item C<$dropbox> indicates that the check-in date is assumed to be
1267 yesterday, or the last non-holiday as defined in C4::Calendar . If
1268 overdue charges are applied and C<$dropbox> is true, the last charge
1269 will be removed. This assumes that the fines accrual script has run
1274 C<&AddReturn> returns a list of four items:
1276 C<$doreturn> is true iff the return succeeded.
1278 C<$messages> is a reference-to-hash giving the reason for failure:
1284 No item with this barcode exists. The value is C<$barcode>.
1288 The book is not currently on loan. The value is C<$barcode>.
1290 =item C<IsPermanent>
1292 The book's home branch is a permanent collection. If you have borrowed
1293 this book, you are not allowed to return it. The value is the code for
1294 the book's home branch.
1298 This book has been withdrawn/cancelled. The value should be ignored.
1302 The item was reserved. The value is a reference-to-hash whose keys are
1303 fields from the reserves table of the Koha database, and
1304 C<biblioitemnumber>. It also has the key C<ResFound>, whose value is
1305 either C<Waiting>, C<Reserved>, or 0.
1309 C<$borrower> is a reference-to-hash, giving information about the
1310 patron who last borrowed the book.
1315 my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
1316 my $dbh = C4::Context->dbh;
1320 my $validTransfert = 0;
1321 my $reserveDone = 0;
1323 # get information on item
1324 my $iteminformation = GetItemIssue( GetItemnumberFromBarcode($barcode));
1325 my $biblio = GetBiblioItemData($iteminformation->{'biblioitemnumber'});
1326 # use Data::Dumper;warn Data::Dumper::Dumper($iteminformation);
1327 unless ($iteminformation->{'itemnumber'} ) {
1328 $messages->{'BadBarcode'} = $barcode;
1332 if ( ( not $iteminformation->{borrowernumber} ) && $doreturn ) {
1333 $messages->{'NotIssued'} = $barcode;
1334 # even though item is not on loan, it may still
1335 # be transferred; therefore, get current branch information
1336 my $curr_iteminfo = GetItem($iteminformation->{'itemnumber'});
1337 $iteminformation->{'homebranch'} = $curr_iteminfo->{'homebranch'};
1338 $iteminformation->{'holdingbranch'} = $curr_iteminfo->{'holdingbranch'};
1342 # check if the book is in a permanent collection....
1343 my $hbr = $iteminformation->{C4::Context->preference("HomeOrHoldingBranch")};
1344 my $branches = GetBranches();
1345 # FIXME -- This 'PE' attribute is largely undocumented. afaict, there's no user interface that reflects this functionality.
1346 if ( $hbr && $branches->{$hbr}->{'PE'} ) {
1347 $messages->{'IsPermanent'} = $hbr;
1350 # if independent branches are on and returning to different branch, refuse the return
1351 if ($hbr ne C4::Context->userenv->{'branch'} && C4::Context->preference("IndependantBranches")){
1352 $messages->{'Wrongbranch'} = 1;
1356 # check that the book has been cancelled
1357 if ( $iteminformation->{'wthdrawn'} ) {
1358 $messages->{'wthdrawn'} = 1;
1362 # new op dev : if the book returned in an other branch update the holding branch
1364 # update issues, thereby returning book (should push this out into another subroutine
1365 $borrower = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
1367 # case of a return of document (deal with issues and holdingbranch)
1370 my $circControlBranch;
1372 # don't allow dropbox mode to create an invalid entry in issues (issuedate > returndate) FIXME: actually checks eq, not gt
1373 undef($dropbox) if ( $iteminformation->{'issuedate'} eq C4::Dates->today('iso') );
1374 if (C4::Context->preference('CircControl') eq 'ItemHomeBranch' ) {
1375 $circControlBranch = $iteminformation->{homebranch};
1376 } elsif ( C4::Context->preference('CircControl') eq 'PatronLibrary') {
1377 $circControlBranch = $borrower->{branchcode};
1378 } else { # CircControl must be PickupLibrary.
1379 $circControlBranch = $iteminformation->{holdingbranch};
1380 # FIXME - is this right ? are we sure that the holdingbranch is still the pickup branch?
1383 MarkIssueReturned($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'},$circControlBranch);
1384 $messages->{'WasReturned'} = 1; # FIXME is the "= 1" right?
1387 # continue to deal with returns cases, but not only if we have an issue
1389 # the holdingbranch is updated if the document is returned in an other location .
1390 if ( $iteminformation->{'holdingbranch'} ne C4::Context->userenv->{'branch'} ) {
1391 UpdateHoldingbranch(C4::Context->userenv->{'branch'},$iteminformation->{'itemnumber'});
1392 # reload iteminformation holdingbranch with the userenv value
1393 $iteminformation->{'holdingbranch'} = C4::Context->userenv->{'branch'};
1395 ModDateLastSeen( $iteminformation->{'itemnumber'} );
1396 ModItem({ onloan => undef }, $biblio->{'biblionumber'}, $iteminformation->{'itemnumber'});
1398 if ($iteminformation->{borrowernumber}){
1399 ($borrower) = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
1401 # fix up the accounts.....
1402 if ( $iteminformation->{'itemlost'} ) {
1403 $messages->{'WasLost'} = 1;
1406 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
1407 # check if we have a transfer for this document
1408 my ($datesent,$frombranch,$tobranch) = GetTransfers( $iteminformation->{'itemnumber'} );
1410 # if we have a transfer to do, we update the line of transfers with the datearrived
1412 if ( $tobranch eq C4::Context->userenv->{'branch'} ) {
1415 "UPDATE branchtransfers SET datearrived = now() WHERE itemnumber= ? AND datearrived IS NULL"
1417 $sth->execute( $iteminformation->{'itemnumber'} );
1419 # now we check if there is a reservation with the validate of transfer if we have one, we can set it with the status 'W'
1420 C4::Reserves::ModReserveStatus( $iteminformation->{'itemnumber'},'W' );
1423 $messages->{'WrongTransfer'} = $tobranch;
1424 $messages->{'WrongTransferItem'} = $iteminformation->{'itemnumber'};
1426 $validTransfert = 1;
1429 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
1430 # fix up the accounts.....
1431 if ($iteminformation->{'itemlost'}) {
1432 FixAccountForLostAndReturned($iteminformation, $borrower);
1433 $messages->{'WasLost'} = 1;
1435 # fix up the overdues in accounts...
1436 FixOverduesOnReturn( $borrower->{'borrowernumber'},
1437 $iteminformation->{'itemnumber'}, $exemptfine, $dropbox );
1439 # find reserves.....
1440 # if we don't have a reserve with the status W, we launch the Checkreserves routine
1441 my ( $resfound, $resrec ) =
1442 C4::Reserves::CheckReserves( $iteminformation->{'itemnumber'} );
1444 $resrec->{'ResFound'} = $resfound;
1445 $messages->{'ResFound'} = $resrec;
1450 # Record the fact that this book was returned.
1452 $branch, 'return', '0', '',
1453 $iteminformation->{'itemnumber'},
1454 $biblio->{'itemtype'},
1455 $borrower->{'borrowernumber'}
1458 logaction("CIRCULATION", "RETURN", $iteminformation->{borrowernumber}, $iteminformation->{'biblionumber'})
1459 if C4::Context->preference("ReturnLog");
1461 #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
1462 #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
1464 if ( ($iteminformation->{'holdingbranch'} ne $iteminformation->{'homebranch'}) and not $messages->{'WrongTransfer'} and ($validTransfert ne 1) and ($reserveDone ne 1) ){
1465 if (C4::Context->preference("AutomaticItemReturn") == 1) {
1466 ModItemTransfer($iteminformation->{'itemnumber'}, C4::Context->userenv->{'branch'}, $iteminformation->{'homebranch'});
1467 $messages->{'WasTransfered'} = 1;
1470 $messages->{'NeedsTransfer'} = 1;
1474 return ( $doreturn, $messages, $iteminformation, $borrower );
1479 ForceReturn( $barcode, $date, $branchcode );
1481 Returns an item is if it were returned on C<$date>.
1483 This function is non-interactive and does not check for reserves.
1485 C<$barcode> is the barcode of the item being returned.
1487 C<$date> is the date of the actual return, in the format YYYY-MM-DD.
1489 C<$branchcode> is the branchcode for the library the item was returned to.
1494 my ( $barcode, $date, $branchcode ) = @_;
1495 my $dbh = C4::Context->dbh;
1497 my $item = GetBiblioFromItemNumber( undef, $barcode );
1499 ## FIXME: Is there a way to get the borrower of an item through the Koha API?
1500 my $sth=$dbh->prepare( "SELECT borrowernumber FROM issues WHERE itemnumber = ? AND returndate IS NULL");
1501 $sth->execute( $item->{'itemnumber'} );
1502 my ( $borrowernumber ) = $sth->fetchrow;
1505 ## Move the issue from issues to old_issues
1506 $sth = $dbh->prepare( "INSERT INTO old_issues ( SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL )" );
1507 $sth->execute( $item->{'itemnumber'} );
1509 ## Delete the row in issues
1510 $sth = $dbh->prepare( "DELETE FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
1511 $sth->execute( $item->{'itemnumber'} );
1513 ## Now set the returndate
1514 $sth = $dbh->prepare( 'UPDATE old_issues SET returndate = ? WHERE itemnumber = ? AND returndate IS NULL' );
1515 $sth->execute( $date, $item->{'itemnumber'} );
1518 UpdateStats( $branchcode, 'return', my $amount, my $other, $item->{ 'itemnumber' }, $item->{ 'itemtype' }, $borrowernumber );
1522 =head2 MarkIssueReturned
1526 MarkIssueReturned($borrowernumber, $itemnumber, $dropbox_branch, $returndate);
1530 Unconditionally marks an issue as being returned by
1531 moving the C<issues> row to C<old_issues> and
1532 setting C<returndate> to the current date, or
1533 the last non-holiday date of the branccode specified in
1534 C<dropbox_branch> . Assumes you've already checked that
1535 it's safe to do this, i.e. last non-holiday > issuedate.
1537 if C<$returndate> is specified (in iso format), it is used as the date
1538 of the return. It is ignored when a dropbox_branch is passed in.
1540 Ideally, this function would be internal to C<C4::Circulation>,
1541 not exported, but it is currently needed by one
1542 routine in C<C4::Accounts>.
1546 sub MarkIssueReturned {
1547 my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate ) = @_;
1548 my $dbh = C4::Context->dbh;
1549 my $query = "UPDATE issues SET returndate=";
1551 if ($dropbox_branch) {
1552 my $calendar = C4::Calendar->new( branchcode => $dropbox_branch );
1553 my $dropboxdate = $calendar->addDate( C4::Dates->new(), -1 );
1555 push @bind, $dropboxdate->output('iso');
1556 } elsif ($returndate) {
1558 push @bind, $returndate;
1560 $query .= " now() ";
1562 $query .= " WHERE borrowernumber = ? AND itemnumber = ?";
1563 push @bind, $borrowernumber, $itemnumber;
1565 my $sth_upd = $dbh->prepare($query);
1566 $sth_upd->execute(@bind);
1567 my $sth_copy = $dbh->prepare("INSERT INTO old_issues SELECT * FROM issues
1568 WHERE borrowernumber = ?
1569 AND itemnumber = ?");
1570 $sth_copy->execute($borrowernumber, $itemnumber);
1571 my $sth_del = $dbh->prepare("DELETE FROM issues
1572 WHERE borrowernumber = ?
1573 AND itemnumber = ?");
1574 $sth_del->execute($borrowernumber, $itemnumber);
1577 =head2 FixOverduesOnReturn
1579 &FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode);
1581 C<$brn> borrowernumber
1585 C<$exemptfine> BOOL -- remove overdue charge associated with this issue.
1586 C<$dropboxmode> BOOL -- remove lastincrement on overdue charge associated with this issue.
1588 internal function, called only by AddReturn
1592 sub FixOverduesOnReturn {
1593 my ( $borrowernumber, $item, $exemptfine, $dropbox ) = @_;
1594 my $dbh = C4::Context->dbh;
1596 # check for overdue fine
1599 "SELECT * FROM accountlines WHERE (borrowernumber = ?) AND (itemnumber = ?) AND (accounttype='FU' OR accounttype='O')"
1601 $sth->execute( $borrowernumber, $item );
1603 # alter fine to show that the book has been returned
1605 if ($data = $sth->fetchrow_hashref) {
1607 my @bind = ($borrowernumber,$item ,$data->{'accountno'});
1609 $uquery = "update accountlines set accounttype='FFOR', amountoutstanding=0";
1610 if (C4::Context->preference("FinesLog")) {
1611 &logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item");
1613 } elsif ($dropbox && $data->{lastincrement}) {
1614 my $outstanding = $data->{amountoutstanding} - $data->{lastincrement} ;
1615 my $amt = $data->{amount} - $data->{lastincrement} ;
1616 if (C4::Context->preference("FinesLog")) {
1617 &logaction("FINES", 'MODIFY',$borrowernumber,"Dropbox adjustment $amt, item $item");
1619 $uquery = "update accountlines set accounttype='F' ";
1620 if($outstanding >= 0 && $amt >=0) {
1621 $uquery .= ", amount = ? , amountoutstanding=? ";
1622 unshift @bind, ($amt, $outstanding) ;
1625 $uquery = "update accountlines set accounttype='F' ";
1627 $uquery .= " where (borrowernumber = ?) and (itemnumber = ?) and (accountno = ?)";
1628 my $usth = $dbh->prepare($uquery);
1629 $usth->execute(@bind);
1637 =head2 FixAccountForLostAndReturned
1639 &FixAccountForLostAndReturned($iteminfo,$borrower);
1641 Calculates the charge for a book lost and returned (Not exported & used only once)
1643 C<$iteminfo> is a hashref to iteminfo. Only {itemnumber} is used.
1645 C<$borrower> is a hashref to borrower. Only {borrowernumber is used.
1647 Internal function, called by AddReturn
1651 sub FixAccountForLostAndReturned {
1652 my ($iteminfo, $borrower) = @_;
1653 my $dbh = C4::Context->dbh;
1654 my $itm = $iteminfo->{'itemnumber'};
1655 # check for charge made for lost book
1656 my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE (itemnumber = ?) AND (accounttype='L' OR accounttype='Rep') ORDER BY date DESC");
1657 $sth->execute($itm);
1658 if (my $data = $sth->fetchrow_hashref) {
1659 # writeoff this amount
1661 my $amount = $data->{'amount'};
1662 my $acctno = $data->{'accountno'};
1664 if ($data->{'amountoutstanding'} == $amount) {
1665 $offset = $data->{'amount'};
1668 $offset = $amount - $data->{'amountoutstanding'};
1669 $amountleft = $data->{'amountoutstanding'} - $amount;
1671 my $usth = $dbh->prepare("UPDATE accountlines SET accounttype = 'LR',amountoutstanding='0'
1672 WHERE (borrowernumber = ?)
1673 AND (itemnumber = ?) AND (accountno = ?) ");
1674 $usth->execute($data->{'borrowernumber'},$itm,$acctno);
1676 #check if any credit is left if so writeoff other accounts
1677 my $nextaccntno = getnextacctno($data->{'borrowernumber'});
1678 if ($amountleft < 0){
1681 if ($amountleft > 0){
1682 my $msth = $dbh->prepare("SELECT * FROM accountlines WHERE (borrowernumber = ?)
1683 AND (amountoutstanding >0) ORDER BY date");
1684 $msth->execute($data->{'borrowernumber'});
1685 # offset transactions
1688 while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){
1689 if ($accdata->{'amountoutstanding'} < $amountleft) {
1691 $amountleft -= $accdata->{'amountoutstanding'};
1693 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
1696 my $thisacct = $accdata->{'accountno'};
1697 my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
1698 WHERE (borrowernumber = ?)
1699 AND (accountno=?)");
1700 $usth->execute($newamtos,$data->{'borrowernumber'},'$thisacct');
1702 $usth = $dbh->prepare("INSERT INTO accountoffsets
1703 (borrowernumber, accountno, offsetaccount, offsetamount)
1706 $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
1711 if ($amountleft > 0){
1714 my $desc="Item Returned ".$iteminfo->{'barcode'};
1715 $usth = $dbh->prepare("INSERT INTO accountlines
1716 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
1717 VALUES (?,?,now(),?,?,'CR',?)");
1718 $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft);
1720 $usth = $dbh->prepare("INSERT INTO accountoffsets
1721 (borrowernumber, accountno, offsetaccount, offsetamount)
1723 $usth->execute($borrower->{'borrowernumber'},$data->{'accountno'},$nextaccntno,$offset);
1725 ModItem({ paidfor => '' }, undef, $itm);
1733 $issues = &GetItemIssue($itemnumber);
1735 Returns patrons currently having a book. nothing if item is not issued atm
1737 C<$itemnumber> is the itemnumber
1739 Returns an array of hashes
1741 FIXME: Though the above says that this function returns nothing if the
1742 item is not issued, this actually returns a hasref that looks like
1753 my ( $itemnumber) = @_;
1754 return unless $itemnumber;
1755 my $dbh = C4::Context->dbh;
1759 my $today = POSIX::strftime("%Y%m%d", localtime);
1761 my $sth = $dbh->prepare(
1762 "SELECT * FROM issues
1763 LEFT JOIN items ON issues.itemnumber=items.itemnumber
1765 issues.itemnumber=?");
1766 $sth->execute($itemnumber);
1767 my $data = $sth->fetchrow_hashref;
1768 my $datedue = $data->{'date_due'};
1770 if ( $datedue < $today ) {
1771 $data->{'overdue'} = 1;
1773 $data->{'itemnumber'} = $itemnumber; # fill itemnumber, in case item is not on issue
1780 $issue = GetOpenIssue( $itemnumber );
1782 Returns the row from the issues table if the item is currently issued, undef if the item is not currently issued
1784 C<$itemnumber> is the item's itemnumber
1791 my ( $itemnumber ) = @_;
1793 my $dbh = C4::Context->dbh;
1794 my $sth = $dbh->prepare( "SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
1795 $sth->execute( $itemnumber );
1796 my $issue = $sth->fetchrow_hashref();
1800 =head2 GetItemIssues
1802 $issues = &GetItemIssues($itemnumber, $history);
1804 Returns patrons that have issued a book
1806 C<$itemnumber> is the itemnumber
1807 C<$history> is 0 if you want actuel "issuer" (if it exist) and 1 if you want issues history
1809 Returns an array of hashes
1814 my ( $itemnumber,$history ) = @_;
1815 my $dbh = C4::Context->dbh;
1819 my $today = POSIX::strftime("%Y%m%d", localtime);
1821 my $sql = "SELECT * FROM issues
1822 JOIN borrowers USING (borrowernumber)
1823 JOIN items USING (itemnumber)
1824 WHERE issues.itemnumber = ? ";
1827 SELECT * FROM old_issues
1828 LEFT JOIN borrowers USING (borrowernumber)
1829 JOIN items USING (itemnumber)
1830 WHERE old_issues.itemnumber = ? ";
1832 $sql .= "ORDER BY date_due DESC";
1833 my $sth = $dbh->prepare($sql);
1835 $sth->execute($itemnumber, $itemnumber);
1837 $sth->execute($itemnumber);
1839 while ( my $data = $sth->fetchrow_hashref ) {
1840 my $datedue = $data->{'date_due'};
1842 if ( $datedue < $today ) {
1843 $data->{'overdue'} = 1;
1845 my $itemnumber = $data->{'itemnumber'};
1846 push @GetItemIssues, $data;
1849 return ( \@GetItemIssues );
1852 =head2 GetBiblioIssues
1854 $issues = GetBiblioIssues($biblionumber);
1856 this function get all issues from a biblionumber.
1859 C<$issues> is a reference to array which each value is ref-to-hash. This ref-to-hash containts all column from
1860 tables issues and the firstname,surname & cardnumber from borrowers.
1864 sub GetBiblioIssues {
1865 my $biblionumber = shift;
1866 return undef unless $biblionumber;
1867 my $dbh = C4::Context->dbh;
1869 SELECT issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
1871 LEFT JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
1872 LEFT JOIN items ON issues.itemnumber = items.itemnumber
1873 LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
1874 LEFT JOIN biblio ON biblio.biblionumber = items.biblioitemnumber
1875 WHERE biblio.biblionumber = ?
1877 SELECT old_issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
1879 LEFT JOIN borrowers ON borrowers.borrowernumber = old_issues.borrowernumber
1880 LEFT JOIN items ON old_issues.itemnumber = items.itemnumber
1881 LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
1882 LEFT JOIN biblio ON biblio.biblionumber = items.biblioitemnumber
1883 WHERE biblio.biblionumber = ?
1886 my $sth = $dbh->prepare($query);
1887 $sth->execute($biblionumber, $biblionumber);
1890 while ( my $data = $sth->fetchrow_hashref ) {
1891 push @issues, $data;
1896 =head2 GetUpcomingDueIssues
1900 my $upcoming_dues = GetUpcomingDueIssues( { days_in_advance => 4 } );
1906 sub GetUpcomingDueIssues {
1909 $params->{'days_in_advance'} = 7 unless exists $params->{'days_in_advance'};
1910 my $dbh = C4::Context->dbh;
1912 my $statement = <<END_SQL;
1913 SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-TO_DAYS( NOW() ) as days_until_due
1915 LEFT JOIN items USING (itemnumber)
1916 WhERE returndate is NULL
1917 AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
1920 my @bind_parameters = ( $params->{'days_in_advance'} );
1922 my $sth = $dbh->prepare( $statement );
1923 $sth->execute( @bind_parameters );
1924 my $upcoming_dues = $sth->fetchall_arrayref({});
1927 return $upcoming_dues;
1930 =head2 CanBookBeRenewed
1932 ($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber);
1934 Find out whether a borrowed item may be renewed.
1936 C<$dbh> is a DBI handle to the Koha database.
1938 C<$borrowernumber> is the borrower number of the patron who currently
1939 has the item on loan.
1941 C<$itemnumber> is the number of the item to renew.
1943 C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
1944 item must currently be on loan to the specified borrower; renewals
1945 must be allowed for the item's type; and the borrower must not have
1946 already renewed the loan. $error will contain the reason the renewal can not proceed
1950 sub CanBookBeRenewed {
1952 # check renewal status
1953 my ( $borrowernumber, $itemnumber ) = @_;
1954 my $dbh = C4::Context->dbh;
1959 # Look in the issues table for this item, lent to this borrower,
1960 # and not yet returned.
1962 # FIXME - I think this function could be redone to use only one SQL call.
1963 my $sth1 = $dbh->prepare(
1964 "SELECT * FROM issues
1965 WHERE borrowernumber = ?
1968 $sth1->execute( $borrowernumber, $itemnumber );
1969 if ( my $data1 = $sth1->fetchrow_hashref ) {
1971 # Found a matching item
1973 # See if this item may be renewed. This query is convoluted
1974 # because it's a bit messy: given the item number, we need to find
1975 # the biblioitem, which gives us the itemtype, which tells us
1976 # whether it may be renewed.
1977 my $query = "SELECT renewalsallowed FROM items ";
1978 $query .= (C4::Context->preference('item-level_itypes'))
1979 ? "LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
1980 : "LEFT JOIN biblioitems on items.biblioitemnumber = biblioitems.biblioitemnumber
1981 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
1982 $query .= "WHERE items.itemnumber = ?";
1983 my $sth2 = $dbh->prepare($query);
1984 $sth2->execute($itemnumber);
1985 if ( my $data2 = $sth2->fetchrow_hashref ) {
1986 $renews = $data2->{'renewalsallowed'};
1988 if ( $renews && $renews > $data1->{'renewals'} ) {
1995 my ( $resfound, $resrec ) = C4::Reserves::CheckReserves($itemnumber);
2003 return ($renewokay,$error);
2008 &AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$issuedate]);
2012 C<$borrowernumber> is the borrower number of the patron who currently
2015 C<$itemnumber> is the number of the item to renew.
2017 C<$branch> is the library branch. Defaults to the homebranch of the ITEM.
2019 C<$datedue> can be a C4::Dates object used to set the due date.
2021 C<$issuedate> can be a iso formatted date to use for the issuedate.
2023 If C<$datedue> is the empty string, C<&AddRenewal> will calculate the due date automatically
2024 from the book's item type.
2029 my $borrowernumber = shift or return undef;
2030 my $itemnumber = shift or return undef;
2031 my $item = GetItem($itemnumber) or return undef;
2032 my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
2033 my $branch = (@_) ? shift : $item->{homebranch}; # opac-renew doesn't send branch
2034 my $datedue = shift;
2035 my $issuedate = shift;
2036 # If the due date wasn't specified, calculate it by adding the
2037 # book's loan length to today's date.
2038 unless ($datedue && $datedue->output('iso')) {
2040 my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef;
2041 my $loanlength = GetLoanLength(
2042 $borrower->{'categorycode'},
2043 (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'} ,
2044 $item->{homebranch} # item's homebranch determines loanlength OR do we want the branch specified by the AddRenewal argument?
2046 #FIXME -- use circControl?
2047 $datedue = CalcDateDue(C4::Dates->new(),$loanlength,$branch); # this branch is the transactional branch.
2048 # The question of whether to use item's homebranch calendar is open.
2051 my $dbh = C4::Context->dbh;
2052 # Find the issues record for this book
2054 $dbh->prepare("SELECT * FROM issues
2055 WHERE borrowernumber=?
2058 $sth->execute( $borrowernumber, $itemnumber );
2059 my $issuedata = $sth->fetchrow_hashref;
2062 # Update the issues record to have the new due date, and a new count
2063 # of how many times it has been renewed.
2064 my $renews = $issuedata->{'renewals'} + 1;
2065 $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = CURRENT_DATE, issuedate = ?
2066 WHERE borrowernumber=?
2069 $sth->execute( $datedue->output('iso'), $renews, $issuedate, $borrowernumber, $itemnumber );
2072 # Update the renewal count on the item, and tell zebra to reindex
2073 $renews = $biblio->{'renewals'} + 1;
2074 ModItem({ renewals => $renews }, $biblio->{'biblionumber'}, $itemnumber);
2076 # Charge a new rental fee, if applicable?
2077 my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
2078 if ( $charge > 0 ) {
2079 my $accountno = getnextacctno( $borrowernumber );
2080 my $item = GetBiblioFromItemNumber($itemnumber);
2081 $sth = $dbh->prepare(
2082 "INSERT INTO accountlines
2084 borrowernumber, accountno, amount,
2086 accounttype, amountoutstanding, itemnumber
2088 VALUES (now(),?,?,?,?,?,?,?)"
2090 $sth->execute( $borrowernumber, $accountno, $charge,
2091 "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
2092 'Rent', $charge, $itemnumber );
2096 UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
2102 ForRenewal( $itemnumber, $date, $date_due );
2104 Renews an item for the given date. This function should only be used to update renewals that have occurred in the past.
2106 C<$itemnumber> is the itemnumber of the item being renewed.
2108 C<$date> is the date the renewal took place, in the format YYYY-MM-DD
2110 C<$date_due> is the date the item is now due to be returned, in the format YYYY-MM-DD
2115 my ( $itemnumber, $date, $date_due ) = @_;
2116 my $dbh = C4::Context->dbh;
2118 my $sth = $dbh->prepare("SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL");
2119 $sth->execute( $itemnumber );
2120 my $issue = $sth->fetchrow_hashref();
2124 $sth = $dbh->prepare('UPDATE issues SET renewals = ?, lastreneweddate = ?, date_due = ? WHERE itemnumber = ? AND returndate IS NULL');
2125 $sth->execute( $issue->{'renewals'} + 1, $date, $date_due, $itemnumber );
2128 my $item = GetBiblioFromItemNumber( $itemnumber );
2129 UpdateStats( $issue->{'branchcode'}, 'renew', undef, undef, $itemnumber, $item->{ 'itemtype' }, $issue->{'borrowernumber'} );
2134 # check renewal status
2135 my ($bornum,$itemno)=@_;
2136 my $dbh = C4::Context->dbh;
2138 my $renewsallowed = 0;
2140 # Look in the issues table for this item, lent to this borrower,
2141 # and not yet returned.
2143 # FIXME - I think this function could be redone to use only one SQL call.
2144 my $sth = $dbh->prepare("select * from issues
2145 where (borrowernumber = ?)
2146 and (itemnumber = ?)");
2147 $sth->execute($bornum,$itemno);
2148 my $data = $sth->fetchrow_hashref;
2149 $renewcount = $data->{'renewals'} if $data->{'renewals'};
2151 my $query = "SELECT renewalsallowed FROM items ";
2152 $query .= (C4::Context->preference('item-level_itypes'))
2153 ? "LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
2154 : "LEFT JOIN biblioitems on items.biblioitemnumber = biblioitems.biblioitemnumber
2155 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
2156 $query .= "WHERE items.itemnumber = ?";
2157 my $sth2 = $dbh->prepare($query);
2158 $sth2->execute($itemno);
2159 my $data2 = $sth2->fetchrow_hashref();
2160 $renewsallowed = $data2->{'renewalsallowed'};
2161 $renewsleft = $renewsallowed - $renewcount;
2162 return ($renewcount,$renewsallowed,$renewsleft);
2165 =head2 GetIssuingCharges
2167 ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
2169 Calculate how much it would cost for a given patron to borrow a given
2170 item, including any applicable discounts.
2172 C<$itemnumber> is the item number of item the patron wishes to borrow.
2174 C<$borrowernumber> is the patron's borrower number.
2176 C<&GetIssuingCharges> returns two values: C<$charge> is the rental charge,
2177 and C<$item_type> is the code for the item's item type (e.g., C<VID>
2182 sub GetIssuingCharges {
2184 # calculate charges due
2185 my ( $itemnumber, $borrowernumber ) = @_;
2187 my $dbh = C4::Context->dbh;
2190 # Get the book's item type and rental charge (via its biblioitem).
2191 my $qcharge = "SELECT itemtypes.itemtype,rentalcharge FROM items
2192 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber";
2193 $qcharge .= (C4::Context->preference('item-level_itypes'))
2194 ? " LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
2195 : " LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
2197 $qcharge .= "WHERE items.itemnumber =?";
2199 my $sth1 = $dbh->prepare($qcharge);
2200 $sth1->execute($itemnumber);
2201 if ( my $data1 = $sth1->fetchrow_hashref ) {
2202 $item_type = $data1->{'itemtype'};
2203 $charge = $data1->{'rentalcharge'};
2204 my $q2 = "SELECT rentaldiscount FROM borrowers
2205 LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
2206 WHERE borrowers.borrowernumber = ?
2207 AND issuingrules.itemtype = ?";
2208 my $sth2 = $dbh->prepare($q2);
2209 $sth2->execute( $borrowernumber, $item_type );
2210 if ( my $data2 = $sth2->fetchrow_hashref ) {
2211 my $discount = $data2->{'rentaldiscount'};
2212 if ( $discount eq 'NULL' ) {
2215 $charge = ( $charge * ( 100 - $discount ) ) / 100;
2221 return ( $charge, $item_type );
2224 =head2 AddIssuingCharge
2226 &AddIssuingCharge( $itemno, $borrowernumber, $charge )
2230 sub AddIssuingCharge {
2231 my ( $itemnumber, $borrowernumber, $charge ) = @_;
2232 my $dbh = C4::Context->dbh;
2233 my $nextaccntno = getnextacctno( $borrowernumber );
2235 INSERT INTO accountlines
2236 (borrowernumber, itemnumber, accountno,
2237 date, amount, description, accounttype,
2239 VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?)
2241 my $sth = $dbh->prepare($query);
2242 $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge );
2248 GetTransfers($itemnumber);
2253 my ($itemnumber) = @_;
2255 my $dbh = C4::Context->dbh;
2261 FROM branchtransfers
2262 WHERE itemnumber = ?
2263 AND datearrived IS NULL
2265 my $sth = $dbh->prepare($query);
2266 $sth->execute($itemnumber);
2267 my @row = $sth->fetchrow_array();
2273 =head2 GetTransfersFromTo
2275 @results = GetTransfersFromTo($frombranch,$tobranch);
2277 Returns the list of pending transfers between $from and $to branch
2281 sub GetTransfersFromTo {
2282 my ( $frombranch, $tobranch ) = @_;
2283 return unless ( $frombranch && $tobranch );
2284 my $dbh = C4::Context->dbh;
2286 SELECT itemnumber,datesent,frombranch
2287 FROM branchtransfers
2290 AND datearrived IS NULL
2292 my $sth = $dbh->prepare($query);
2293 $sth->execute( $frombranch, $tobranch );
2296 while ( my $data = $sth->fetchrow_hashref ) {
2297 push @gettransfers, $data;
2300 return (@gettransfers);
2303 =head2 DeleteTransfer
2305 &DeleteTransfer($itemnumber);
2309 sub DeleteTransfer {
2310 my ($itemnumber) = @_;
2311 my $dbh = C4::Context->dbh;
2312 my $sth = $dbh->prepare(
2313 "DELETE FROM branchtransfers
2315 AND datearrived IS NULL "
2317 $sth->execute($itemnumber);
2321 =head2 AnonymiseIssueHistory
2323 $rows = AnonymiseIssueHistory($borrowernumber,$date)
2325 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
2326 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
2328 return the number of affected rows.
2332 sub AnonymiseIssueHistory {
2334 my $borrowernumber = shift;
2335 my $dbh = C4::Context->dbh;
2338 SET borrowernumber = NULL
2339 WHERE returndate < '".$date."'
2340 AND borrowernumber IS NOT NULL
2342 $query .= " AND borrowernumber = '".$borrowernumber."'" if defined $borrowernumber;
2343 my $rows_affected = $dbh->do($query);
2344 return $rows_affected;
2347 =head2 updateWrongTransfer
2349 $items = updateWrongTransfer($itemNumber,$borrowernumber,$waitingAtLibrary,$FromLibrary);
2351 This function validate the line of brachtransfer but with the wrong destination (mistake from a librarian ...), and create a new line in branchtransfer from the actual library to the original library of reservation
2355 sub updateWrongTransfer {
2356 my ( $itemNumber,$waitingAtLibrary,$FromLibrary ) = @_;
2357 my $dbh = C4::Context->dbh;
2358 # first step validate the actual line of transfert .
2361 "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
2363 $sth->execute($FromLibrary,$itemNumber);
2366 # second step create a new line of branchtransfer to the right location .
2367 ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
2369 #third step changing holdingbranch of item
2370 UpdateHoldingbranch($FromLibrary,$itemNumber);
2373 =head2 UpdateHoldingbranch
2375 $items = UpdateHoldingbranch($branch,$itmenumber);
2376 Simple methode for updating hodlingbranch in items BDD line
2380 sub UpdateHoldingbranch {
2381 my ( $branch,$itemnumber ) = @_;
2382 ModItem({ holdingbranch => $branch }, undef, $itemnumber);
2387 $newdatedue = CalcDateDue($startdate,$loanlength,$branchcode);
2388 this function calculates the due date given the loan length ,
2389 checking against the holidays calendar as per the 'useDaysMode' syspref.
2390 C<$startdate> = C4::Dates object representing start date of loan period (assumed to be today)
2391 C<$branch> = location whose calendar to use
2392 C<$loanlength> = loan length prior to adjustment
2396 my ($startdate,$loanlength,$branch) = @_;
2397 if(C4::Context->preference('useDaysMode') eq 'Days') { # ignoring calendar
2398 my $datedue = time + ($loanlength) * 86400;
2399 #FIXME - assumes now even though we take a startdate
2400 my @datearr = localtime($datedue);
2401 return C4::Dates->new( sprintf("%04d-%02d-%02d", 1900 + $datearr[5], $datearr[4] + 1, $datearr[3]), 'iso');
2403 my $calendar = C4::Calendar->new( branchcode => $branch );
2404 my $datedue = $calendar->addDate($startdate, $loanlength);
2409 =head2 CheckValidDatedue
2410 This function does not account for holiday exceptions nor does it handle the 'useDaysMode' syspref .
2411 To be replaced by CalcDateDue() once C4::Calendar use is tested.
2413 $newdatedue = CheckValidDatedue($date_due,$itemnumber,$branchcode);
2414 this function validates the loan length against the holidays calendar, and adjusts the due date as per the 'useDaysMode' syspref.
2415 C<$date_due> = returndate calculate with no day check
2416 C<$itemnumber> = itemnumber
2417 C<$branchcode> = location of issue (affected by 'CircControl' syspref)
2418 C<$loanlength> = loan length prior to adjustment
2421 sub CheckValidDatedue {
2422 my ($date_due,$itemnumber,$branchcode)=@_;
2423 my @datedue=split('-',$date_due->output('iso'));
2424 my $years=$datedue[0];
2425 my $month=$datedue[1];
2426 my $day=$datedue[2];
2427 # die "Item# $itemnumber ($branchcode) due: " . ${date_due}->output() . "\n(Y,M,D) = ($years,$month,$day)":
2429 for (my $i=0;$i<2;$i++){
2430 $dow=Day_of_Week($years,$month,$day);
2431 ($dow=0) if ($dow>6);
2432 my $result=CheckRepeatableHolidays($itemnumber,$dow,$branchcode);
2433 my $countspecial=CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2434 my $countspecialrepeatable=CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2435 if (($result ne '0') or ($countspecial ne '0') or ($countspecialrepeatable ne '0') ){
2437 (($years,$month,$day) = Add_Delta_Days($years,$month,$day, 1))if ($i ne '1');
2440 my $newdatedue=C4::Dates->new(sprintf("%04d-%02d-%02d",$years,$month,$day),'iso');
2445 =head2 CheckRepeatableHolidays
2447 $countrepeatable = CheckRepeatableHoliday($itemnumber,$week_day,$branchcode);
2448 this function checks if the date due is a repeatable holiday
2449 C<$date_due> = returndate calculate with no day check
2450 C<$itemnumber> = itemnumber
2451 C<$branchcode> = localisation of issue
2455 sub CheckRepeatableHolidays{
2456 my($itemnumber,$week_day,$branchcode)=@_;
2457 my $dbh = C4::Context->dbh;
2458 my $query = qq|SELECT count(*)
2459 FROM repeatable_holidays
2462 my $sth = $dbh->prepare($query);
2463 $sth->execute($branchcode,$week_day);
2464 my $result=$sth->fetchrow;
2470 =head2 CheckSpecialHolidays
2472 $countspecial = CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2473 this function check if the date is a special holiday
2474 C<$years> = the years of datedue
2475 C<$month> = the month of datedue
2476 C<$day> = the day of datedue
2477 C<$itemnumber> = itemnumber
2478 C<$branchcode> = localisation of issue
2482 sub CheckSpecialHolidays{
2483 my ($years,$month,$day,$itemnumber,$branchcode) = @_;
2484 my $dbh = C4::Context->dbh;
2485 my $query=qq|SELECT count(*)
2486 FROM `special_holidays`
2492 my $sth = $dbh->prepare($query);
2493 $sth->execute($years,$month,$day,$branchcode);
2494 my $countspecial=$sth->fetchrow ;
2496 return $countspecial;
2499 =head2 CheckRepeatableSpecialHolidays
2501 $countspecial = CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2502 this function check if the date is a repeatble special holidays
2503 C<$month> = the month of datedue
2504 C<$day> = the day of datedue
2505 C<$itemnumber> = itemnumber
2506 C<$branchcode> = localisation of issue
2510 sub CheckRepeatableSpecialHolidays{
2511 my ($month,$day,$itemnumber,$branchcode) = @_;
2512 my $dbh = C4::Context->dbh;
2513 my $query=qq|SELECT count(*)
2514 FROM `repeatable_holidays`
2519 my $sth = $dbh->prepare($query);
2520 $sth->execute($month,$day,$branchcode);
2521 my $countspecial=$sth->fetchrow ;
2523 return $countspecial;
2528 sub CheckValidBarcode{
2530 my $dbh = C4::Context->dbh;
2531 my $query=qq|SELECT count(*)
2535 my $sth = $dbh->prepare($query);
2536 $sth->execute($barcode);
2537 my $exist=$sth->fetchrow ;
2548 Koha Developement team <info@koha.org>