+=head2 _koha_notify_reserve
+
+ _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
+
+Sends a notification to the patron that their hold has been filled (through
+ModReserveAffect, _not_ ModReserveFill)
+
+=cut
+
+sub _koha_notify_reserve {
+ my ($itemnumber, $borrowernumber, $biblionumber) = @_;
+
+ my $dbh = C4::Context->dbh;
+ my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
+
+ # Try to get the borrower's email address
+ my $to_address;
+ my $which_address = C4::Context->preference('AutoEmailPrimaryAddress');
+ # If the system preference is set to 'first valid' (value == OFF), look up email address
+ if ($which_address eq 'OFF') {
+ $to_address = C4::Members::GetFirstValidEmailAddress( $borrowernumber );
+ } else {
+ $to_address = $borrower->{$which_address};
+ }
+
+ my $letter_code;
+ my $print_mode = 0;
+ 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';
+ $print_mode = 1;
+ }
+
+ my $sth = $dbh->prepare("
+ SELECT *
+ FROM reserves
+ WHERE borrowernumber = ?
+ AND biblionumber = ?
+ ");
+ $sth->execute( $borrowernumber, $biblionumber );
+ my $reserve = $sth->fetchrow_hashref;
+ my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
+
+ my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
+
+ my $letter = C4::Letters::GetPreparedLetter (
+ module => 'reserves',
+ letter_code => $letter_code,
+ branchcode => $reserve->{branchcode},
+ tables => {
+ 'branches' => $branch_details,
+ 'borrowers' => $borrower,
+ 'biblio' => $biblionumber,
+ 'reserves' => $reserve,
+ 'items', $reserve->{'itemnumber'},
+ },
+ substitute => { today => C4::Dates->new()->output() },
+ ) or die "Could not find a letter called '$letter_code' in the 'reserves' module";
+
+
+
+ if ( $print_mode ) {
+ C4::Letters::EnqueueLetter( {
+ letter => $letter,
+ borrowernumber => $borrowernumber,
+ message_transport_type => 'print',
+ } );
+
+ return;
+ }
+
+ if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
+ # aka, 'email' in ->{'transports'}
+ C4::Letters::EnqueueLetter(
+ { letter => $letter,
+ borrowernumber => $borrowernumber,
+ message_transport_type => 'email',
+ from_address => $admin_email_address,
+ }
+ );
+ }
+
+ if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
+ C4::Letters::EnqueueLetter(
+ { letter => $letter,
+ borrowernumber => $borrowernumber,
+ message_transport_type => 'sms',
+ }
+ );
+ }
+}
+
+=head2 _ShiftPriorityByDateAndPriority
+
+ $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
+
+This increments the priority of all reserves after the one
+with either the lowest date after C<$reservedate>
+or the lowest priority after C<$priority>.
+
+It effectively makes room for a new reserve to be inserted with a certain
+priority, which is returned.
+
+This is most useful when the reservedate can be set by the user. It allows
+the new reserve to be placed before other reserves that have a later
+reservedate. Since priority also is set by the form in reserves/request.pl
+the sub accounts for that too.
+
+=cut
+
+sub _ShiftPriorityByDateAndPriority {
+ my ( $biblio, $resdate, $new_priority ) = @_;
+
+ my $dbh = C4::Context->dbh;
+ my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
+ my $sth = $dbh->prepare( $query );
+ $sth->execute( $biblio, $resdate, $new_priority );
+ my $min_priority = $sth->fetchrow;
+ # if no such matches are found, $new_priority remains as original value
+ $new_priority = $min_priority if ( $min_priority );
+
+ # Shift the priority up by one; works in conjunction with the next SQL statement
+ $query = "UPDATE reserves
+ SET priority = priority+1
+ WHERE biblionumber = ?
+ AND borrowernumber = ?
+ AND reservedate = ?
+ AND found IS NULL";
+ my $sth_update = $dbh->prepare( $query );
+
+ # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
+ $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
+ $sth = $dbh->prepare( $query );
+ $sth->execute( $new_priority, $biblio );
+ while ( my $row = $sth->fetchrow_hashref ) {
+ $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
+ }
+
+ return $new_priority; # so the caller knows what priority they wind up receiving
+}
+
+=head2 MoveReserve
+
+ MoveReserve( $itemnumber, $borrowernumber, $cancelreserve )
+
+Use when checking out an item to handle reserves
+If $cancelreserve boolean is set to true, it will remove existing reserve
+
+=cut
+
+sub MoveReserve {
+ my ( $itemnumber, $borrowernumber, $cancelreserve ) = @_;
+
+ my ( $restype, $res, $all_reserves ) = CheckReserves( $itemnumber );
+ return unless $res;
+
+ my $biblionumber = $res->{biblionumber};
+ my $biblioitemnumber = $res->{biblioitemnumber};
+
+ if ($res->{borrowernumber} == $borrowernumber) {
+ ModReserveFill($res);
+ }
+ else {
+ # warn "Reserved";
+ # The item is reserved by someone else.
+ # Find this item in the reserves
+
+ my $borr_res;
+ foreach (@$all_reserves) {
+ $_->{'borrowernumber'} == $borrowernumber or next;
+ $_->{'biblionumber'} == $biblionumber or next;
+
+ $borr_res = $_;
+ last;
+ }
+
+ if ( $borr_res ) {
+ # The item is reserved by the current patron
+ ModReserveFill($borr_res);
+ }
+
+ if ($cancelreserve) { # cancel reserves on this item
+ CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
+ CancelReserve($res->{'biblionumber'}, 0, $res->{'borrowernumber'});
+ }
+ }
+}
+
+=head2 MergeHolds
+
+ MergeHolds($dbh,$to_biblio, $from_biblio);
+
+This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by the date they were placed
+
+=cut
+
+sub MergeHolds {
+ my ( $dbh, $to_biblio, $from_biblio ) = @_;
+ my $sth = $dbh->prepare(
+ "SELECT count(*) as reservenumber FROM reserves WHERE biblionumber = ?"
+ );
+ $sth->execute($from_biblio);
+ if ( my $data = $sth->fetchrow_hashref() ) {
+
+ # holds exist on old record, if not we don't need to do anything
+ $sth = $dbh->prepare(
+ "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
+ $sth->execute( $to_biblio, $from_biblio );
+
+ # Reorder by date
+ # don't reorder those already waiting
+
+ $sth = $dbh->prepare(
+"SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC"
+ );
+ my $upd_sth = $dbh->prepare(
+"UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
+ AND reservedate = ? AND constrainttype = ? AND (itemnumber = ? or itemnumber is NULL) "
+ );
+ $sth->execute( $to_biblio, 'W', 'T' );
+ my $priority = 1;
+ while ( my $reserve = $sth->fetchrow_hashref() ) {
+ $upd_sth->execute(
+ $priority, $to_biblio,
+ $reserve->{'borrowernumber'}, $reserve->{'reservedate'},
+ $reserve->{'constrainttype'}, $reserve->{'itemnumber'}
+ );
+ $priority++;
+ }
+ }
+}
+
+=head2 ReserveSlip
+
+ ReserveSlip($branchcode, $borrowernumber, $biblionumber)
+
+ Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
+
+=cut
+
+sub ReserveSlip {
+ my ($branch, $borrowernumber, $biblionumber) = @_;
+
+# return unless ( C4::Context->boolean_preference('printreserveslips') );
+
+ my $reserve = GetReserveInfo($borrowernumber,$biblionumber )
+ or return;
+
+ return C4::Letters::GetPreparedLetter (
+ module => 'circulation',
+ letter_code => 'RESERVESLIP',
+ branchcode => $branch,
+ tables => {
+ 'reserves' => $reserve,
+ 'branches' => $reserve->{branchcode},
+ 'borrowers' => $reserve,
+ 'biblio' => $reserve,
+ 'items' => $reserve,
+ },
+ );
+}