@@ -, +, @@ your MARC frameworks to have this visible in the Editor) --- C4/Circulation.pm | 19 ++++ catalogue/moredetail.pl | 3 + catalogue/updateitem.pl | 12 ++ cataloguing/additem.pl | 13 +++ ..._20844-add_RevertLostBibLevelHolds_syspref.perl | 6 + installer/data/mysql/mandatory/sysprefs.sql | 2 + .../en/modules/admin/preferences/circulation.pref | 6 + .../prog/en/modules/catalogue/moredetail.tt | 12 ++ .../prog/en/modules/cataloguing/additem.tt | 19 ++++ t/db_dependent/Reserves.t | 124 ++++++++++++++++++++- 10 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 installer/data/mysql/atomicupdate/bug_20844-add_RevertLostBibLevelHolds_syspref.perl --- a/C4/Circulation.pm +++ a/C4/Circulation.pm @@ -3930,6 +3930,25 @@ sub LostItem{ while (my $transfer = $transfers->next) { $transfer->cancel({ reason => 'ItemLost', force => 1 }); } + + # RevertLostBibLevelHolds + my $hold = Koha::Holds->find({ itemnumber => $itemnumber, found => { '!=' => undef } }); + if ( C4::Context->preference('RevertLostBibLevelHolds') && defined $hold ){ + # syspref is enabled and there is a waiting hold for this item + if ( $hold->item_level_hold && defined $params->{cancel_reserve} ){ + if ( $params->{cancel_reserve} ){ + # cancel item-level hold + $hold->cancel; + } else { # eq 'revert' + # revert item-level hold's waiting status + $hold = C4::Reserves::RevertWaitingStatus({ itemnumber => $itemnumber }); + } + } + if ( !$hold->item_level_hold ){ + # revert biblio-level hold's waiting status + $hold = C4::Reserves::RevertWaitingStatus({ itemnumber => $itemnumber }); + } + } } sub GetOfflineOperations { --- a/catalogue/moredetail.pl +++ a/catalogue/moredetail.pl @@ -235,6 +235,9 @@ foreach my $item (@items){ $item_info->{'nomod'}=1; } } + + $item_info->{waiting_holds} = $item->{object}->holds({ found => 'W', item_level_hold => { '!=' => 0 } })->count; + push @item_data, $item_info; } --- a/catalogue/updateitem.pl +++ a/catalogue/updateitem.pl @@ -39,6 +39,18 @@ my $itemnotes_nonpublic=$cgi->param('itemnotes_nonpublic'); my $withdrawn=$cgi->param('withdrawn'); my $damaged=$cgi->param('damaged'); my $exclude_from_local_holds_priority = $cgi->param('exclude_from_local_holds_priority'); +my $cancelhold=$cgi->param('cancelhold'); +my $reverthold=$cgi->param('reverthold'); +my $redirecturl=$cgi->param('redirecturl'); +if ( defined $cancelhold ){ + LostItem($itemnumber, 'moredetail', 0, { cancel_reserve => $cancelhold }); +} elsif ( defined $reverthold ){ + $cancelhold = 0; + LostItem($itemnumber, 'moredetail', 0, { cancel_reserve => $cancelhold }); +} +if ( defined $redirecturl ){ + print $cgi->redirect( $redirecturl . $biblionumber ); +} my $confirm=$cgi->param('confirm'); my $dbh = C4::Context->dbh; --- a/cataloguing/additem.pl +++ a/cataloguing/additem.pl @@ -651,6 +651,19 @@ if( my $default_location = C4::Context->preference('NewItemsDefaultLocation') ) } my @ig = Koha::Biblio::ItemGroups->search({ biblio_id => $biblionumber })->as_list(); + +if ( C4::Context->preference('RevertLostBibLevelHolds') ){ + my @lostitems = Koha::Items->search({ biblionumber => $biblionumber, itemlost => { '!=' => 0 } }); + # find if any of these lost items have waiting holds + my @waitinglostitems; + foreach my $lostitem ( @lostitems ){ + if ( $lostitem->holds({ found => 'W', item_level_hold => 1 })->count > 0 ){ + push( @waitinglostitems, $lostitem ); + } + } + $template->param( waitinglostitems => \@waitinglostitems ); +} + # what's the next op ? it's what we are not in : an add if we're editing, otherwise, and edit. $template->param( biblio => $biblio, --- a/installer/data/mysql/atomicupdate/bug_20844-add_RevertLostBibLevelHolds_syspref.perl +++ a/installer/data/mysql/atomicupdate/bug_20844-add_RevertLostBibLevelHolds_syspref.perl @@ -0,0 +1,6 @@ +$DBversion = 'XXX'; +if( CheckVersion( $DBversion ) ) { + $dbh->do(q{INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('RevertLostBibLevelHolds', '0', NULL, 'If an item is marked as lost after being allocated for a biblio-level hold, revert the hold from its waiting status', 'YesNo') }); + + NewVersion( $DBversion, 20844, "Add RevertLostBibLevelHolds system preference"); +} --- a/installer/data/mysql/mandatory/sysprefs.sql +++ a/installer/data/mysql/mandatory/sysprefs.sql @@ -609,6 +609,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('ReturnLog','1',NULL,'If ON, enables the circulation (returns) log','YesNo'), ('ReturnpathDefault','',NULL,'Use this email address as return path or bounce address for undeliverable emails','Free'), ('OPACComments','1','','If ON, enables patron reviews of bibliographic records in the OPAC','YesNo'), +('RevertLostBibLevelHolds', '0', NULL, 'If an item is marked as lost after being allocated for a biblio-level hold, revert the hold from its waiting status', 'YesNo'), ('RisExportAdditionalFields', '', NULL , 'Define additional RIS tags to export from MARC records in YAML format as an associative array with either a marc tag/subfield combination as the value, or a list of tag/subfield combinations.', 'textarea'), ('RoundFinesAtPayment','0', NULL,'If enabled any fines with fractions of a cent will be rounded to the nearest cent when payments are coll ected. e.g. 1.004 will be paid off by a 1.00 payment','YesNo'), ('RoutingListAddReserves','0','','If ON the patrons on routing lists are automatically added to holds on the issue.','YesNo'), @@ -634,6 +635,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('SelfCheckReceiptPrompt','1','NULL','If ON, print receipt dialog pops up when self checkout is finished','YesNo'), ('SelfCheckTimeout','120','','Define the number of seconds before the Web-based Self Checkout times out a patron','Integer'), ('SendAllEmailsTo','',NULL,'All emails will be redirected to this email if it is not empty','free'), +('SendLostHoldNotices', '0', NULL, 'Send a notice to a borrower if their reserved and waiting item is marked as lost', 'YesNo'), ('SeparateHoldings','0',NULL,'Separate current branch holdings from other holdings','YesNo'), ('SeparateHoldingsBranch','homebranch','homebranch|holdingbranch','Branch used to separate holdings','Choice'), ('SessionRestrictionByIP','1','Check for change in remote IP address for session security. Disable only when remote IP address changes frequently.','','YesNo'), --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref @@ -662,6 +662,12 @@ Circulation: no: "Don't enable" - the item groups feature to allow collecting groups of items on a record together. - + - pref: RevertLostBibLevelHolds + choices: + yes: Revert + no: "Don't revert" + - waiting status of a hold if the allocated item is marked as Lost. + - - In the staff client, split the holds queue into separate tables by - pref: HoldsSplitQueue choices: --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/moredetail.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/moredetail.tt @@ -96,6 +96,18 @@

Barcode [% ITEM_DAT.barcode | html %] [% IF not_for_loan %][% not_for_loan_description | html %] [% END %]

+ [% IF ITEM_DAT.itemlost and ITEM_DAT.waiting_holds > 0 %] +
+ This item has a waiting item-level hold on it, and has been marked as lost.
+
+ + + + +
+
+ [% END %] +

Item information [% UNLESS ( ITEM_DAT.nomod ) %] [% IF ( CAN_user_editcatalogue_edit_items ) %] Edit item --- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt @@ -66,6 +66,25 @@ [% IF ( linked_analytics ) %]
Cannot delete: item has linked analytics..
[% END %] [% IF last_item_for_hold %]
Cannot delete: Last item for bibliographic record with biblio-level hold on it.
[% END %] +[% IF waitinglostitems %] +
+ The following item(s) have a waiting item-level hold and have been marked as lost. +
    + [% FOREACH i IN waitinglostitems %] +
    +
  • Item ([% i.barcode %]) +
    + + + + +
  • + + [% END %] +
+
+[% END %] +
[% IF items %] [% SET date_fields = [ 'dateaccessioned', 'onloan', 'datelastseen', 'datelastborrowed', 'replacementpricedate' ] %] --- a/t/db_dependent/Reserves.t +++ a/t/db_dependent/Reserves.t @@ -17,7 +17,7 @@ use Modern::Perl; -use Test::More tests => 77; +use Test::More tests => 78; use Test::MockModule; use Test::Warn; @@ -1408,6 +1408,128 @@ subtest 'ModReserveAffect logging' => sub { like( $log->info, qr{$expected}, 'Timestamp logged is the current one' ); }; +subtest 'Waiting item is marked as lost' => sub { + + plan tests => 15; + + # Set up data + my $biblio = $builder->build_sample_biblio(); + my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber }); + my $patron = $builder->build_object({ class => 'Koha::Patrons' }); + + ## TEST 1: BIBLIO-LEVEL HOLD + + # place biblio-level hold + my $reserve_id = AddReserve({ + branchcode => $item->homebranch, + borrowernumber => $patron->borrowernumber, + biblionumber => $biblio->biblionumber, + }); + my $hold = Koha::Holds->find( $reserve_id ); + + is( $hold->item_level_hold, 0, 'Biblio-level hold placed' ); + + # set hold as waiting and assign item + $hold->set_waiting; + $hold->set({ itemnumber => $item->itemnumber, priority => 0 })->store; + + # mark item in biblio as lost + $item->itemlost( 1 )->store; + is( $item->itemlost, 1, 'Item assigned to waiting hold has been marked lost'); + + # do test + t::lib::Mocks::mock_preference( 'RevertLostBibLevelHolds', 0 ); + C4::Circulation::LostItem( $item->itemnumber, 'test', 0, { cancel_reserve => 0 }); + is( $hold->found, 'W', 'Hold is still waiting even though the item is lost because RevertLostBibLevelHolds is disabled' ); + + # enable preference + t::lib::Mocks::mock_preference( 'RevertLostBibLevelHolds', 1 ); + + # try again + is( $item->itemlost, 1, 'Item assigned to waiting hold has been marked lost'); + C4::Circulation::LostItem( $item->itemnumber, 'test', 0, { cancel_reserve => 0 }); + $hold = Koha::Holds->find( $reserve_id ); + is( $hold->found, undef, 'Hold waiting status has been reverted because RevertLostBibLevelHolds is enabled' ); + + # clean up + $hold->cancel; + $item->itemlost( 0 ); + + ## TEST 2: ITEM-LEVEL HOLD, REVERT + + # place item-level hold + $reserve_id = AddReserve({ + branchcode => $item->homebranch, + borrowernumber => $patron->borrowernumber, + biblionumber => $biblio->biblionumber, + itemnumber => $item->itemnumber + }); + $hold = Koha::Holds->find( $reserve_id ); + + is( $hold->item_level_hold, 1, 'Item-level hold placed' ); + + # set hold as waiting and assign item + $hold->set_waiting; + $hold->set({ itemnumber => $item->itemnumber, priority => 0 })->store; + + # mark item in biblio as lost + $item->itemlost( 1 ); + is( $item->itemlost, 1, 'Item assigned to waiting hold has been marked lost'); + + # do test + t::lib::Mocks::mock_preference( 'RevertLostBibLevelHolds', 0 ); + C4::Circulation::LostItem( $item->itemnumber, 'test', 0, { cancel_reserve => 0 }); + is( $hold->found, 'W', 'Hold is still waiting even though the item is lost because RevertLostBibLevelHolds is disabled' ); + + # enable preference + t::lib::Mocks::mock_preference( 'RevertLostBibLevelHolds', 1 ); + + # try again + is( $item->itemlost, 1, 'Item assigned to waiting hold has been marked lost'); + C4::Circulation::LostItem( $item->itemnumber, 'test', 0, { cancel_reserve => 0 }); + $hold = Koha::Holds->find( $reserve_id ); + is( $hold->found, undef, 'Hold waiting status has been reverted because RevertLostBibLevelHolds is enabled, and we chose not to cancel' ); + + # clean up + $hold->cancel; + $item->itemlost( 0 ); + + # TEST 3: ITEM-LEVEL HOLD, CANCEL + + # place item-level hold + $reserve_id = AddReserve({ + branchcode => $item->homebranch, + borrowernumber => $patron->borrowernumber, + biblionumber => $biblio->biblionumber, + itemnumber => $item->itemnumber + }); + $hold = Koha::Holds->find( $reserve_id ); + + is( $hold->item_level_hold, 1, 'Item-level hold placed' ); + + # set hold as waiting and assign item + $hold->set_waiting; + $hold->set({ itemnumber => $item->itemnumber, priority => 0 })->store; + + # mark item in biblio as lost + $item->itemlost( 1 ); + is( $item->itemlost, 1, 'Item assigned to waiting hold has been marked lost'); + + # do test + t::lib::Mocks::mock_preference( 'RevertLostBibLevelHolds', 0 ); + C4::Circulation::LostItem( $item->itemnumber, 'test', 0, { cancel_reserve => 1 }); + is( $hold->found, 'W', 'Hold is still waiting even though the item is lost because RevertLostBibLevelHolds is disabled' ); + + # enable preference + t::lib::Mocks::mock_preference( 'RevertLostBibLevelHolds', 1 ); + + # try again + is( $item->itemlost, 1, 'Item assigned to waiting hold has been marked lost'); + C4::Circulation::LostItem( $item->itemnumber, 'test', 0, { cancel_reserve => 1 }); + $hold = Koha::Holds->find( $reserve_id ); + is( Koha::Holds->find( $reserve_id ), undef, 'Hold has been cancelled with RevertLostBibLevelHolds enabled' ); +}; + sub count_hold_print_messages { my $message_count = $dbh->selectall_arrayref(q{ SELECT COUNT(*) --