View | Details | Raw Unified | Return to bug 36135
Collapse All | Expand All

(-)a/admin/columns_settings.yml (-1 / +46 lines)
Lines 1987-1993 modules: Link Here
1987
              columnname: suspend
1987
              columnname: suspend
1988
            -
1988
            -
1989
              columnname: print_slip
1989
              columnname: print_slip
1990
1990
      holds_to_modify:
1991
        default_display_length: 20
1992
        columns:
1993
            -
1994
              columnname: checkbox
1995
              cannot_be_toggled: 1
1996
              cannot_be_modified: 1
1997
            -
1998
              columnname: expiration_date
1999
            -
2000
              columnname: title
2001
            -
2002
              columnname: barcode
2003
            -
2004
              columnname: patron
2005
            -
2006
              columnname: status
2007
            -
2008
              columnname: pickup_library
2009
            -
2010
              columnname: suspended
2011
            -
2012
              columnname: suspended_until
2013
            -
2014
              columnname: notes
2015
      modified_holds:
2016
        default_display_length: 20
2017
        columns:
2018
            -
2019
              columnname: expiration_date
2020
            -
2021
              columnname: title
2022
            -
2023
              columnname: barcode
2024
            -
2025
              columnname: patron
2026
            -
2027
              columnname: status
2028
            -
2029
              columnname: pickup_library
2030
            -
2031
              columnname: suspended
2032
            -
2033
              columnname: suspended_until
2034
            -
2035
              columnname: notes
1991
    holdsratios:
2036
    holdsratios:
1992
      holds-ratios:
2037
      holds-ratios:
1993
        default_display_length: 20
2038
        default_display_length: 20
(-)a/api/v1/swagger/definitions/hold.yaml (+5 lines)
Lines 135-139 properties: Link Here
135
      - object
135
      - object
136
      - "null"
136
      - "null"
137
    description: Pickup library
137
    description: Pickup library
138
  patron:
139
    type:
140
      - object
141
      - "null"
142
    description: Patron for hold
138
143
139
additionalProperties: false
144
additionalProperties: false
(-)a/api/v1/swagger/paths/holds.yaml (+2 lines)
Lines 105-110 Link Here
105
            - biblio
105
            - biblio
106
            - deleted_biblio
106
            - deleted_biblio
107
            - pickup_library
107
            - pickup_library
108
            - item
109
            - patron
108
        collectionFormat: csv
110
        collectionFormat: csv
109
    produces:
111
    produces:
110
      - application/json
112
      - application/json
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/modals/batch_modify_hold_errors.inc (+17 lines)
Line 0 Link Here
1
<!-- Batch modify hold error modal -->
2
<div class="modal fade" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="errorModalLabel">
3
    <div class="modal-dialog">
4
        <div class="modal-content">
5
            <div class="modal-header">
6
                <h1 class="modal-title" id="errorModalLabel">Errors</h1>
7
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
8
            </div>
9
            <div class="modal-body">
10
                <ul id="errorList"></ul>
11
            </div>
12
            <div class="modal-footer">
13
                <button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
14
            </div>
15
        </div>
16
    </div>
17
</div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc (+3 lines)
Lines 39-44 Link Here
39
            [% IF ( CAN_user_tools_batch_extend_due_dates ) %]
39
            [% IF ( CAN_user_tools_batch_extend_due_dates ) %]
40
                <li><a href="/cgi-bin/koha/tools/batch_extend_due_dates.pl">Batch extend due dates</a></li>
40
                <li><a href="/cgi-bin/koha/tools/batch_extend_due_dates.pl">Batch extend due dates</a></li>
41
            [% END %]
41
            [% END %]
42
            [% IF ( CAN_user_tools_batch_modify_holds ) %]
43
                <li><a href="/cgi-bin/koha/tools/batch_modify_holds.pl">Batch modify holds</a></li>
44
            [% END %]
42
            [% IF ( CAN_user_tools_moderate_tags ) %]
45
            [% IF ( CAN_user_tools_moderate_tags ) %]
43
                <li><a href="/cgi-bin/koha/tags/review.pl">Tags</a></li>
46
                <li><a href="/cgi-bin/koha/tags/review.pl">Tags</a></li>
44
            [% END %]
47
            [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reports/guided_reports_start.tt (-4 / +28 lines)
Lines 1071-1076 Link Here
1071
                                    >
1071
                                    >
1072
                                </form>
1072
                                </form>
1073
1073
1074
                                <form method="post" action="/cgi-bin/koha/tools/batch_modify_holds.pl" id="batch_modify_holds" target="_blank">
1075
                                    [% INCLUDE 'csrf-token.inc' %]
1076
                                    <input type="hidden" name="op" value="cud-form" />
1077
                                    <textarea style="display:none" name="reserve_ids_list" id="reserve_ids_list">
1078
                                    [%# Preserve the whitespace of the following textarea in order to format the values correctly %]
1079
                                        [%- reserve_ids = PROCESS batch_list results=results batch_type='reserve_id' | trim | html %][% IF reserve_ids %][% SET batch_reserve_ids = 1 %][% reserve_ids | html %][% END -%]
1080
                                    </textarea
1081
                                    >
1082
                                </form>
1083
1074
                                [% BLOCK batch_list %]
1084
                                [% BLOCK batch_list %]
1075
                                    [%- FOREACH result IN results %]
1085
                                    [%- FOREACH result IN results %]
1076
                                        [%- FOREACH cells IN result.cells %]
1086
                                        [%- FOREACH cells IN result.cells %]
Lines 1095-1103 Link Here
1095
                                </form>
1105
                                </form>
1096
                                <!-- /#limitselect -->
1106
                                <!-- /#limitselect -->
1097
1107
1098
                                [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_borrowernumbers ) || ( unlimited_total > 10 && limit <= 1000 ) %]
1108
                                [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_borrowernumbers || batch_reserve_ids ) || ( unlimited_total > 10 && limit <= 1000 ) %]
