From cc90374ecef87ec8fef8dc3aaa1d80a2ec552be4 Mon Sep 17 00:00:00 2001 From: Aleisha Amohia Date: Thu, 2 Feb 2023 03:48:08 +0000 Subject: [PATCH] Bug 32776: Select reserves to convert using new syspref This patchset enhances the convert_reserves_to_recalls.pl cronjob by allowing libraries to be more specific about which reserves should be converted to recalls. The new ConvertSelectedReservesToRecalls adds the flexibility to choose: - the oldest placed reserve to be converted to a recall - each reserve will be converted, one by one, until all recallable items are recalled When reserves are converted to recalls, they will be allocated a specific item and become an item-level recall. This is so recallable items can be allocated one by one. To test: 1. Apply Bug 31415 and configure recalls 2. Apply these patches and update database 3. Create a biblio with 3 items 4. Check out all 3 items to 3 different patrons 5. Place 4 biblio-level reserves for 4 different patrons 6. Set the ConvertSelectedReservesToRecalls system preference to 'oldest' 7. In your shell, run the script misc/cronjobs/recalls/convert_reserves_to_recalls.pl -v --min 1 8. Confirm there is now 1 recall and 3 biblio-level reserves on the record 9. Confirm the recall has been allocated to one item (there should be a barcode next to the title in the recalls table) 10. In your shell, run the script misc/cronjobs/recalls/convert_reserves_to_recalls.pl -v --min 1 11. Confirm there are now 2 recalls and 2 biblio-level reserves on the record 12. Confirm the second recall was allocated to the second item. NOT the same item as the first recall. 13. In your shell, run the script twice misc/cronjobs/recalls/convert_reserves_to_recalls.pl -v --min 1 misc/cronjobs/recalls/convert_reserves_to_recalls.pl -v --min 1 14. Confirm there is still one reserve remaining that can't be converted because all recallable items are already recalled. 15. Set the ConvertSelectedReservesToRecalls system preference to 'recallable' 16. Select all of the recalls on the record and delete them 17. Place 4 biblio-level reserves for 4 different patrons 18. In your shell, run the script misc/cronjobs/recalls/convert_reserves_to_recalls.pl -v --min 1 19. Confirm all reserves have been converted to recalls and allocated an item. The fourth reserve will have been allocated as a second recall to the first item. 20. Select all of the recalls on the record and delete them. 21. Place 2 biblio-level recalls and 2 item-level recalls for 4 different patrons, in any order 22. In your shell, run the script misc/cronjobs/recalls/convert_reserves_to_recalls.pl -v --min 1 23. Confirm all reserves have been converted to recalls and allocated an item. The item-level reserves should have been allocated to their intended item. The biblio-level reserves should have been allocated to the other items. Sponsored-by: Auckland University of Technology --- .../recalls/convert_holds_to_recalls.pl | 137 ++++++++++++++---- 1 file changed, 107 insertions(+), 30 deletions(-) diff --git a/misc/cronjobs/recalls/convert_holds_to_recalls.pl b/misc/cronjobs/recalls/convert_holds_to_recalls.pl index 79bd0f9bc0e..49982492933 100755 --- a/misc/cronjobs/recalls/convert_holds_to_recalls.pl +++ b/misc/cronjobs/recalls/convert_holds_to_recalls.pl @@ -46,7 +46,11 @@ This script converts holds to recalls when a record has a set minimum number of If a record is found to have the specified minimum number of holds, the script will find the oldest hold and convert it to a recall, as long as it would be a legal recall. -It will only convert the single oldest hold. Once converted, the script will move on to the next record, it will not continue to convert holds on this record. +This script uses the ConvertSelectedReservesToRecalls system preference. + +When ConvertSelectedReservesToRecalls is set to 'oldest', this script will only convert the single oldest hold. Once converted, the script will move on to the next record, it will not continue to convert holds on this record. + +When ConvertSelectedReservesToRecalls is set to 'recallable', this script will convert reserves one-by-one, oldest first, until all recallable items have been recalled. Options: -v verbose @@ -89,47 +93,120 @@ my @bib_holds = Koha::Holds->search({}, { my $count = 0; foreach my $bib ( @bib_holds ) { $bib = $bib->unblessed; + # If there are $min or more holds on the same record, we can begin converting holds if ( $bib->{bibcount} >= $min ) { - # If there are $min or more holds on the same record, convert the oldest hold to a recall + # Get all holds on this biblio my @holds = Koha::Holds->search({ biblionumber => $bib->{biblionumber} }, { order_by => { -asc => 'reservedate' } })->as_list; - my $hold_to_convert = $holds[0]; - foreach my $res ( @holds ) { - if ( dt_from_string($res->reservedate) < dt_from_string($hold_to_convert->reservedate) ) { - $hold_to_convert = $res; + + if ( C4::Context->preference('ConvertSelectedReservesToRecalls') eq 'oldest' ) { + + my $hold_to_convert = $holds[0]; + my $itemnumber_to_allocate = can_convert( $hold_to_convert ); + if ( $itemnumber_to_allocate ) { + my $item_to_allocate = Koha::Items->find( $itemnumber_to_allocate ); + do_convert( $hold_to_convert, $item_to_allocate ); + report( $hold_to_convert, ++$count ); + } + + } elsif ( C4::Context->preference('ConvertSelectedReservesToRecalls') eq 'recallable' ) { + + my $this_record_holds_converted = 0; + + my @items = Koha::Items->search({ biblionumber => $bib->{biblionumber} })->as_list; + foreach my $item ( @items ) { + my $hold_to_convert = $holds[ $this_record_holds_converted ]; + + if ( !$hold_to_convert ) { + last; + } + + my $itemnumber_to_allocate = can_convert( $hold_to_convert, $item ); + + if ( $itemnumber_to_allocate ) { + my $item_to_allocate = $itemnumber_to_allocate == $item->id ? $item : Koha::Items->find( $itemnumber_to_allocate ); + do_convert( $hold_to_convert, $item_to_allocate ); + report( $hold_to_convert, ++$count ); + } + $this_record_holds_converted++; } } + } +} + +sub can_convert { + my $hold = shift; + my $item = shift; - my $patron = Koha::Patrons->find( $hold_to_convert->borrowernumber ); - my $biblio = Koha::Biblios->find( $hold_to_convert->biblionumber ); - my $item; - my $can_convert; - if ( $hold_to_convert->item_level_hold ) { - $item = Koha::Items->find( $hold_to_convert->itemnumber ); + my $patron = Koha::Patrons->find( $hold_to_convert->borrowernumber ); + my $biblio = Koha::Biblios->find( $hold_to_convert->biblionumber ); + + if ( $hold->item_level_hold ) { + + if ( $item and $item->id == $hold->itemnumber ) { + # item-level reserve, on this specific item if ( $item->can_be_recalled({ patron => $patron, hold_convert => 1 }) ) { - $can_convert = 1; + return $item->id; } } else { - if ( $biblio->can_be_recalled({ patron => $patron, hold_convert => 1 }) ) { - $can_convert = 1; + # item-level reserve but not on this item + # so check if intended item can be allocated for the recall + $item = Koha::Items->find( $hold->itemnumber ); + if ( $item and $item->can_be_recalled({ patron => $patron, hold_convert => 1 }) ) { + return $item->id; } } - if ( $can_convert ) { - my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({ - patron => $patron, - biblio => $biblio, - branchcode => $hold_to_convert->branchcode, - item => $item, - expirationdate => $hold_to_convert->expirationdate, - interface => 'commandline', - }); - $hold_to_convert->cancel({ cancellation_reason => 'RECALLED' }); - $count++; - if ( $verbose ) { - my $hold_id = $hold_to_convert->hold_id; - my $biblionumber = $hold_to_convert->biblionumber; - print "$count. Hold converted to recall (hold_id: $hold_id, biblionumber: $biblionumber).\n"; + + } else { + # bib-level reserve + + if ( $item and $item->can_be_recalled({ patron => $patron, hold_convert => 1 }) ) { + # this item may be able to fill the recall + return $item->id; + } + + # don't have a specific item to allocate for this recall + # so get an eligible item that's not already recalled + foreach my $i ( $biblio->items->as_list ) { + if ( Koha::Recalls->filter_by_current->search({ item_id => $i->id })->count < 1 ) { + if ( $i and $i->can_be_recalled({ patron => $patron, hold_convert => 1 }) ) { + return $i->id; + } } } + + # there are no recallable items left + return; + } + + return; +} + +sub do_convert { + my $hold = shift; + my $item = shift; + + my $patron = Koha::Patrons->find( $hold->borrowernumber ); + my $biblio = Koha::Biblios->find( $hold->biblionumber ); + + my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({ + patron => $patron, + biblio => $biblio, + branchcode => $hold->branchcode, + item => $item, + expirationdate => $hold->patron_expiration_date ? $hold->patron_expiration_date : $hold->expirationdate, + interface => 'commandline', + }); + $hold->cancel({ cancellation_reason => 'RECALLED' }); +} + +sub report { + my $hold = shift; + my $count = shift; + + if ( $verbose ) { + my $hold_id = $hold->reserve_id; + my $biblionumber = $hold->biblionumber; + print "$count. Hold converted to recall (reserve_id: $hold_id, biblionumber: $biblionumber).\n"; } } -- 2.30.2