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

(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc (+7 lines)
Lines 81-86 Link Here
81
                        </div> <!-- / .dropdown-menu -->
81
                        </div> <!-- / .dropdown-menu -->
82
                    </li> <!-- / .nav-item.dropdown -->
82
                    </li> <!-- / .nav-item.dropdown -->
83
                [% END # / IF virtualshelves %]
83
                [% END # / IF virtualshelves %]
84
                [% IF ( Koha.Preference( 'OpacTrustedCheckout' ) && loggedinusername ) %]
85
                    <li class="nav-item">
86
                        <a href="#" class="nav-link" title="Self check out" id="comenulink" role="button" data-toggle="modal" data-target="#checkoutModal">
87
                            <i id="checkout-icon" class="fa fa-barcode fa-icon-black" aria-hidden="true"></i> <span class="checkout-label">Self checkout</span>
88
                        </a>
89
                    </li>
90
                [% END %]
84
            </ul> <!-- / .navbar-nav -->
91
            </ul> <!-- / .navbar-nav -->
85
92
86
            [% IF Koha.Preference( 'opacuserlogin' ) == 1 ||  Koha.Preference( 'EnableOpacSearchHistory') || Koha.Preference( 'opaclanguagesdisplay' ) %]
93
            [% IF Koha.Preference( 'opacuserlogin' ) == 1 ||  Koha.Preference( 'EnableOpacSearchHistory') || Koha.Preference( 'opaclanguagesdisplay' ) %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/checkout.inc (+40 lines)
Line 0 Link Here
1
<!-- Checkout form hidden by default, used for modal window -->
2
<div class="modal" id="checkoutModal" tabindex="-1" role="dialog" aria-labelledby="checkoutModalLabel" aria-hidden="true">
3
    <div class="modal-dialog">
4
        <div class="modal-content">
5
            <div class="modal-header">
6
                <h3 class="modal-title" id="checkoutModalLabel">Self checkout</h3>
7
                <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close">
8
                    <span aria-hidden="true">&times;</span>
9
                </button>
10
            </div>
11
            <div class="modal-body">
12
                <div id="checkoutResults"></div>
13
                <div id="availabilityResult"></div>
14
                <div class="form-group">
15
                    <label for="checkout_barcode">Enter item barcode: </label>
16
                    <input type="text" name="checkout_barcode" id="checkout_barcode" required="required"></input>
17
                </div>
18
                <table id="checkoutsTable" class="table table-bordered table-striped" style="width:100%;">
19
                    <thead>
20
                      <tr>
21
                          <th>Barcode</th>
22
                          <th>Due date</th>
23
                      </tr>
24
                    </thead>
25
                    <tbody>
26
                    </tbody>
27
                    <tfoot>
28
                      <tr>                    
29
                        <td colspan="2" class="text-right"><span id="checkoutsCount"></span> items checked out</td>
30
                      </tr>
31
                    </tfoot>
32
                </table>
33
            </div>
34
            <div class="modal-footer">
35
                <button type="submit" id="checkoutSubmit" class="btn btn-primary">Submit</button>
36
                <button type="button" id="checkoutClose" class="btn btn-default" data-dismiss="modal">Close</button>
37
            </div>
38
        </div>
39
    </div>
40
</div>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-bottom.inc (+11 lines)
Lines 117-122 Link Here
117
    [% END # /IF OpacLangSelectorMode == 'both' || OpacLangSelectorMode == 'footer' %]
117
    [% END # /IF OpacLangSelectorMode == 'both' || OpacLangSelectorMode == 'footer' %]
118
[% END # / UNLESS is_popup %]
118
[% END # / UNLESS is_popup %]
119
119
120
[% IF ( Koha.Preference( 'OpacTrustedCheckout' ) && loggedinusername ) %]
121
    [% INCLUDE 'modals/checkout.inc' %]
122
[% END %]
123
120
<!-- JavaScript includes -->
124
<!-- JavaScript includes -->
121
[% Asset.js("lib/jquery/jquery-3.6.0.min.js") | $raw %]
125
[% Asset.js("lib/jquery/jquery-3.6.0.min.js") | $raw %]
122
[% Asset.js("lib/jquery/jquery-migrate-3.3.2.min.js") | $raw %]
126
[% Asset.js("lib/jquery/jquery-migrate-3.3.2.min.js") | $raw %]
Lines 124-129 Link Here
124
[% Asset.js("lib/bootstrap/js/bootstrap.bundle.min.js") | $raw %]
128
[% Asset.js("lib/bootstrap/js/bootstrap.bundle.min.js") | $raw %]
125
[% Asset.js("lib/fontfaceobserver.min.js") | $raw %]
129
[% Asset.js("lib/fontfaceobserver.min.js") | $raw %]
126
[% Asset.js("lib/enquire.min.js") | $raw %]
130
[% Asset.js("lib/enquire.min.js") | $raw %]
131
<script>
132
    let logged_in_user_id = "[% logged_in_user.borrowernumber | html %]";
133
</script>
127
[% Asset.js("js/global.js") | $raw %]
134
[% Asset.js("js/global.js") | $raw %]
128
[% IF ( OPACAmazonCoverImages || SyndeticsCoverImages ) %]
135
[% IF ( OPACAmazonCoverImages || SyndeticsCoverImages ) %]
129
    <script>
136
    <script>
Lines 288-293 $(document).ready(function() { Link Here
288
    }
295
    }
289
});
296
});
290
</script>
297
</script>
298
[% INCLUDE 'js-date-format.inc' %]
291
[% PROCESS jsinclude %]
299
[% PROCESS jsinclude %]
292
[% IF ( Koha.Preference('OPACUserJS') ) %]
300
[% IF ( Koha.Preference('OPACUserJS') ) %]
293
    <script>
301
    <script>
Lines 304-309 $(document).ready(function() { Link Here
304
        </script>
312
        </script>
305
    [% END %]
313
    [% END %]
306
[% END %]
314
[% END %]
315
[% IF ( Koha.Preference( 'OpacTrustedCheckout' ) && loggedinusername ) %]
316
    [% Asset.js("js/modals/checkout.js") | $raw %]
317
[% END %]
307
[% KohaPlugins.get_plugins_opac_js | $raw %]
318
[% KohaPlugins.get_plugins_opac_js | $raw %]
308
</body>
319
</body>
309
</html>
320
</html>
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js (-1 / +246 lines)
Line 0 Link Here
0
- 
1
$(document).ready(function() {
2
3
    let checkouts_count = 0;
4
    let current_item;
5
6
    function addResult(type, code, data) {
7
        let result = '';
8
        if (type == 'danger') {
9
            result += '<div class="alert alert-danger">';
10
        } else if (type == 'warning') {
11
            result += '<div class="alert alert-warning">';
12
        } else if (type == 'info') {
13
            result += '<div class="alert alert-info">';
14
        } else {
15
            result += '<div class="alert alert-success">';
16
        }
17
18
        if (code == 'NOT_FOUND') {
19
            result += _("Item '%s' not found").format(data);
20
        } else if (code == 'RENEW_ISSUE') {
21
            result += _("Item will be renewed").format(data);
22
        } else if (code == 'OTHER_CHARGES') {
23
            result += _("Your account currently has outstanding charges of '%s'").format(data);
24
        } else if (code == 'DEBT') {
25
            result += _("Your account is currently in debt by '%s'").format(data);
26
        } else if (code == 'ISSUED_TO_ANOTHER') {
27
            result += _("This item appears to be checked out to another patron, please return it to the desk");
28
        } else if (code == 'RESERVED' || code == 'RESERVED_WAITING') {
29
            result += _("This item appears to be reserved for another patron, please return it to the desk");
30
        } else if (code == 'TOO_MANY') {
31
            result += _("You have reached the maximum number of checkouts allowed on your account");
32
        } else if (code == 'AGE_RESTRICTION') {
33
            result += _("This item is age restricted");
34
        } else if (code == 'NO_MORE_RENEWALS') {
35
            result += _("Maximum renewals reached for this item");
36
        } else if (code == 'NOT_FOR_LOAN') {
37
            result += _("This item is not normally for loan, please select another or ask at the desk");
38
        } else if (code == 'WTHDRAWN') {
39
            result += _("This item is marked withdrawn, please select another or ask at the desk");
40
        } else if (code == 'EMPTY') {
41
            result += _("Please enter the barcode for the item you wish to checkout");
42
        } else {
43
            result += _("Message code '%s' with data '%s'").format(code, data);
44
        }
45
46
        result += '</div>';
47
        $('#availabilityResult').append(result);
48
    };
49
50
    function addCheckout(checkout) {
51
        // Alert that checkout was successful
52
        $('#checkoutResults').replaceWith('<div id="checkoutResults" class="alert alert-success">' + _("Item '%s' was checked out").format(current_item.external_id) + '</div>');
53
        // Cleanup input and unset readonly
54
        $('#checkout_barcode').val("").prop("readonly", false).focus();
55
        // Display checkouts table if not already visible
56
        $('#checkoutsTable').show();
57
        // Add checkout to checkouts table
58
        $('#checkoutsTable > tbody').append('<tr><td>' + current_item.external_id +'</td><td>'+ $date(checkout.due_date) +'</td></tr>');
59
        $('#checkoutsCount').html(++checkouts_count);
60
        // Reset to submission
61
        $('#checkoutConfirm').replaceWith('<button type="submit" id="checkoutSubmit" class="btn btn-primary">Submit</button>');
62
    };
63
64
    // On modal show, clear any prior results and set focus
65
    $('#checkoutModal').on('shown.bs.modal', function(e) {
66
        $('#checkoutResults').replaceWith('<div id="checkoutResults"></div>');
67
        $('#availabilityResult').replaceWith('<div id="availabilityResult"></div>');
68
        $('#checkoutsTable').hide();
69
        $('#checkout_barcode').val("").focus();
70
    });
71
72
    // On modal submit
73
    $('#checkoutModal').on('click', '#checkoutSubmit', function(e) {
74
75
        // Get item from barcode
76
        let external_id = $('#checkout_barcode').val();
77
        if ( external_id === '' ) {
78
            addResult('warning', 'EMPTY');
79
            return;
80
        }
81
82
        let item_id;
83
        let items = $.ajax({
84
            url: '/api/v1/public/items?external_id=' + external_id,
85
            dataType: 'json',
86
            type: 'GET'
87
        });
88
89
        $('#availabilityResult').replaceWith('<div id="availabilityResult"></div>');
90
91
        // Get availability of the item
92
        let availability = items.then(
93
            function(data, textStatus, jqXHR) {
94
                if (data.length == 1) {
95
                    current_item = data[0];
96
                    item_id = current_item.item_id;
97
                    return $.ajax({
98
                        url: '/api/v1/public/checkouts/availability?item_id=' + item_id + '&patron_id=' + logged_in_user_id,
99
                        type: 'GET',
100
                        contentType: "json"
101
                    });
102
                } else {
103
                    addResult('danger', 'NOT_FOUND', external_id);
104
                }
105
            },
106
            function(data, textStatus, jqXHR) {
107
                addResult('danger', 'NOT_FOUND', external_id);
108
                console.log('Items request failed with: ' + textStatus);
109
            }
110
        );
111
112
        let checkout = availability.then(
113
            function(data, textStatus, jqXHR) {
114
                let result;
115
                // blocked
116
                if (Object.keys(data.blockers).length >= 1) {
117
                    for (const key in data.blockers) {
118
                        if (data.blockers.hasOwnProperty(key)) {
119
                            addResult('danger', `${key}`, `${data.blockers[key]}`);
120
                            console.log(`${key}: ${data.blockers[key]}`);
121
                            $('#checkout_barcode').val("").prop("readonly", false).focus();
122
                        }
123
                    }
124
                }
125
                // requires confirmation
126
                else if (Object.keys(data.confirms).length >= 1) {
127
                    for (const key in data.confirms) {
128
                        if (data.confirms.hasOwnProperty(key)) {
129
                            addResult('warning', `${key}`, `${data.confirms[key]}`);
130
                        }
131
                    }
132
                    $('#checkout_barcode').prop("readonly", true);
133
                    $('#checkoutSubmit').replaceWith('<input type="hidden" id="item_id" value="' + item_id + '"><input type="hidden" id="confirm_token" value="' + data.confirmation_token + '"><button type="submit" id="checkoutConfirm" class="btn btn-warning">Confirm</button>');
134
                }
135
                // straight checkout
136
                else {
137
                    let params = {
138
                        "checkout_id": undefined,
139
                        "patron_id": logged_in_user_id,
140
                        "item_id": item_id,
141
                        "due_date": undefined,
142
                        "library_id": undefined,
143
                        "issuer_id": undefined,
144
                        "checkin_date": undefined,
145
                        "last_renewed_date": undefined,
146
                        "renewals_count": undefined,
147
                        "unseen_renewals": undefined,
148
                        "auto_renew": undefined,
149
                        "auto_renew_error": undefined,
150
                        "timestamp": undefined,
151
                        "checkout_date": undefined,
152
                        "onsite_checkout": false,
153
                        "note": undefined,
154
                        "note_date": undefined,
155
                        "note_seen": undefined,
156
                    };
157
                    result = $.ajax({
158
                        url: '/api/v1/public/checkouts',
159
                        type: 'POST',
160
                        data: JSON.stringify(params),
161
                        contentType: "json"
162
                    });
163
                }
164
165
                // warnings to display
166
                if (Object.keys(data.warnings).length >= 1) {
167
                    for (const key in data.warnings) {
168
                        if (data.warnings.hasOwnProperty(key)) {
169
                            addResult('info', `${key}`, `${data.warnings[key]}`);
170
                        }
171
                    }
172
                }
173
174
                // return a rejected promise if we've reached here
175
                return result ? result : $.Deferred().reject('Checkout halted');
176
            },
177
            function(data, textStatus, jqXHR) {
178
                console.log('Items request failed with: ' + textStatus);
179
                console.log(data);
180
            }
181
        );
182
183
        checkout.then(
184
            function(data, textStatus, jqXHR) {
185
                addCheckout(data);
186
                // data retrieved from url2 as provided by the first request
187
            },
188
            function(data, textStatus, jqXHR) {
189
                console.log("checkout.then failed");
190
            }
191
        );
192
193
    });
194
195
    $('#checkoutModal').on('click', '#checkoutConfirm', function(e) {
196
        let external_id = $('#checkout_barcode').val();
197
        let item_id = $('#item_id').val();
198
        let token = $('#confirm_token').val();
199
        let params = {
200
            "checkout_id": undefined,
201
            "patron_id": logged_in_user_id,
202
            "item_id": item_id,
203
            "due_date": undefined,
204
            "library_id": undefined,
205
            "issuer_id": undefined,
206
            "checkin_date": undefined,
207
            "last_renewed_date": undefined,
208
            "renewals_count": undefined,
209
            "unseen_renewals": undefined,
210
            "auto_renew": undefined,
211
            "auto_renew_error": undefined,
212
            "timestamp": undefined,
213
            "checkout_date": undefined,
214
            "onsite_checkout": false,
215
            "note": undefined,
216
            "note_date": undefined,
217
            "note_seen": undefined,
218
        };
219
        let checkout = $.ajax({
220
            url: '/api/v1/public/checkouts?confirmation=' + token,
221
            type: 'POST',
222
            data: JSON.stringify(params),
223
            contentType: "json"
224
        });
225
226
        checkout.then(
227
            function(data, textStatus, jqXHR) {
228
                $('#item_id').remove;
229
                $('#confirm_token').remove;
230
                $('#availabilityResult').replaceWith('<div id="availabilityResult"></div>');
231
                addCheckout(data);
232
                // data retrieved from url2 as provided by the first request
233
            },
234
            function(data, textStatus, jqXHR) {
235
                console.log("checkout.then failed");
236
            }
237
        );
238
    });
239
240
    $('#checkoutModal').on('hidden.bs.modal', function (e) {
241
        let pageName = $(location).attr("pathname");
242
        if ( pageName == '/cgi-bin/koha/opac-user.pl' ) {
243
            location.reload();
244
        }
245
    })
246
});

Return to bug 30979