1099
                                    <div id="toolbar" class="btn-toolbar">
1109
                                    <div id="toolbar" class="btn-toolbar">
1100
                                        [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_borrowernumbers ) %]
1110
                                        [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_borrowernumbers || batch_reserve_ids ) %]
1101
                                            <div class="btn-group">
1111
                                            <div class="btn-group">
1102
                                                <button class="btn btn-default btn-sm dropdown-toggle" type="button" id="batch_mod_menu" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
1112
                                                <button class="btn btn-default btn-sm dropdown-toggle" type="button" id="batch_mod_menu" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
1103
                                                    Batch operations with [% IF unlimited_total >= limit %][% results.size | html %][% ELSE %][% unlimited_total | html %][% END %] visible records
1113
                                                    Batch operations with [% IF unlimited_total >= limit %][% results.size | html %][% ELSE %][% unlimited_total | html %][% END %] visible records
Lines 1205-1216 Link Here
1205
                                                                >
1215
                                                                >
1206
                                                            </li>
1216
                                                            </li>
1207
                                                        [% END %]
1217
                                                        [% END %]
1218
                                                        [% IF header_ro.cell == 'reserve_id' || header_types.item( header_ro.cell ) == 'reserve_id' %]
1219
                                                            <li class="dropdown-header">Hold records</li>
1220
                                                            <li>
1221
                                                                <a
1222
                                                                    href="#"
1223
                                                                    data-submit="batch_modify_holds"
1224
                                                                    data-toggle="tooltip"
1225
                                                                    data-placement="right"
1226
                                                                    title="Send visible results to batch hold modification"
1227
                                                                    class="batch_op send_to_hold_mod dropdown-item"
1228
                                                                    >Batch hold modification</a
1229
                                                                >
1230
                                                            </li>
1231
                                                        [% END %]
