Bugzilla – Attachment 185754 Details for
Bug 31698
Add ability to move a hold to a new bibliographic record/item
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 31698: Add ability to move holds to new items or new records
Bug-31698-Add-ability-to-move-holds-to-new-items-o.patch (text/plain), 19.97 KB, created by
Lucas Gass (lukeg)
on 2025-08-25 20:54:56 UTC
(
hide
)
Description:
Bug 31698: Add ability to move holds to new items or new records
Filename:
MIME Type:
Creator:
Lucas Gass (lukeg)
Created:
2025-08-25 20:54:56 UTC
Size:
19.97 KB
patch
obsolete
>From 10170435cf86259a3d1e8e44036ebbfb4407a4b2 Mon Sep 17 00:00:00 2001 >From: Lucas Gass <lucas@bywatersolutions.com> >Date: Wed, 12 Mar 2025 21:31:35 +0000 >Subject: [PATCH] Bug 31698: Add ability to move holds to new items or new > records > >Test plan: > >1. APPLY PATCH, updatedatabase, restart_all >2. Have two staff members. 1. Superlibrarian, 2. Staff with new permission to `alter_hold_targets` >3. Now place some holds on different record, you'll want some item level holds and some record level holds. >4. After placing some holds go to a record with some holds on it. >5. Go to the holds tab (reserve/request.p) >6. You should notice a disabled 'Move selected (0)' button, next to 'Cancel selected (0)' >7. Select some holds to move, ensure that the number updates accordingly. ( Move selected (2) ) >8. When you click the button you should see 2 options, 'Item level holds to a different item' and 'Record level holds to a different record' >9. Clicking on either should bring up a modal with information about the holds you selected to move. >10. If you have item level holds and you select 'Item level holds to a different item' those holds should appear pre-checked. >11. If you have item level holds and you select 'Record level holds to a different record' those holds checkboxes should be disabled by default, you should see the 'Problem' as 'Cannot move a waiting or record level hold' >12. If you are moving a item level hold you should see a searchbar labeled 'Enter the item barcode of new hold target:'. >13. Enter a full or partial barcode and click search >14. You should see your item, if it exists. >15. Check 'Move all selected item level holds to this item', once you do so the yellow 'Move selected holds' button should be enabled. >16. Click 'Move selected holds', the hold should be moved. >17. You should see a message like " Moved the following holds: > Hold ID: 1854 - Moved from The Arden edition of the works of William Shakespeare. to The Arden edition of the works of William Shakespeare." > >19. Do steps 12 - 17 again, but with a record level hold. >20. Now make or have many holds on a specific target record to test priority. Make note of the current priority >21. Now move a hold on to that record, testing both item and record level holds. >22. Now on the target record review the priority. The holds originally on the record should not have changed. The hold you move should now have the lowest priority on the record. >23. Check the original record, if a hold was moved all other priority should have been adjusted correctly. FOr example, if the hold you moved was 3 of 4 then the others should have adjusted priority of 1 2 3 now. > >24. Turn on HoldsLog if it is disabled. >25. Move some holds, both item and record ones, and review the logs after each one. There should be a correct MODIFY entry for each move. > >26. Make an item with a hold on it as damaged and turn on AllowHoldsOnDamagedItems. Try to move the hold, you should get an error telling you that the target item is damaged. > >27. Now login as the non-superlibrarian patron from step 2. If they have the `alter_hold_targets` permission they should see the 'Move selected' button. >28. Revoke the `alter_hold_targets` permission. The button should not appear now. >--- > .../prog/en/modules/reserve/request.tt | 234 ++++++++++++++++++ > reserve/request.pl | 54 ++++ > 2 files changed, 288 insertions(+) > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >index 02dd27aa3b9..b806ddc323c 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >@@ -1243,6 +1243,15 @@ > </select> > [% END %] > </fieldset> >+ [% IF ( CAN_user_reserveforothers_alter_hold_targets ) %] >+ <div class="btn-group"> >+ <a class="btn btn-default btn-xs dropdown-toggle move_selected_holds" id="item_record_choice" role="button" data-bs-toggle="dropdown" href="#" aria-expanded="false"></a> >+ <ul class="dropdown-menu" role="menu" aria-labelledby="item_record_choice" style=""> >+ <li><a class="dropdown-item move_hold_item" href="#">Item level holds to a different item</a></li> >+ <li><a class="dropdown-item move_hold_record" href="#">Record level holds to a different record</a></li> >+ </ul> >+ </div> >+ [% END %] > </div> > <div class="page-section"> > [% FOREACH biblioloo IN biblioloop %] >@@ -1387,6 +1396,104 @@ > > [% PROCESS 'cancel-hold-modal' form_action = 'request.pl' %] > >+<div id="moveHoldItemModal" class="modal modal-lg" tabindex="-1" role="dialog" aria-hidden="true"> >+ <div class="modal-dialog"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <h1 class="modal-title">Move hold(s) to a different item</h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <div class="modal-body"> >+ <div id="move_hold_item_selection"> >+ <h3>1. Review holds to move</h3> >+ <table class="table table-striped"> >+ <thead> >+ <tr> >+ <th>Hold ID</th> >+ <th>Original hold</th> >+ <th>Problem</th> >+ </tr> >+ </thead> >+ <tbody> </tbody> >+ </table> >+ </div> >+ <hr /> >+ <div id="move_hold_item_search" class="alert alert-danger" style="display:none;"></div> >+ <form id="itemSearchForm"> >+ <div id="move_hold_item_searchform"> >+ <h3>2. Enter the item barcode of new hold target: </h3> >+ <fieldset class="action"> >+ <input type="text" class="form-control" name="external_id" id="external_id" required /> >+ <button type="submit" class="btn btn-default mt-3">Search</button> >+ </fieldset> >+ </div> >+ </form> >+ <form id="move_hold_item_form" method="post" action="request.pl"> >+ <div id="itemResultMessage" class="mt-3"> </div> >+ <div id="move_hold_item_confirm" style="display:none;"> >+ <h3>3. Confirm moving of holds:</h3> >+ [% INCLUDE 'csrf-token.inc' %] >+ <input type="hidden" name="op" value="cud-move_hold_item" /> >+ <button type="submit" class="btn btn-primary mt-3">Move selected holds</button> >+ <input type="hidden" name="original_biblionumber" value="[% biblio.biblionumber %]" /> >+ </div> >+ </form> >+ </div> >+ <div class="modal-footer"> >+ <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button> >+ </div> >+ </div> >+ </div> >+</div> >+<div id="moveHoldBiblioModal" class="modal modal-lg" tabindex="-1" role="dialog" aria-hidden="true"> >+ <div class="modal-dialog"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <h1 class="modal-title">Move hold(s) to a different record</h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <div class="modal-body"> >+ <div id="move_hold_item_selection"> >+ <h3>1. Review holds to move</h3> >+ <table class="table table-striped"> >+ <thead> >+ <tr> >+ <th>Hold ID</th> >+ <th>Original hold</th> >+ <th>Problem</th> >+ </tr> >+ </thead> >+ <tbody> </tbody> >+ </table> >+ </div> >+ <hr /> >+ <div id="move_hold_biblio_search" class="alert alert-danger" style="display:none;"></div> >+ <form id="biblioSearchForm"> >+ <div id="move_hold_item_searchform"> >+ <h3>2. Enter the bibloinumber of the new hold target: </h3> >+ <fieldset class="action"> >+ <input type="text" class="form-control" name="biblio_id" id="biblio_id" required /> >+ <button type="submit" class="btn btn-default mt-3">Search</button> >+ </fieldset> >+ </div> >+ </form> >+ <form id="move_hold_biblio_form" method="post" action="request.pl"> >+ <div id="biblioResultMessage" class="mt-3"> </div> >+ <div id="move_hold_biblio_confirm" style="display:none;"> >+ <h3>3. Confirm moving of holds:</h3> >+ [% INCLUDE 'csrf-token.inc' %] >+ <input type="hidden" name="op" value="cud-move_hold_biblio" /> >+ <button type="submit" class="btn btn-primary mt-3">Move selected holds</button> >+ <input type="hidden" name="original_biblionumber" value="[% biblio.biblionumber %]" /> >+ </div> >+ </form> >+ </div> >+ <div class="modal-footer"> >+ <button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button> >+ </div> >+ </div> >+ </div> >+</div> > <div id="hold-actions"> > <form id="hold-actions-form"> [% INCLUDE 'csrf-token.inc' %] </form> > </div> >@@ -1699,6 +1806,91 @@ > } > > $(document).ready(function() { >+ $("#itemSearchForm").on("submit", function (event) { >+ event.preventDefault(); >+ >+ let externalID = $("#external_id").val(); >+ let apiUrl = `/api/v1/items?external_id=${encodeURIComponent(externalID)}`; >+ >+ $.ajax({ >+ url: apiUrl, >+ method: "GET", >+ dataType: "json", >+ success: function (data) { >+ if (data.length > 0) { >+ let resultHtml = ""; >+ $.each(data, function (index, item) { >+ resultHtml += ` >+ <div class="alert alert-success"> >+ <strong>Biblionumber:</strong> ${item.biblio_id} <br> >+ <strong>Item:</strong> ${item.external_id} <br> >+ <input id="new_itemnumber_${item.item_id}" name="new_itemnumber" type="checkbox" value="${item.item_id}"> >+ <label for="new_itemnumber_${item.item_id}">Move all selected item level holds to this item</label> >+ <input id="new_biblionumber_${item.item_id}" name="new_biblionumber" type="hidden" value="${item.biblio_id}"> >+ </div> >+ <hr /> >+ `; >+ }); >+ $("#itemResultMessage").html(resultHtml); >+ } else { >+ $("#itemResultMessage").html(` >+ <div class="alert alert-warning">No item found with barcode: ${externalID}.</div> >+ `); >+ } >+ }, >+ }); >+ }); >+ >+ $("#biblioSearchForm").on("submit", function (event) { >+ event.preventDefault(); >+ >+ let biblioID = $("#biblio_id").val(); >+ let apiUrl = `/api/v1/items?q={"biblio_id":"${encodeURIComponent(biblioID)}"}`; >+ >+ $.ajax({ >+ url: apiUrl, >+ method: "GET", >+ dataType: "json", >+ success: function (data) { >+ if (data.length > 0) { >+ let resultHtml = ""; >+ $.each(data, function (index, item) { >+ resultHtml += ` >+ <div class="alert alert-success"> >+ <strong>Biblionumber:</strong> ${item.biblio_id} <br> >+ <input id="new_biblionumber_${item.biblio_id}" name="new_biblionumber" type="checkbox" value="${item.biblio_id}"> >+ <label for="new_biblionumber_${item.biblio_id}">Move all selected record level holds to this record</label> >+ </div> >+ <hr /> >+ `; >+ }); >+ $("#biblioResultMessage").html(resultHtml); >+ } else { >+ $("#biblioResultMessage").html(` >+ <div class="alert alert-warning">No item found with barcode: ${biblioID}.</div> >+ `); >+ } >+ }, >+ }); >+ }); >+ >+ $(document).on("change", 'input[name="new_itemnumber"]', function() { >+ $('input[name="new_itemnumber"]').not(this).prop("checked", false); >+ if ( $('input[name="new_itemnumber"]:checked').length ){ >+ $('#move_hold_item_confirm').show(); >+ } else { >+ $('#move_hold_item_confirm').hide(); >+ } >+ }); >+ >+ $(document).on("change", 'input[name="new_biblionumber"]', function() { >+ $('input[name="new_biblionumber"]').not(this).prop("checked", false); >+ if ( $('input[name="new_biblionumber"]:checked').length ){ >+ $('#move_hold_biblio_confirm').show(); >+ } else { >+ $('#move_hold_biblio_confirm').hide(); >+ } >+ }); > > $("#always_show_holds").change(function(){ > if( $(this).prop('checked') ){ >@@ -1800,6 +1992,48 @@ > }); > [% END %] > >+ $('.move_hold_item').click(function(e) { >+ e.preventDefault(); >+ if($('.holds_table .select_hold:checked').length) { >+ $('#itemResultMessage').empty(); >+ $('#move_hold_item_selection table tbody').empty(); >+ $('#moveHoldItemModal').modal('show'); >+ $('.select_hold:checked').each( function() { >+ let reserve_id = $(this).data('id'); >+ let reserve_biblionumber = $(this).data('biblionumber'); >+ let reserve_itemnumber = $(this).data('itemnumber'); >+ let item_level_hold = $(this).data('item_level_hold'); >+ let error_message = $(this).data('item_level_hold') ? "" : _("Cannot move a waiting or record level hold"); >+ let found_status = $(this).data('found'); >+ $('#move_hold_item_selection table').append(`<tr><td>${reserve_id}</td><td>Biblionumber: <a target="_blank" href="/cgi-bin/koha/reserve/request.pl?biblionumber=${reserve_biblionumber}">${reserve_biblionumber}</a> Itemnumber: <a target="_blank" href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=${reserve_biblionumber}#item${reserve_itemnumber}">${reserve_itemnumber}</a></td><td>${error_message}</td></tr>`) >+ if ( item_level_hold ) { >+ $('#move_hold_item_form').append(`<input type="hidden" name="hold_id" value="${reserve_id}">`); >+ } >+ }); >+ } >+ }); >+ >+ $('.move_hold_record').click(function(e) { >+ e.preventDefault(); >+ if($('.holds_table .select_hold:checked').length) { >+ $('#biblioResultMessage').empty(); >+ $('#move_hold_item_selection table tbody').empty(); >+ $('#moveHoldBiblioModal').modal('show'); >+ $('.select_hold:checked').each( function() { >+ let reserve_id = $(this).data('id'); >+ let reserve_biblionumber = $(this).data('biblionumber'); >+ let reserve_itemnumber = $(this).data('itemnumber'); >+ let item_level_hold = $(this).data('item_level_hold'); >+ let error_message = $(this).data('item_level_hold') ? _("Cannot move a waiting or item level hold") : ""; >+ let found_status = $(this).data('found'); >+ $('#move_hold_item_selection table').append(`<tr><td>${reserve_id}</td><td>Biblionumber: <a target="_blank" href="/cgi-bin/koha/reserve/request.pl?biblionumber=${reserve_biblionumber}">${reserve_biblionumber}</a></td><td>${error_message}</td></tr>`) >+ if ( !item_level_hold ) { >+ $('#move_hold_biblio_form').append(`<input type="hidden" name="hold_id" value="${reserve_id}">`); >+ } >+ }); >+ } >+ }); >+ > $(".hold-arrow").click(function(e) { > e.preventDefault(); > let arrowForm = $("#hold-actions-form").attr({ >diff --git a/reserve/request.pl b/reserve/request.pl >index 64b2809f205..5c1e3905eba 100755 >--- a/reserve/request.pl >+++ b/reserve/request.pl >@@ -110,6 +110,60 @@ if ( $op eq 'cud-move' ) { > $next_priority, $first_priority, $last_priority > ); > } >+} elsif ( $op eq 'cud-move_hold_item' or $op eq 'cud-move_hold_biblio' ) { >+ my @hold_ids = $input->multi_param('hold_id'); >+ my $original_biblionumber = $input->param('original_biblionumber'); >+ my @moving_holds = Koha::Holds->search( >+ { reserve_id => \@hold_ids }, >+ { order_by => { -asc => 'priority' } } >+ )->as_list; >+ >+ my $new_biblionumber = $input->param('new_biblionumber'); >+ my $target_biblio = Koha::Biblios->find($new_biblionumber); >+ >+ my %args = ( >+ new_biblionumber => $new_biblionumber, >+ ); >+ >+ # Only pass new_itemnumber if this is an item level move >+ if ( $op eq 'cud-move_hold_item' ) { >+ $args{new_itemnumber} = $input->param('new_itemnumber'); >+ } >+ >+ my @success_messages; >+ my @error_messages; >+ >+ foreach my $hold (@moving_holds) { >+ my $original_biblio = Koha::Biblios->find( $hold->biblionumber ); >+ >+ my $result = $hold->move_hold( \%args ); >+ >+ if ( $result->{success} ) { >+ push @success_messages, { >+ hold_id => $hold->id, >+ success => 1, >+ original_biblio => $original_biblio, >+ target_biblio => $target_biblio, >+ }; >+ } else { >+ push @error_messages, { >+ hold_id => $hold->id, >+ success => 0, >+ original_biblio => $original_biblio, >+ target_biblio => $target_biblio, >+ error => $result->{error}, >+ }; >+ } >+ } >+ >+ #Fix the priority on the original record >+ C4::Reserves::_FixPriority( { biblionumber => $original_biblionumber } ); >+ >+ $template->param( >+ hold_move_successes => \@success_messages, >+ hold_move_failures => \@error_messages, >+ ); >+ > } elsif ( $op eq 'cud-cancel' ) { > my $reserve_id = $input->param('reserve_id'); > my $cancellation_reason = $input->param("cancellation-reason"); >-- >2.39.5
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 31698
:
179226
|
179235
|
179236
|
179279
|
179287
|
179290
|
179291
|
179292
|
179295
|
179704
|
179711
|
179713
|
181222
|
181223
|
181224
|
181225
|
181226
|
181228
|
181229
|
181230
|
181231
|
181232
|
181233
|
181234
|
181929
|
181930
|
181944
|
181945
|
181946
|
181947
|
181948
|
181951
|
181952
|
181953
|
181954
|
181955
|
181956
|
181957
|
181958
|
181959
|
181960
|
181961
|
181962
|
182639
|
182640
|
182641
|
184750
|
185050
|
185064
|
185065
|
185066
|
185067
|
185068
|
185069
|
185070
|
185121
|
185379
|
185380
|
185607
|
185608
|
185609
|
185610
|
185611
|
185612
|
185613
|
185614
|
185615
|
185616
|
185753
|
185754
|
185755
|
185756
|
185757
|
185758
|
185759
|
185760
|
185761
|
185762
|
185763
|
185764
|
185765
|
185766
|
185767
|
185768
|
185769
|
185770
|
185771
|
185772
|
185773
|
185774
|
185775
|
185776
|
185834