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

(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/bookings.inc (-2 / +135 lines)
Lines 66-72 Link Here
66
                                    </ol>
66
                                    </ol>
67
                                </fieldset>
67
                                </fieldset>
68
                            [% END %]
68
                            [% END %]
69
                        [% CASE %]
69
                        [% CASE 'add-multiple' %]
70
                        <div id="booking_result"></div>
71
                            <fieldset class="brief">
72
                                <input type="hidden" name="biblio_id" id="biblio-id" value="[% biblio.biblionumber %]">
73
                                <input type="hidden" name="start_date" id="booking-start_date" value="">
74
                                <input type="hidden" name="end_date" id="booking-end_date" value="">
75
                                <input type="hidden" id="items-hidden" name="items">
76
                                <ol>
77
                                    <li>
78
                                        <div>
79
                                            <label for="itemnumber">Add barcode :</label>
80
                                            <input type="text" id="barcode" name="barcode">
81
                                            <button type="button" class="btn btn-sm btn-link" >
82
                                                <i class="fa fa-fw fa-plus" aria-hidden="true" onclick="addItem()"></i>
83
                                            </button>
84
                                        </div>
85
                                        <ul id="items-list"></ul>
86
                                    </li>
87
                                    <button type="button" id="checks_bookings" name="opac-add-multiple-check" class="btn btn-sm btn-link" style="display:none" onclick="validateItems()">
88
                                        <i class="fa fa-fw fa-arrows-rotate" aria-hidden="true"></i>&nbsp;Validate items
89
                                    </button>
90
                                    <li>
91
                                        <label class="required" for="booking_pickup">Pickup at:</label>
92
                                        <select name="booking_pickup" id="pickup_library_id" required="required" disabled="true"></select>
93
                                        <span class="required">Required</span>
94
                                    </li>
95
                                    <li>
96
                                        <div id="period_fields">
97
                                            <label class="required" for="period">Booking dates: </label>
98
                                            <input type="text" id="period" name="period" class="flatpickr" data-flatpickr-futuredate="true" data-flatpickr-disable-shortcuts="true" required autocomplete="off">
99
                                            <span class="required">Required</span>
100
                                        </div>
101
                                        <div class="hint">Select the booking start date and end date</div>
102
                                    </li>
103
                                </ol>
104
                            </fieldset>
70
                    [% END %]
105
                    [% END %]
71
                </div>
106
                </div>
72
                <div class="modal-footer">
107
                <div class="modal-footer">
Lines 86-89 Link Here
86
            </div> <!-- /.modal-content -->
121
            </div> <!-- /.modal-content -->
87
        </form>
122
        </form>
88
    </div> <!-- /.modal-dialog -->
123
    </div> <!-- /.modal-dialog -->
89
</div> <!-- /.modal -->
124
</div> <!-- /.modal -->
125
<script>
126
function addItem() {
127
    const barcodeInput = document.getElementById('barcode');
128
    const itemsList = document.getElementById('items-list');
129
    const barcode = barcodeInput.value.trim();
130
    const errorDiv = document.getElementById('booking_result');
131
    const errorElement = document.createElement('div');
132
    errorElement.id = 'add_booking_error';
133
    errorElement.classList.add('alert', 'alert-danger');
134
    const observer = new MutationObserver(function() {
135
        if (itemsList.children.length === 0) {
136
            $("#pickup_library_id").prop("disabled", true);
137
            $("#checks_bookings").hide();
138
139
        } else {
140
            $("#pickup_library_id").prop("disabled", false);
141
            $("#checks_bookings").show();
142
        }
143
    });
144
    observer.observe(itemsList, {
145
        childList: true,
146
        subtree: true
147
    });
148
149
    if (barcode) {
150
        let targetItemInList = document.getElementById('booking_item-' + barcode);
151
        if (targetItemInList) {
152
            errorElement.textContent = 'Item already in the list.';
153
            errorDiv.appendChild(errorElement);
154
            return;
155
        }
156
        let biblioTitle;
157
        let itemRepresentation = $.ajax({
158
            url: "/api/v1/items/?external_id=" + barcode,
159
            type: "GET",
160
            dataType: "json",
161
            success: function (response) {
162
                if (!response[0].bookable) {
163
                    errorElement.textContent = 'Item not bookable';
164
                    errorDiv.replaceWith(errorElement);  
165
                } else {
166
                    let biblio_id = response[0].biblio_id;
167
                    let item_id = response[0].item_id;
168
                    let biblioRepresentation = $.ajax({
169
                        url: "/api/v1/biblios/" + biblio_id,
170
                        type: "GET",
171
                        dataType: "json",
172
                        success: function (response) {
173
                            if (response) {
174
                                biblioTitle = response.title;
175
                                const listItem = document.createElement('li');
176
                                listItem.setAttribute('id', 'booking_item-' + barcode);
177
                                listItem.dataset.biblio_id = biblio_id;
178
                                listItem.dataset.item_id = item_id;
179
                                listItem.textContent = `${item_id} - ${biblioTitle} (${barcode})`;
180
                                const deleteLink = document.createElement('a');
181
                                deleteLink.classList.add('fa', 'fa-fw', 'fa-times');
182
                                deleteLink.setAttribute('aria-hidden', 'true');
183
                                deleteLink.onclick = function () {
184
                                    deleteItem(listItem);
185
                                };
186
                                listItem.appendChild(deleteLink);
187
                                itemsList.appendChild(listItem);
188
                                barcodeInput.value = '';
189
                                updateItemList();
190
                            }
191
                        },
192
                        error: function (xhr, status, error) {
193
                            errorElement.textContent = 'Fetch failed: ' + error;
194
                            errorDiv.replaceWith(errorElement);
195
                        },
196
                    });
197
                }
198
            },
199
            error: function (xhr, status, error) {
200
                errorElement.textContent = 'Fetch failed: ' + error;
201
                errorDiv.replaceWith(errorElement);
202
            },
203
        });
204
    }
205
}
206
function updateItemList() {
207
    const itemsList = document.getElementById('items-list');
208
    const itemnumbers = [];
209
    const barcodes = itemsList.getElementsByTagName('li');
210
    for (let barcode of barcodes) {
211
        itemnumbers.push(barcode.textContent);
212
    }
213
    const itemsHidden = document.getElementById('items-hidden');
214
    itemsHidden.value = JSON.stringify(itemnumbers);
215
}
216
function deleteItem(listItem) {
217
    const itemsList = document.getElementById('items-list');
218
    itemsList.removeChild(listItem);
219
    $('#pickup_library_id').empty();
220
    updateItemList();
221
}
222
</script>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-bookings.tt (-2 / +4 lines)
Lines 67-81 Link Here
67
                        <div class="tab-content">
67
                        <div class="tab-content">
68
                            <div id="toolbar" class="toolbar clearfix">
68
                            <div id="toolbar" class="toolbar clearfix">
69
                                <div class="list-actions">
69
                                <div class="list-actions">
70
                                    [% SET action_method = biblio == '' ? 'add-multiple' : 'add' %]
70
                                    <button
71
                                    <button
71
                                        type="button"
72
                                        type="button"
72
                                        class="btn btn-sm btn-link"
73
                                        class="btn btn-sm btn-link"
73
                                        data-bs-toggle="modal"
74
                                        data-bs-toggle="modal"
74
                                        data-bs-target="#opac-add-booking-modal"
75
                                        data-bs-target="#opac-[%action_method %]-booking-modal"
75
                                    >
76
                                    >
76
                                        <i class="fa fa-fw fa-plus" aria-hidden="true"></i>&nbsp;New booking
77
                                        <i class="fa fa-fw fa-plus" aria-hidden="true"></i>&nbsp;New booking
77
                                    </button>
78
                                    </button>
78
                                    [% PROCESS 'modals/bookings.inc' action = 'add' %]
79
                                    [% PROCESS 'modals/bookings.inc' action = action_method %]
79
                                </div> <!-- / .list-actions -->
80
                                </div> <!-- / .list-actions -->
80
                            </div> <!-- / #toolbar -->
81
                            </div> <!-- / #toolbar -->
81
                            [% PROCESS 'bookings-table.inc' %]
82
                            [% PROCESS 'bookings-table.inc' %]
Lines 92-97 Link Here
92
    [% Asset.js("js/form-submit.js") | $raw %]
93
    [% Asset.js("js/form-submit.js") | $raw %]
93
    [% Asset.js("lib/vis-timeline/vis-timeline-graph2d.min.js") | $raw %]
94
    [% Asset.js("lib/vis-timeline/vis-timeline-graph2d.min.js") | $raw %]
94
    [% Asset.js("js/modals/place_booking.js") | $raw %]
95
    [% Asset.js("js/modals/place_booking.js") | $raw %]
96
    [% Asset.js("js/modals/place_multiple_bookings.js") | $raw %]
95
    [% Asset.js("lib/bootstrap/js/bootstrap.min.js") | $raw %]
97
    [% Asset.js("lib/bootstrap/js/bootstrap.min.js") | $raw %]
96
    [% INCLUDE 'select2.inc' %]
98
    [% INCLUDE 'select2.inc' %]
97
    [% INCLUDE 'calendar.inc' %]
99
    [% INCLUDE 'calendar.inc' %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/modals/place_multiple_bookings.js (-1 / +169 lines)
Line 0 Link Here
0
- 
1
function validateItems (){
2
    let $pickupSelect = $("#pickup_library_id");
3
    var bookingItems = $('#items-list li').map(function() {
4
        var barcode = $(this).attr('id').replace('booking_item-', '');
5
        var biblioId = $(this).data('biblio_id');
6
        var itemId = $(this).data('item_id');
7
        return {
8
            [barcode]: {
9
                biblionumber: biblioId,
10
                itemnumber: itemId
11
            }
12
        };
13
    }).get();
14
15
    var pickupLocations = [];
16
    var promises = bookingItems.map(function(item) {
17
        var barcode = Object.keys(item)[0];
18
        var biblionumber = item[barcode].biblionumber;
19
        
20
        return new Promise(function(resolve, reject) {
21
            addPickupLocation(pickupLocations, biblionumber, resolve, reject);
22
        });
23
    });
24
    Promise.all(promises).then(function() {
25
        $pickupSelect.empty();
26
        pickupLocations.forEach(el => {
27
            let option = $(
28
                '<option value="' +
29
                    el.id +
30
                    '">' +
31
                    el.name +
32
                    "</option>"
33
        );
34
            $pickupSelect.append(option);
35
        });
36
    }).catch(function(error) {
37
        console.log("An error occurred while fetching pickup locations: ", error);
38
    });
39
    $pickupSelect.prop('disabled', false);
40
41
    // Set flatpickr
42
    let periodPicker = $("#period").get(0)._flatpickr;
43
    periodPicker.set("mode", "range");
44
45
    // Set onChange for flatpickr
46
    let changeExists = periodPicker.config.onChange.filter(
47
        (f) => f.name === "periodChange"
48
    );
49
    if (changeExists.length === 0) {
50
        periodPicker.config.onChange.push(function periodChange(
51
            selectedDates,
52
            dateStr,
53
            instance
54
        ) {
55
            // Range set, update hidden fields and set available items
56
            if (selectedDates[0] && selectedDates[1]) {
57
                // set form fields from picker
58
                let picker_start = dayjs(selectedDates[0]);
59
                let picker_end = dayjs(selectedDates[1]).endOf(
60
                    "day"
61
                );
62
                $("#booking-start_date").val(
63
                    picker_start.toISOString()
64
                );
65
                $("#booking-end_date").val(
66
                    picker_end.toISOString()
67
                );
68
            }
69
        });
70
    }
71
}
72
73
function addPickupLocation(locations, biblionumber, resolve, reject) {
74
    let pickup_url = "/api/v1/biblios/" + biblionumber + "/pickup_locations";
75
    $.ajax({
76
        url: pickup_url,
77
        type: "GET",
78
        dataType: "json",
79
        data: {
80
            _order_by: "name",
81
            _per_page: "-1",
82
            patron_id: logged_in_user_id,
83
        },
84
        success: function(response) {
85
            var library_id = response[0].library_id;
86
            var library_name = response[0].name;
87
88
            if (!locations.some(location => location.id === library_id)) {
89
                locations.push({ id: library_id, name: library_name });
90
            }
91
            resolve();
92
        },
93
        error: function(xhr, status, error) {
94
            console.log("Pickup location fetch failed: ", error);
95
            reject(error);
96
        },
97
    });
98
}
99
100
$("#opac-add-multiple-form-booking").on("submit", function (e) {
101
    e.preventDefault();
102
103
    var url = "/api/v1/bookings";
104
105
    var items_list = $('#items-list li');
106
    var start_date = $("#booking-start_date").val();
107
    var end_date = $("#booking-end_date").val();
108
    var pickup_library_id = $("#pickup_library_id").val();
109
110
    var errors = [];
111
    var requests = [];
112
    var sendBookingRequest = function (item_id, biblio_id) {
113
        return $.post(
114
            url,
115
            JSON.stringify({
116
                start_date: start_date,
117
                end_date: end_date,
118
                biblio_id: biblio_id,
119
                item_id: item_id,
120
                patron_id: logged_in_user_id,
121
                pickup_library_id: pickup_library_id
122
            })
123
        );
124
    };
125
126
    items_list.each(function (i) {
127
        item_id = $(this).data('item_id');
128
        biblio_id = $(this).data('biblio_id');
129
        var request = sendBookingRequest(item_id, biblio_id);
130
131
        requests.push(request);
132
133
        request.fail(function (data) {
134
            var error = data.responseJSON.error;
135
            var errorMessage = error ? error : "Failure";
136
            errors.push(item_id + " - " + errorMessage);
137
        });
138
    });
139
140
    $.when.apply($, requests).done(function () {
141
        if (errors.length > 0) {
142
            $("#booking_result").replaceWith(
143
                '<div id="booking_result" class="alert alert-danger">' +
144
                errors.join("<br>") +
145
                "</div>"
146
            );
147
        } else {
148
            $("#opac-add-multiple-booking-modal").modal("hide");
149
            window.location.reload();
150
        }
151
    }).fail(function () {
152
        $("#booking_result").replaceWith(
153
            '<div id="booking_result" class="alert alert-danger">' +
154
            errors.join("<br>") +
155
            "</div>"
156
        );
157
    });
158
});
159
160
$("#opac-add-multiple-booking-modal").on("hidden.bs.modal", function (e) {
161
    $("#period").get(0)._flatpickr.clear();
162
    $("#booking-start_date").val("");
163
    $("#booking-end_date").val("");
164
    $("#booking_id").val("");
165
    // Reset pickup library select
166
    $("#pickup_library_id").val(null).trigger("change");
167
    $("#pickup_library_id").empty();
168
    $("#pickup_library_id").prop("disabled", true);
169
});

Return to bug 38493