1208
                                                    [% END # /FOREACH header_ro %]
1232
                                                    [% END # /FOREACH header_ro %]
1209
                                                </ul>
1233
                                                </ul>
1210
                                                <!-- /.dropdown-menu -->
1234
                                                <!-- /.dropdown-menu -->
1211
                                            </div>
1235
                                            </div>
1212
                                            <!-- /.dropdown -->
1236
                                            <!-- /.dropdown -->
1213
                                        [% END # /IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers ) %]
1237
                                        [% END # /IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_reserve_ids ) %]
1214
1238
1215
                                        [% IF ( unlimited_total > 10 && limit <= 1000 ) %]
1239
                                        [% IF ( unlimited_total > 10 && limit <= 1000 ) %]
1216
                                            <div class="btn-group">
1240
                                            <div class="btn-group">
Lines 1238-1244 Link Here
1238
                                            </div>
1262
                                            </div>
1239
                                        [% END # /IF ( unlimited_total > 10 && limit <= 1000 ) %]
1263
                                        [% END # /IF ( unlimited_total > 10 && limit <= 1000 ) %]
1240
1264
1241
                                        [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers ) %]
1265
                                        [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_reserve_ids ) %]
1242
                                            <a href="#" class="btn btn-link" id="toggle_auto_links">
1266
                                            <a href="#" class="btn btn-link" id="toggle_auto_links">
1243
                                                <i class="fa-solid fa-eye autolink" aria-hidden="true" style="display:none"></i>
1267
                                                <i class="fa-solid fa-eye autolink" aria-hidden="true" style="display:none"></i>
1244
                                                <i class="fa-solid fa-eye-slash autolink" aria-hidden="true"></i>
1268
                                                <i class="fa-solid fa-eye-slash autolink" aria-hidden="true"></i>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_modify_holds.tt (+716 lines)
Line 0 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% SET footerjs = 1 %]
4
[% USE Branches %]
5
[% USE Categories %]
6
[% USE KohaDates %]
7
[% USE ItemTypes %]
8
[% USE TablesSettings %]
9
[% USE To %]
10
[% PROCESS 'html_helpers.inc' %]
11
12
[% INCLUDE 'doc-head-open.inc' %]
13
<title>Batch modify holds &rsaquo; Tools &rsaquo; Koha</title>
14
[% INCLUDE 'doc-head-close.inc' %]
15
[% Asset.css("css/humanmsg.css") | $raw %]
16
</head>
17
18
<body id="tools_batch_extend_due_dates" class="tools">
19
[% WRAPPER 'header.inc' %]
20
    [% INCLUDE 'cat-search.inc' %]
21
    [% INCLUDE modals/batch_modify_hold_errors.inc %]
22
[% END %]
23
[% WRAPPER 'sub-header.inc' %]
24
    [% WRAPPER breadcrumbs %]
25
        [% WRAPPER breadcrumb_item %]
26
            <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a>
27
        [% END %]
28
        [% IF ( view == 'form' || view == 'report' ) %]
29
            [% WRAPPER breadcrumb_item %]
30
                <a href="/cgi-bin/koha/tools/batch_modify_holds.pl">Batch modify holds</a>
31
            [% END %]
32
        [% ELSE %]
33
            [% WRAPPER breadcrumb_item bc_active= 1 %]
34
                <span>Batch modify holds</span>
35
            [% END %]
36
        [% END %]
37
    [% END #/ WRAPPER breadcrumbs %]
38
[% END #/ WRAPPER sub-header.inc %]
39
40
<div class="main container-fluid">
41
    <div class="row">
42
        <div class="col-sm-10 col-sm-push-2 order-sm-1">
43
            <main>
44
                <h1>Batch modify holds</h1>
45
                [% IF view == "cud-form" %]
46
                    <div id="modify_holds_search">
47
                        <form method="get" action="/cgi-bin/koha/tools/batch_modify_holds.pl" id="modify_holds_form">
48
                            <fieldset class="rows">
49
                                <legend>Hold search criteria:</legend>
50
                                <ol>
51
                                    <li>
52
                                        <label for="expirationdate_from">Expiration date from:</label>
53
                                        <input type="text" size="10" id="expirationdate_from" name="expirationdate_from" class="flatpickr" />
54
                                    </li>
55
                                    <li>
56
                                        <label for="expirationdate_to">Expiration date to:</label>
57
                                        <input type="text" size="10" id="expirationdate_to" name="expirationdate_to" class="flatpickr" />
58
                                    </li>
59
                                    <li>
60
                                        <label for="branchcodes">Libraries:</label>
61
                                        <select name="branchcodes" id="branchcodes" multiple="multiple">
62
                                            [% PROCESS options_for_libraries libraries => Branches.all(selected => "") %]
63
                                        </select>
64
                                    </li>
65
                                    <li>
66
                                        <label for="found_status">Found status:</label>
67
                                        <select name="found_status" id="found_status" multiple="multiple">
68
                                            <option value="NULL">No status</option>
69
                                            <option value="T">In transit</option>
70
                                            <option value="P">In processing</option>
71
                                            <option value="W">Waiting</option>
72
                                        </select>
73
                                    </li>
74
                                    <li>
75
                                        <label for="suspend_status">Suspended:</label>
76
                                        <select name="suspend_status" id="suspend_status">
77
                                            <option value="none" selected="selected"></option>
78
                                            <option value="0">Not suspended</option>
79
                                            <option value="1">Suspended</option>
80
                                        </select>
81
                                    </li>
82
                                    <li>
83
                                        <label for="suspend_until_from">Suspended until from:</label>
84
                                        <input type="text" size="10" id="suspend_until_from" name="suspend_until_from" class="flatpickr" data-date_to="suspend_until_to" />
85
                                    </li>
86
                                    <li>
87
                                        <label for="suspend_until_to">Suspended until to:</label>
88
                                        <input type="text" size="10" id="suspend_until_to" name="suspend_until_to" class="flatpickr" />
89
                                    </li>
90
                                    <li>
91
                                        <label for="holdnotes">Hold note:</label>
92
                                        <input type="text" id="holdnotes" name="holdnotes" />
93
                                    </li>
94
                                </ol>
95
                            </fieldset>
96
                            <fieldset class="action">
97
                                <input type="submit" class="btn btn-primary" value="Search" />
98
                            </fieldset>
99
                        </form>
100
                        <!-- /#modify_holds_form -->
101
                    </div>
102
                    <!-- /#modify_holds_search -->
103
                [% ELSIF view == "report" %]
104
                    <div id="modified_holds_results-wrapper">
105
                        <div id="modified_holds_results" class="page-section">
106
                            <table id="modified_holds"></table>
107
                        </div>
108
                        <!-- /#modified_holds_results -->
109
                        <fieldset class="action">
110
                            <a href="/cgi-bin/koha/tools/batch_modify_holds.pl">Return to batch hold modification</a>
111
                        </fieldset>
112
                    </div>
113
                    <!-- /#modified_holds_results-wrapper -->
114
                [% END %]
115
                <div id="modify_holds_results-wrapper" style="display:none;">
116
                    <div id="toolbar" class="btn-toolbar">
117
                        <a href="#" id="edit_search" class="btn btn-default"><i class="fa-solid fa-pencil" aria-hidden="true"></i> Edit search</a>
118
                    </div>
119
                    <!-- /#toolbar -->
120
                    <form action="/cgi-bin/koha/tools/batch_modify_holds.pl" method="post" id="process_mods">
121
                        [% INCLUDE 'csrf-token.inc' %]
122
                        <div id="modify_holds_results" class="page-section">
123
                            <div id="searchheader" class="searchheader">
124
                                <a id="select_all" href="#" class="btn btn-link"><i class="fa fa-check"></i> Select all visible rows</a>
125
                                | <a id="clear_all" href="#" class="btn btn-link"><i class="fa fa-times"></i> Clear selections</a>
126
                                <div id="table_search_selections" class="btn-group" style="display:none;">
127
                                    <span></span>
128
                                    <a href="#" id="clear-row-selection"><i class="fa fa-times"></i> Clear</a>
129
                                </div>
130
                            </div>
131
                            <!-- /#searchheader -->
132
                            <h3>Holds found for: <span class="searchpattern"></span></h3>
133
                            <table id="holds_to_modify"></table>
134
                        </div>
135
                        <!-- /#modify_holds_results -->
136
                        <div id="modify_holds_form_options" class="page-section">
137
                            <h2>Modify holds</h2>
138
                            <table id="modify_holds_options">
139
                                <thead>
140
                                    <tr>
141
                                        <th>New expiration date</th>
142
                                        <th>New pickup library</th>
143
                                        <th>Suspend holds</th>
144
                                        <th>Suspend until</th>
145
                                        <th>New hold note</th>
146
                                        <th>Clear hold notes</th>
147
                                    </tr>
148
                                </thead>
149
                                <tbody>
150
                                    <tr>
151
                                        <td>
152
                                            <input type="text" id="new_expiration_date" name="new_expiration_date" class="flatpickr" data-flatpickr-futuredate="true" />
153
                                        </td>
154
                                        <td>
155
                                            <select name="new_pickup_loc" id="new_pickup_loc">
156
                                                <option value="" selected="selected"></option>
157
                                                [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ selected => "" }) %]
158
                                            </select>
159
                                        </td>
160
                                        <td>
161
                                            <select id="new_suspend_status" name="new_suspend_status">
162
                                                <option value="" selected="selected"></option>
163
                                                <option value="0">Not suspended</option>
164
                                                <option value="1">Suspend</option>
165
                                            </select>
166
                                        </td>
167
                                        <td>
168
                                            <input type="text" id="new_suspend_date" name="new_suspend_date" class="flatpickr" data-flatpickr-futuredate="true" />
169
                                        </td>
170
                                        <td>
171
                                            <input type="text" id="new_hold_note" name="new_hold_note" />
172
                                        </td>
173
                                        <td>
174
                                            <input class="selection" type="checkbox" id="clear_hold_notes" name="clear_hold_notes" value="1" />
175
                                        </td>
176
                                    </tr>
177
                                </tbody>
178
                            </table>
179
                            <!-- /#modify_holds_options -->
180
                        </div>
181
                        <!-- /#modify_holds_options -->
182
                        <fieldset class="action">
183
                            <input type="hidden" name="op" value="cud-modify" />
184
                            <input type="submit" class="btn btn-primary" value="Modify holds" />
185
                            <a class="cancel" href="/cgi-bin/koha/tools/batch_modify_holds.pl">Cancel</a>
186
                        </fieldset>
187
                    </form>
188
                    <!-- /#process_mods -->
189
                </div>
190
                <!-- /#modify_holds_results-wrapper -->
191
            </main>
192
        </div>
193
        <!-- /.col-sm-10 col-sm-push-2 -->
194
        <div class="col-sm-2 col-sm-pull-10">
195
            <aside> [% INCLUDE 'tools-menu.inc' %] </aside>
196
        </div>
197
        <!-- /.col-sm-2 col-sm-pull-10 -->
198
    </div>
199
    [% MACRO jsinclude BLOCK %]
200
        [% Asset.js("js/tools-menu.js") | $raw %]
201
        [% INCLUDE 'calendar.inc' %]
202
        [% INCLUDE 'datatables.inc' %]
203
        [% INCLUDE 'select2.inc' %]
204
        [% INCLUDE 'js-patron-format.inc' %]
205
        [% Asset.js("lib/jquery/plugins/humanmsg.js") | $raw %]
206
        <script>
207
            let hide_patron_name = [% IF Koha.Preference('HidePatronName') %]true[% ELSE %]false[% END %];
208
            let hold_ids = [% IF updated_holds %] [% updated_holds | $raw %] [% ELSE %] [] [% END %];
209
            let hold_ids_from_sql = [% IF reserve_ids_list  %] [% reserve_ids_list  | $raw %] [% ELSE %] [] [% END %];
210
211
            $(document).ready(function() {
212
213
                // Apply select2 to all select fields having a "multiple" attribute
214
                let selectFields = document.querySelectorAll('select[multiple]');
215
                selectFields.forEach((selectField) => {
216
                    selectField.style.minWidth = '200px';
217
                    $(selectField).select2();
218
                });
219
220
                //Empty local storage from previous selections
221
                localStorage.removeItem("holds_modify_selections");
222
223
                $("#holds_to_modify").on("change", "input[type='checkbox']", function(){
224
                    var holds_modify_selections = JSON.parse(localStorage.getItem("holds_modify_selections")) || [];
225
                    var hold_id = $(this).val();
226
                    if ($(this).prop("checked")) {
227
228
                        hold_data = {
229
                            hold_id: hold_id,
230
                            found_status: $(this).closest('tr').find('.found_status').children('span').data('found-status'),
231
                            suspended: $(this).closest('tr').find('.suspended').children('span').data('suspended'),
232
                        }
233
234
                        holds_modify_selections.push(hold_data);
235
                        localStorage.setItem("holds_modify_selections", JSON.stringify(holds_modify_selections));
236
                        showHoldSelections(holds_modify_selections.length);
237
                    } else {
238
                        var filtered = holds_modify_selections.filter(function( hold_obj ){
239
                            return hold_obj.hold_id !== hold_id;
240
                        });
241
                        if( filtered.length > 0 ){
242
                            localStorage.setItem("holds_modify_selections", JSON.stringify( filtered ));
243
                            holds_modify_selections = filtered;
244
                            showHoldSelections( filtered.length );
245
                        } else {
246
                            holds_modify_selections = [];
247
                            localStorage.removeItem("holds_modify_selections");
248
                            showHoldSelections( 0 );
249
                        }
250
                    }
251
                });
252
253
                $("#select_all").on("click", function(e){
254
                    e.preventDefault();
255
                    $("#holds_to_modify input[type='checkbox']").each(function(){
256
                        $(this).prop("checked", true).change();
257
                    });
258
                });
259
260
                $("#clear_all").on("click", function(e){
261
                    e.preventDefault();
262
                    $("#holds_to_modify input[type='checkbox']").each(function(){
263
                        $(this).prop("checked", false).change();
264
                    });
265
                });
266
267
                $("#clear-row-selection").on("click", function (e) {
268
                    e.preventDefault();
269
                    $("input[type='checkbox']").prop("checked", false).change();
270
                    localStorage.removeItem("holds_modify_selections");
271
                    $("#table_search_selections").hide();
272
                });
273
274
                $('#modify_holds_form').submit(function() {
275
                    var modify_holds_form = $(this);
276
                    search_holds(modify_holds_form);
277
                    $("#modify_holds_search").hide();
278
                    $('#edit_search').show();
279
                    return false;
280
                });
281
282
                if(hold_ids_from_sql.length > 0){
283
                    $('#modify_holds_form').trigger('submit');
284
                }
285
286
                $("#edit_search").on("click", function(e){
287
                    e.preventDefault();
288
                    $('#modify_holds_search').show();
289
                    $('#edit_search').hide();
290
                });
291
292
                function showHoldSelections( number ){
293
                    if (number === 0) {
294
                        $("#table_search_selections").hide();
295
                    } else {
296
                        $("#table_search_selections")
297
                            .show()
298
                            .find("span")
299
                            .text(__("Holds selected: %s").format(number));
300
                    }
301
                }
302
303
                function search_holds(modify_holds_form){
304
                    var table_settings = [% TablesSettings.GetTableSettings( 'circ', 'holds', 'holds_to_modify', 'json' ) | $raw %];
305
                    var searchpattern = "";
306
                    var filters = {
307
                        "me.expirationdate": function(){
308
                            var expirationdate_from = modify_holds_form.find("#expirationdate_from").val();
309
                            var expirationdate_to   = modify_holds_form.find("#expirationdate_to").val();
310
311
                            if (expirationdate_from && expirationdate_to){
312
                                searchpattern += _("expiration date between ") + expirationdate_from + " and " + expirationdate_to + " ";
313
                                return {"-between": [expirationdate_from, expirationdate_to]};
314
                            }
315
                            else if (expirationdate_from && !expirationdate_to){
316
                                searchpattern += _("expiration date from ") + expirationdate_from + " ";
317
                                return {">=": expirationdate_from };
318
                            } else if (!expirationdate_from && expirationdate_to){
319
                                searchpattern += _("expiration date to ") + expirationdate_to + " ";
320
                                return {"<=": expirationdate_to };
321
                            }
322
                        },
323
                        "me.branchcode": function(){
324
                            var branchcodes = modify_holds_form.find("#branchcodes").val();
325
                            if( branchcodes.length > 0 ){
326
                                searchpattern += _("from libraries ");
327
                                var selected_libraries = modify_holds_form.find("#branchcodes option:selected");
328
                                $.each(selected_libraries, function(key, value) {
329
                                    searchpattern += value.text + " ";
330
                                });
331
                                return {"-in": branchcodes };
332
                            }
333
                        },
334
                        "me.status": function(){
335
                            var found_status = modify_holds_form.find("#found_status").val();
336
                            if( found_status.length > 0 ){
337
                                var filters = [{"-in": found_status}];
338
                                if(jQuery.inArray("NULL", found_status) !== -1){
339
                                    filters.push({"=": null});
340
                                }
341
                                searchpattern += _("found status in ");
342
                                $.each(found_status, function(key, value) {
343
                                    let status = _("No status ");
344
                                    if ( value == "T" ) {
345
                                        status = _("In transit ");
346
                                    } else if( value == "P" ) {
347
                                        status = _("In processing ");
348
                                    } else if( value == "W" ) {
349
                                        status = _("Waiting ");
350
                                    }
351
                                    searchpattern += status;
352
                                });
353
                                return filters;
354
                            }
355
                        },
356
                        "me.suspended": function(){
357
                            var suspend_status = modify_holds_form.find("#suspend_status").val();
358
359
                            if(suspend_status != "none"){
360
                                var suspended_text = suspend_status == 0 ? _("Not suspended") : _("Suspended");
361
                                searchpattern += _("with suspend status ") + suspended_text;
362
363
                                return {"=": suspend_status};
364
                            }
365
                        },
366
                        "me.suspend_until": function(){
367
                            var suspend_until_from  = modify_holds_form.find("#suspend_until_from").val();
368
                            var suspend_until_to    = modify_holds_form.find("#suspend_until_to").val();
369
370
                            searchpattern += suspend_until_from && suspend_until_to ? _("suspend date between ")+ suspend_until_from +" and "+suspend_until_to : "";
371
372
                            return suspend_until_from && suspend_until_to ? {"-between": [suspend_until_from, suspend_until_to]} : "";
373
                        },
374
                        "me.notes": function(){
375
                            var holdnotes = modify_holds_form.find("#holdnotes").val();
376
377
                            searchpattern += holdnotes ? _("hold notes like ") + holdnotes : "";
378
379
                            return holdnotes ? {"-like": "%"+holdnotes+"%"} : "";
380
                        },
381
                        "me.hold_id": function(){
382
                            return hold_ids_from_sql.length > 0 ? { "-in": hold_ids_from_sql } : "";
383
                        }
384
                    }
385
386
                    var holds_modify_table = $("#holds_to_modify").kohaTable({
387
                        "ajax": {
388
                            "url": "/api/v1/holds"
389
                        },
390
                        "embed": [
391
                            "biblio",
392
                            "item",
393
                            "pickup_library",
394
                            "patron"
395
                        ],
396
                        "destroy": true,
397
                        "autoWidth": false,
398
                        "processing": true,
399
                        "columns": [
400
                            {
401
                                "data": "hold_id",
402
                                "name": "checkbox",
403
                                "orderable": false,
404
                                "render": function(data, type, row, meta) {
405
                                    return '<input class="selection" type="checkbox" value="'+ encodeURIComponent(data) +'"/>'
406
                                }
407
                            },
408
                            {
409
                                "data": "expiration_date",
410
                                "name": "expiration_date",
411
                                "type": "date",
412
                                "title": _("Expiration date"),
413
                                "orderable": true,
414
                                "render": function( data, type, row, meta ) {
415
                                    return $date(data);
416
                                }
417
                            },
418
                            {
419
                                "data": "biblio.title",
420
                                "name": "title",
421
                                "title": _("Title"),
422
                                "orderable": true,
423
                                "render": function( data, type, row, meta ) {
424
                                    return '<a href="/cgi-bin/koha/catalogue/detail.pl?' +
425
                                    'biblionumber=' + encodeURIComponent( row.biblio_id ) + '">' + escape_str( data ) + '</a>';
426
                                }
427
                            },
428
                            {
429
                                "data": "item.external_id",
430
                                "name": "barcode",
431
                                "title": _("Barcode"),
432
                                "defaultContent": _(""),
433
                                "orderable": true,
434
                                "render": function( data, type, row, meta ) {
435
                                    if( row.item ){
436
                                        let item_id = encodeURIComponent( row.item_id );
437
                                        let biblio_id = encodeURIComponent( row.biblio_id );
438
                                        return '<a href="/cgi-bin/koha/catalogue/moredetail.pl?' +
439
                                        'itemnumber='+ item_id +'&biblionumber='+ biblio_id +'&' +
440
                                        'bi='+ biblio_id +'#item'+ item_id +'">' + escape_str( data ) + '</a>'
441
                                    }
442
                                }
443
                            },
444
                            {
445
                                "data": "patron_id",
446
                                "name": "patron",
447
                                "title": _("Patron"),
448
                                "orderable": true,
449
                                "render": function( data, type, row, meta ) {
450
                                    let patron_to_html = $patron_to_html(row.patron, { url: true, display_cardnumber: true, hide_patron_name });
451
                                    return patron_to_html;
452
                                }
453
                            },
454
                            {
455
                                "data": "status",
456
                                "name": "status",
457
                                "title": _("Status"),
458
                                "className": "found_status",
459
                                "orderable": true,
460
                                "render": function( data, type, row, meta ) {
461
                                    let status = _("No status");
462
                                    if ( data == "T" ) {
463
                                        status = _("In transit");
464
                                    } else if( data == "P" ) {
465
                                        status = _("In processing");
466
                                    } else if( data == "W" ) {
467
                                        status = _("Waiting");
468
                                    }
469
                                    return "<span data-found-status='" + escape_str( data ) + "'>" + status + "</span>";
470
                                }
471
                            },
472
                            {
473
                                "data": "pickup_library_id",
474
                                "name": "pickup_library",
475
                                "title": _("Hold pickup library"),
476
                                "orderable": true,
477
                                "render": function( data, type, row, meta ) {
478
                                    return escape_str( row.pickup_library.name );
479
                                }
480
                            },
481
                            {
482
                                "data": "suspended",
483
                                "name": "suspended",
484
                                "className": "suspended",
485
                                "title": _("Suspended"),
486
                                "orderable": true,
487
                                "render": function( data, type, row, meta ) {
488
                                    var suspended_string = data == 0 ? _("No") : _("Yes");
489
                                    return "<span data-suspended='" + data + "'>" + suspended_string + "</span>";
490
                                }
491
                            },
492
                            {
493
                                "data": "suspended_until",
494
                                "name": "suspended_until",
495
                                "title": _("Suspended until"),
496
                                "orderable": true,
497
                                "render": function( data, type, row, meta ) {
498
                                    return $date( data );
499
                                }
500
                            },
501
                            {
502
                                "data": "notes",
503
                                "name": "notes",
504
                                "title": _("Notes"),
505
                                "orderable": true,
506
                                "render": function( data, type, row, meta ) {
507
                                    return escape_str( data );
508
                                }
509
                            }
510
                        ],
511
                    }, table_settings, 0, filters);
512
513
                    $(".searchpattern").text(searchpattern);
514
515
                    $("#modify_holds_results-wrapper").show();
516
                }
517
518
                $("#process_mods").on('submit', function(e) {
519
520
                    var hold_ids = JSON.parse( localStorage.getItem("holds_modify_selections") ) || [];
521
522
                    $("#process_mods input[name='hold_id']").remove();
523
524
                    hold_ids.forEach(function(hold) {
525
                        $('<input>').attr({
526
                            type: 'hidden',
527
                            name: 'hold_id',
528
                            value: hold.hold_id
529
                        }).appendTo('#process_mods');
530
                    });
531
532
                    var errors = [];
533
534
                    var new_expiration_date = $("#new_expiration_date").val();
535
                    var new_pickup_loc      = $("#new_pickup_loc").val();
536
                    var new_suspend_status  = $("#new_suspend_status").val();
537
                    var new_suspend_date    = $("#new_suspend_date").val();
538
                    var new_hold_note       = $("#new_hold_note").val();
539
                    var clear_hold_notes    = $("#clear_hold_notes").val();
540
541
                    if ( hold_ids.length == 0 ) {
542
                        e.preventDefault();
543
                        errors.push(_("Please select at least one hold to process."));
544
                    }
545
546
                    if( new_pickup_loc || new_suspend_status || new_suspend_date ){
547
                        var found = false;
548
                        hold_ids.forEach(function(hold){
549
                            if( hold.found_status ){
550
                                e.preventDefault();
551
                                found = true;
552
                            }
553
                        });
554
                        if( ( new_suspend_status || new_suspend_date ) && found ){
555
                            errors.push(_("One or more holds have found status and can't be suspended."));
556
                        }
557
                        if( new_pickup_loc && found ){
558
                            errors.push(_("One or more holds have found status and their pick up location can't be changed."));
559
                        }
560
                    }
561
562
                    var suspended = false;
563
564
                    hold_ids.forEach(function(hold){
565
                        if( hold.suspened ){
566
                            suspended = true;
567
                        }
568
                    });
569
570
                    if( ( !new_suspend_status || new_suspend_status == "0" || !suspended ) && new_suspend_date ){
571
                        new_suspend_status = 1;
572
                    }
573
574
                    if (errors.length > 0) {
575
                        e.preventDefault();
576
                        $("#errorList").html(errors.map(error => `<li>${error}</li>`).join(""));
577
                        $("#errorModal").modal('show');
578
                        return false;
579
                    }
580
                    return true;
581
                });
582
583
                //Modified holds table
584
                var table_settings = [% TablesSettings.GetTableSettings( 'circ', 'holds', 'modified_holds', 'json' ) | $raw %];
585
                var filters = {
586
                    "me.hold_id": function(){
587
                        return {"-in": hold_ids};
588
                    }
589
                }
590
591
                var holds_modified_table = $("#modified_holds").kohaTable({
592
                    "ajax": {
593
                        "url": "/api/v1/holds"
594
                    },
595
                    "embed": [
596
                        "biblio",
597
                        "item",
598
                        "pickup_library",
599
                        "patron"
600
                    ],
601
                    "destroy": true,
602
                    "autoWidth": false,
603
                    "processing": true,
604
                    "columns": [
605
                        {
606
                            "data": "expiration_date",
607
                            "name": "expiration_date",
608
                            "type": "date",
609
                            "title": _("Expiration date"),
610
                            "orderable": true,
611
                            "render": function( data, type, row, meta ) {
612
                                return $date(data);
613
                            }
614
                        },
615
                        {
616
                            "data": "biblio.title",
617
                            "name": "title",
618
                            "title": _("Title"),
619
                            "orderable": true,
620
                            "render": function( data, type, row, meta ) {
621
                                return '<a href="/cgi-bin/koha/catalogue/detail.pl?' +
622
                                'biblionumber=' + encodeURIComponent( row.biblio_id ) + '">' + escape_str( data ) + '</a>';
623
                            }
624
                        },
625
                        {
626
                            "data": "item.external_id",
627
                            "name": "barcode",
628
                            "title": _("Barcode"),
629
                            "defaultContent": _(""),
630
                            "orderable": true,
631
                            "render": function( data, type, row, meta ) {
632
                                if( row.item ){
633
                                    let item_id = encodeURIComponent( row.item_id );
634
                                    let biblio_id = encodeURIComponent( row.biblio_id );
635
                                    return '<a href="/cgi-bin/koha/catalogue/moredetail.pl?' +
636
                                    'itemnumber='+ item_id +'&biblionumber='+ biblio_id +'&' +
637
                                    'bi='+ biblio_id +'#item'+ item_id +'">' + escape_str( data ) + '</a>'
638
                                }
639
                            }
640
                        },
641
                        {
642
                            "data": "patron_id",
643
                            "name": "patron",
644
                            "title": _("Patron"),
645
                            "orderable": true,
646
                            "render": function( data, type, row, meta ) {
647
                                let patron_to_html = $patron_to_html(row.patron, { url: true, display_cardnumber: true, hide_patron_name });
648
                                return patron_to_html;
649
                            }
650
                        },
651
                        {
652
                            "data": "status",
653
                            "name": "status",
654
                            "className": "found_status",
655
                            "title": _("Status"),
656
                            "orderable": true,
657
                            "render": function( data, type, row, meta ) {
658
                                let status = _("No status");
659
                                if ( data == "T" ) {
660
                                    status = _("In transit");
661
                                } else if( data == "P" ) {
662
                                    status = _("In processing");
663
                                } else if( data == "W" ) {
664
                                    status = _("Waiting");
665
                                }
666
                                return status;
667
                            }
668
                        },
669
                        {
670
                            "data": "pickup_library_id",
671
                            "name": "pickup_library",
672
                            "title": _("Hold pickup library"),
673
                            "orderable": true,
674
                            "render": function( data, type, row, meta ) {
675
                                return escape_str( row.pickup_library.name );
676
                            }
677
                        },
678
                        {
679
                            "data": "suspended",
680
                            "name": "suspended",
681
                            "title": _("Suspended"),
682
                            "orderable": true,
683
                            "render": function( data, type, row, meta ) {
684
                                return data == 0 ? _("No") : _("Yes");
685
                            }
686
                        },
687
                        {
688
                            "data": "suspended_until",
689
                            "name": "suspended_until",
690
                            "title": _("Suspended until"),
691
                            "orderable": true,
692
                            "render": function( data, type, row, meta ) {
693
                                return $date( data );
694
                            }
695
                        },
696
                        {
697
                            "data": "notes",
698
                            "name": "notes",
699
                            "title": _("Notes"),
700
                            "orderable": true,
701
                            "render": function( data, type, row, meta ) {
702
                                return escape_str( data );
703
                            }
704
                        }
705
                    ]
706
                }, table_settings, 0, filters);
707
708
                var modified_message = '<div class="alert alert-info">'+hold_ids.length+' hold(s) have been modified!</div>';
709
                $("#modified_holds_results-wrapper").prepend(modified_message);
710
711
            });
712
        </script>
713
    [% END %]
714
715
    [% INCLUDE 'intranet-bottom.inc' %]
716
</div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt (-1 / +6 lines)
Lines 29-35 Link Here
29
    <h1>Tools</h1>
29
    <h1>Tools</h1>
30
    <div class="row">
30
    <div class="row">
31
        <div class="col-sm-6">
31
        <div class="col-sm-6">
32
            [% IF ( CAN_user_tools_manage_patron_lists || CAN_user_clubs || CAN_user_tools_moderate_comments || CAN_user_tools_import_patrons  || CAN_user_tools_edit_notices || CAN_user_tools_edit_notice_status_triggers || CAN_user_tools_label_creator || CAN_user_tools_delete_anonymize_patrons  || CAN_user_tools_edit_patrons || CAN_user_tools_batch_extend_due_dates || CAN_user_tools_moderate_tags || CAN_user_tools_rotating_collections || ( CAN_user_tools_batch_upload_patron_images && Koha.Preference('patronimages') ) ) %]
32
            [% IF ( CAN_user_tools_manage_patron_lists || CAN_user_clubs || CAN_user_tools_moderate_comments || CAN_user_tools_import_patrons  || CAN_user_tools_edit_notices || CAN_user_tools_edit_notice_status_triggers || CAN_user_tools_label_creator || CAN_user_tools_delete_anonymize_patrons  || CAN_user_tools_edit_patrons || CAN_user_tools_batch_extend_due_dates || CAN_user_tools_batch_modify_holds || CAN_user_tools_moderate_tags || CAN_user_tools_rotating_collections || ( CAN_user_tools_batch_upload_patron_images && Koha.Preference('patronimages') ) ) %]
33
                <h3>Patrons and circulation</h3>
33
                <h3>Patrons and circulation</h3>
34
            [% END %]
34
            [% END %]
35
            <dl>
35
            <dl>
Lines 88-93 Link Here
88
                    <dd>Modify the due date checkouts in batch</dd>
88
                    <dd>Modify the due date checkouts in batch</dd>
89
                [% END %]
89
                [% END %]
90
90
91
                [% IF ( CAN_user_tools_batch_modify_holds ) %]
92
                    <dt><a href="/cgi-bin/koha/tools/batch_modify_holds.pl">Batch modify holds</a></dt>
93
                    <dd>Modify holds in batch</dd>
94
                [% END %]
95
91
                [% IF ( CAN_user_tools_moderate_tags ) %]
96
                [% IF ( CAN_user_tools_moderate_tags ) %]
92
                    <dt>
97
                    <dt>
93
                        <a href="/cgi-bin/koha/tags/review.pl">Tags</a>
98
                        <a href="/cgi-bin/koha/tags/review.pl">Tags</a>
(-)a/tools/batch_modify_holds.pl (-1 / +107 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Copyright 2024 Koha Development Team
6
#
7
# Koha is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as
9
# published by the Free Software Foundation; either version 3
10
# of the License, or (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General
18
# Public License along with Koha; if not, see
19
# <http://www.gnu.org/licenses>
20
21
use Modern::Perl;
22
use CGI;
23
24
use JSON qw( to_json );
25
26
use C4::Auth   qw( get_template_and_user );
27
use C4::Output qw( output_html_with_http_headers );
28
29
use Koha::DateUtils qw( dt_from_string );
30
use Koha::Holds;
31
32
my $input = CGI->new;
33
my $op    = $input->param('op') // q|cud-form|;
34
35
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
36
    {
37
        template_name => 'tools/batch_modify_holds.tt',
38
        query         => $input,
39
        type          => "intranet",
40
        flagsrequired => { tools => 'batch_modify_holds' },
41
    }
42
);
43
44
my @hold_ids;
45
46
if ( $op eq 'cud-form' ) {
47
    my $reserve_ids_list = $input->param('reserve_ids_list') || undef;
48
    if ($reserve_ids_list) {
49
        my @reserve_ids = split /\n/, $reserve_ids_list;
50
        $template->param( reserve_ids_list => to_json( \@reserve_ids ), );
51
    }
52
    $template->param( view => 'cud-form', );
53
} elsif ( $op eq 'cud-modify' ) {
54
    my $new_expiration_date = $input->param('new_expiration_date');
55
    my $new_pickup_loc      = $input->param('new_pickup_loc');
56
    my $new_suspend_status  = $input->param('new_suspend_status');
57
    my $new_suspend_date    = $input->param('new_suspend_date');
58
    my $new_hold_note       = $input->param('new_hold_note');
59
    my $clear_hold_notes    = $input->param('clear_hold_notes');
60
61
    @hold_ids = $input->multi_param('hold_id');
62
63
    my $holds_to_update =
64
        Koha::Holds->search( { reserve_id => { -in => \@hold_ids } }, { join => [ "item", "biblio" ] } );
65
66
    while ( my $hold = $holds_to_update->next ) {
67
68
        if ($new_expiration_date) {
69
            $hold->expirationdate($new_expiration_date)->store;
70
        }
71
72
        if ( $new_pickup_loc && ( $hold->branchcode ne $new_pickup_loc ) ) {
73
            $hold->branchcode($new_pickup_loc)->store;
74
        }
75
76
        if ( $new_suspend_status && $new_suspend_status ne "" && !$hold->is_found ) {
77
            $hold->suspend(1)->store;
78
            if ($new_suspend_date) {
79
                $hold->suspend_until($new_suspend_date)->store;
80
            } else {
81
                $hold->suspend_until(undef)->store;
82
            }
83
        } elsif ( !$new_suspend_status && $new_suspend_date ) {
84
            $hold->suspend(1)->store;
85
            $hold->suspend_until($new_suspend_date)->store;
86
        } else {
87
            $hold->suspend(0)->store;
88
            $hold->suspend_until(undef)->store;
89
        }
90
91
        if ($new_hold_note) {
92
            $hold->reservenotes($new_hold_note)->store;
93
        }
94
95
        if ($clear_hold_notes) {
96
            $hold->reservenotes(undef)->store;
97
        }
98
    }
99
100
    $template->param(
101
        updated_holds => to_json( \@hold_ids ),
102
        view          => 'report',
103
    );
104
105
}
106
107
output_html_with_http_headers $input, $cookie, $template->output;

Return to bug 36135