Owen Leonard
e5f1ada249
This patch replaces the use of jQueryUI's datepicker on circulation and patron-related pages. The patch modifies Flatpickr's default configuration (in calendar.inc) so that it has the following features: - A Flatpickr input with a "futuredate" class will require that the selected date be after today. - The Flatpickr input field will be wrapped in a container to facilitate better CSS styling. - Generic handling of paired date fields is enabled using ".flatpickrfrom" and ".flatpickrto" field classes. This mimics the same feature we have for jQueryUI datepickers using ".datepickerfrom" and ".datepickerto". This patch also removes an unused function which was repeated in three templates: validate1. To test, apply the patch and rebuild the staff interface CSS (https://wiki.koha-community.org/wiki/Working_with_SCSS_in_the_OPAC_and_staff_client). - Go to Circulation and check out to a patron. - Open the Restrictions tab and click "Add manual restriction." - In the "Expiration" field, test that the Flatpickr widget works correctly and limits to dates after today. - Enable the SpecifyDueDate preference if necessary. - Test the behavior of the SpecifyDueDate controls: Setting a date, clearing a date, session persistence. - Enable the SuspendHoldsIntranet system preference if necessary. - Check out to a patron with existing holds. - Open the Holds tab and click the "Suspend" button for one of the holds. - In the modal window which appears, check that the Flatpickr widget works correctly and limits to dates after today. - At the bottom of the table of holds, test that the "Suspend all holds" Flatpickr works correctly and limits to dates after today. - Perform this same test from the patron details page. - Enable the BatchCheckouts system preference if necessary. - Open a patron record and click "Batch check out" in the left-hand sidebar menu. - Test that the "Hard due date" Flatpickr works correctly as a date and time picker. - Go to Circulation -> Overdues. - Test that the date due filters in the sidebar work correctly and are linked, e.g. the "to" field cannot be before the "from" field. - Perform the same test here: Circulation -> Holds to pull; and here: Circulation -> Hold ratios. - Enable the HouseboundModule system preference if necessary. - Check out to or view details of a patron. - Click "Housebound" in the sidebar menu. - Save delivery day and frequency settings for that patron. - Click "Add a new delivery." - Test that the "Date" Flatpickr widget works correctly. - Go to Patrons -> A patron record -> Edit. - Test that Flatpickr widgets work on the following fields: - Date of birth - Registration date & Expiration date (linked). - Patron restrictions -> Add manual restriction -> Expiration. - View a bibliographic record and start the process of placing a hold. - After selecting a patron, test the "Hold starts on" and "Hold expires on" date fields. The fields should be linked and each should limit to future dates. - Confirm that the dates are saved correctly when you submit the hold. - Locate a bibliographic record with multiple holds and view the holds. - In the table of holds, test each date field: Date, expiration, and suspend-until. - Test that Flatpickr's static "formatDate" method is working correctly: - Locate a bibliographic record's item so that there is text in both the "Public note" and "Non-public note" field. - Check that item out to a patron. - After the page reloads the public and non-public notes should be shown under the checkout title highlighted in red. - Check for references to a "validate1" function. There should be none. Signed-off-by: Lucas Gass <lucas@bywatersolutions.com> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
360 lines
18 KiB
JavaScript
360 lines
18 KiB
JavaScript
/* global __ dataTablesDefaults borrowernumber SuspendHoldsIntranet */
|
||
$(document).ready(function() {
|
||
var holdsTable;
|
||
|
||
// Don't load holds table unless it is clicked on
|
||
$("#holds-tab").on( "click", function(){ load_holds_table() } );
|
||
|
||
// If the holds tab is preselected on load, we need to load the table
|
||
if ( $("#holds-tab").parent().hasClass('ui-state-active') ) { load_holds_table() }
|
||
|
||
function load_holds_table() {
|
||
var holds = new Array();
|
||
if ( ! holdsTable ) {
|
||
var title;
|
||
holdsTable = $("#holds-table").dataTable($.extend(true, {}, dataTablesDefaults, {
|
||
"bAutoWidth": false,
|
||
"sDom": "rt",
|
||
"columns": [
|
||
{
|
||
"data": { _: "reservedate_formatted", "sort": "reservedate" }
|
||
},
|
||
{
|
||
"mDataProp": function ( oObj ) {
|
||
title = "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber="
|
||
+ oObj.biblionumber
|
||
+ "'>"
|
||
+ oObj.title.escapeHtml();
|
||
|
||
$.each(oObj.subtitle, function( index, value ) {
|
||
title += " " + value.escapeHtml();
|
||
});
|
||
|
||
title += " " + oObj.part_number + " " + oObj.part_name;
|
||
|
||
if ( oObj.enumchron ) {
|
||
title += " (" + oObj.enumchron.escapeHtml() + ")";
|
||
}
|
||
|
||
title += "</a>";
|
||
|
||
if ( oObj.author ) {
|
||
title += " " + __("by _AUTHOR_").replace("_AUTHOR_", oObj.author.escapeHtml());
|
||
}
|
||
|
||
if ( oObj.itemnotes ) {
|
||
var span_class = "";
|
||
if ( flatpickr.formatDate( new Date(oObj.issuedate), "Y-m-d" ) == ymd ){
|
||
span_class = "circ-hlt";
|
||
}
|
||
title += " - <span class='" + span_class + "'>" + oObj.itemnotes.escapeHtml() + "</span>"
|
||
}
|
||
|
||
return title;
|
||
}
|
||
},
|
||
{
|
||
"mDataProp": function( oObj ) {
|
||
return oObj.itemcallnumber && oObj.itemcallnumber.escapeHtml() || "";
|
||
}
|
||
},
|
||
{
|
||
"mDataProp": function( oObj ) {
|
||
var data = "";
|
||
if ( oObj.barcode ) {
|
||
data += " <a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber="
|
||
+ oObj.biblionumber
|
||
+ "&itemnumber="
|
||
+ oObj.itemnumber
|
||
+ "#item"
|
||
+ oObj.itemnumber
|
||
+ "'>"
|
||
+ oObj.barcode.escapeHtml()
|
||
+ "</a>";
|
||
}
|
||
return data;
|
||
}
|
||
},
|
||
{
|
||
"mDataProp": function( oObj ) {
|
||
if( oObj.branches.length > 1 && oObj.found !== 'W' && oObj.found !== 'T' ){
|
||
var branchSelect='<select priority='+oObj.priority+' class="hold_location_select" data-hold-id="'+oObj.reserve_id+'" reserve_id="'+oObj.reserve_id+'" name="pick-location">';
|
||
for ( var i=0; i < oObj.branches.length; i++ ){
|
||
var selectedbranch;
|
||
var setbranch;
|
||
if( oObj.branches[i].selected ){
|
||
|
||
selectedbranch = " selected='selected' ";
|
||
setbranch = __(" (current) ");
|
||
} else if ( oObj.branches[i].pickup_location == 0 ) {
|
||
continue;
|
||
} else{
|
||
selectedbranch = '';
|
||
setbranch = '';
|
||
}
|
||
branchSelect += '<option value="'+ oObj.branches[i].branchcode.escapeHtml() +'"'+selectedbranch+'>'+oObj.branches[i].branchname.escapeHtml()+setbranch+'</option>';
|
||
}
|
||
branchSelect +='</select>';
|
||
return branchSelect;
|
||
}
|
||
else { return oObj.branchcode.escapeHtml() || ""; }
|
||
}
|
||
},
|
||
{ "data": { _: "expirationdate_formatted", "sort": "expirationdate" } },
|
||
{
|
||
"mDataProp": function( oObj ) {
|
||
if ( oObj.priority && parseInt( oObj.priority ) && parseInt( oObj.priority ) > 0 ) {
|
||
return oObj.priority;
|
||
} else {
|
||
return "";
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"bSortable": false,
|
||
"mDataProp": function( oObj ) {
|
||
return "<select name='rank-request'>"
|
||
+"<option value='n'>" + __("No") + "</option>"
|
||
+"<option value='del'>" + __("Yes") + "</option>"
|
||
+ "</select>"
|
||
+ "<input type='hidden' name='biblionumber' value='" + oObj.biblionumber + "'>"
|
||
+ "<input type='hidden' name='borrowernumber' value='" + borrowernumber + "'>"
|
||
+ "<input type='hidden' name='reserve_id' value='" + oObj.reserve_id + "'>";
|
||
}
|
||
},
|
||
{
|
||
"bSortable": false,
|
||
"visible": SuspendHoldsIntranet,
|
||
"mDataProp": function( oObj ) {
|
||
holds[oObj.reserve_id] = oObj; //Store holds for later use
|
||
|
||
if ( oObj.found ) {
|
||
return "";
|
||
} else if ( oObj.suspend == 1 ) {
|
||
return "<a class='hold-resume btn btn-default btn-xs' id='resume" + oObj.reserve_id + "'>"
|
||
+"<i class='fa fa-play'></i> " + __("Resume") + "</a>";
|
||
} else {
|
||
return "<a class='hold-suspend btn btn-default btn-xs' id='suspend" + oObj.reserve_id + "'>"
|
||
+"<i class='fa fa-pause'></i> " + __("Suspend") + "</a>";
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"mDataProp": function( oObj ) {
|
||
var data = "";
|
||
|
||
if ( oObj.suspend == 1 ) {
|
||
data += "<p>" + __("Hold is <strong>suspended</strong>");
|
||
if ( oObj.suspend_until ) {
|
||
data += " " + __("until %s").format(oObj.suspend_until_formatted);
|
||
}
|
||
data += "</p>";
|
||
}
|
||
|
||
if ( oObj.itemtype_limit ) {
|
||
data += __("Next available %s item").format(oObj.itemtype_limit);
|
||
}
|
||
|
||
if ( oObj.barcode ) {
|
||
data += "<em>";
|
||
if ( oObj.found == "W" ) {
|
||
|
||
if ( oObj.waiting_here ) {
|
||
data += __("Item is <strong>waiting here</strong>");
|
||
if (oObj.desk_name) {
|
||
data += ", " + __("at %s").format(oObj.desk_name.escapeHtml());
|
||
}
|
||
} else {
|
||
data += __("Item is <strong>waiting</strong>");
|
||
data += " " + __("at %s").format(oObj.waiting_at);
|
||
if (oObj.desk_name) {
|
||
data += ", " + __("at %s").format(oObj.desk_name.escapeHtml());
|
||
}
|
||
|
||
}
|
||
|
||
} else if ( oObj.transferred ) {
|
||
data += __("Item is <strong>in transit</strong> from %s since %s").format(oObj.from_branch, oObj.date_sent);
|
||
} else if ( oObj.not_transferred ) {
|
||
data += __("Item hasn't been transferred yet from %s").format(oObj.not_transferred_by);
|
||
}
|
||
data += "</em>";
|
||
}
|
||
return data;
|
||
}
|
||
}
|
||
],
|
||
"bPaginate": false,
|
||
"bProcessing": true,
|
||
"bServerSide": false,
|
||
"ajax": {
|
||
"url": '/cgi-bin/koha/svc/holds',
|
||
"data": function ( d ) {
|
||
d.borrowernumber = borrowernumber;
|
||
}
|
||
},
|
||
}));
|
||
|
||
$('#holds-table').on( 'draw.dt', function () {
|
||
$(".hold-suspend").on( "click", function() {
|
||
var id = $(this).attr("id").replace("suspend", "");
|
||
var hold = holds[id];
|
||
$("#suspend-modal-title").html( hold.title );
|
||
$("#suspend-modal-reserve_id").val( hold.reserve_id );
|
||
$('#suspend-modal').modal('show');
|
||
});
|
||
|
||
$(".hold-resume").on( "click", function() {
|
||
var id = $(this).attr("id").replace("resume", "");
|
||
var hold = holds[id];
|
||
$.post('/cgi-bin/koha/svc/hold/resume', { "reserve_id": hold.reserve_id }, function( data ){
|
||
if ( data.success ) {
|
||
holdsTable.api().ajax.reload();
|
||
} else {
|
||
if ( data.error == "HOLD_NOT_FOUND" ) {
|
||
alert( __("Unable to resume, hold not found") );
|
||
holdsTable.api().ajax.reload();
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
function display_pickup_location (state) {
|
||
var $text;
|
||
if ( state.needs_override === true ) {
|
||
$text = $(
|
||
'<span>' + state.text + '</span> <span style="float:right;" title="' +
|
||
_("This pickup location is not allowed according to circulation rules") +
|
||
'"><i class="fa fa-exclamation-circle" aria-hidden="true"></i></span>'
|
||
);
|
||
}
|
||
else {
|
||
$text = $('<span>'+state.text+'</span>');
|
||
}
|
||
|
||
return $text;
|
||
};
|
||
|
||
$(".hold_location_select").each( function () {
|
||
var this_dropdown = $(this);
|
||
var hold_id = $(this).data('hold-id');
|
||
|
||
this_dropdown.select2({
|
||
allowClear: false,
|
||
ajax: {
|
||
url: '/api/v1/holds/' + encodeURIComponent(hold_id) + '/pickup_locations',
|
||
delay: 300, // wait 300 milliseconds before triggering the request
|
||
dataType: 'json',
|
||
cache: true,
|
||
data: function (params) {
|
||
var search_term = (params.term === undefined) ? '' : params.term;
|
||
var query = {
|
||
"q": JSON.stringify({"name":{"-like":search_term+'%'}}),
|
||
"_order_by": "name"
|
||
};
|
||
return query;
|
||
},
|
||
processResults: function (data) {
|
||
var results = [];
|
||
data.forEach( function ( pickup_location ) {
|
||
results.push(
|
||
{
|
||
"id": pickup_location.library_id.escapeHtml(),
|
||
"text": pickup_location.name.escapeHtml(),
|
||
"needs_override": pickup_location.needs_override
|
||
}
|
||
);
|
||
});
|
||
return { "results": results };
|
||
}
|
||
},
|
||
templateResult: display_pickup_location
|
||
});
|
||
$('.select2-container').css('width','100%');
|
||
});
|
||
|
||
$(".hold_location_select").on("change", function(){
|
||
$(this).prop("disabled",true);
|
||
var cur_select = $(this);
|
||
var res_id = $(this).attr('reserve_id');
|
||
$(this).after('<div id="updating_reserveno'+res_id+'" class="waiting"><img src="/intranet-tmpl/prog/img/spinner-small.gif" alt="" /><span class="waiting_msg"></span></div>');
|
||
var api_url = '/api/v1/holds/' + encodeURIComponent(res_id) + '/pickup_location';
|
||
$.ajax({
|
||
method: "PUT",
|
||
url: api_url,
|
||
data: JSON.stringify({ "pickup_library_id": $(this).val() }),
|
||
headers: { "x-koha-override": "any" },
|
||
success: function( data ){ holdsTable.api().ajax.reload(); },
|
||
error: function( jqXHR, textStatus, errorThrown) {
|
||
alert('There was an error:'+textStatus+" "+errorThrown);
|
||
cur_select.prop("disabled",false);
|
||
$("#updating_reserveno"+res_id).remove();
|
||
cur_select.val( cur_select.children('option[selected="selected"]').val() );
|
||
},
|
||
});
|
||
});
|
||
|
||
});
|
||
|
||
if ( $("#holds-table").length ) {
|
||
$("#holds-table_processing").position({
|
||
of: $( "#holds-table" ),
|
||
collision: "none"
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
$("body").append("\
|
||
<div id='suspend-modal' class='modal fade' role='dialog' aria-hidden='true'>\
|
||
<div class='modal-dialog'>\
|
||
<div class='modal-content'>\
|
||
<form id='suspend-modal-form' class='form-inline'>\
|
||
<div class='modal-header'>\
|
||
<button type='button' class='closebtn' data-dismiss='modal' aria-hidden='true'>×</button>\
|
||
<h3 id='suspend-modal-label'>" + __("Suspend hold on") + " <i><span id='suspend-modal-title'></span></i></h3>\
|
||
</div>\
|
||
\
|
||
<div class='modal-body'>\
|
||
<input type='hidden' id='suspend-modal-reserve_id' name='reserve_id' />\
|
||
\
|
||
<label for='suspend-modal-until'>" + __("Suspend until:") + "</label>\
|
||
<input name='suspend_until' id='suspend-modal-until' class='suspend-until' size='10' />\
|
||
\
|
||
<p><a class='btn btn-link' id='suspend-modal-clear-date' >" + __("Clear date to suspend indefinitely") + "</a></p>\
|
||
\
|
||
</div>\
|
||
\
|
||
<div class='modal-footer'>\
|
||
<button id='suspend-modal-submit' class='btn btn-primary' type='submit' name='submit'>" + __("Suspend") + "</button>\
|
||
<a href='#' data-dismiss='modal' aria-hidden='true' class='cancel'>" + __("Cancel") + "</a>\
|
||
</div>\
|
||
</form>\
|
||
</div>\
|
||
</div>\
|
||
</div>\
|
||
");
|
||
|
||
$("#suspend-modal-until").flatpickr({
|
||
minDate: new Date().fp_incr(1) // Require that "until date" be in the future
|
||
});
|
||
$("#suspend-modal-clear-date").on( "click", function() { $("#suspend-modal-until").val(""); } );
|
||
|
||
$("#suspend-modal-submit").on( "click", function( e ) {
|
||
e.preventDefault();
|
||
$.post('/cgi-bin/koha/svc/hold/suspend', $('#suspend-modal-form').serialize(), function( data ){
|
||
$('#suspend-modal').modal('hide');
|
||
if ( data.success ) {
|
||
holdsTable.api().ajax.reload();
|
||
} else {
|
||
if ( data.error == "INVALID_DATE" ) {
|
||
alert( __("Unable to suspend hold, invalid date") );
|
||
}
|
||
else if ( data.error == "HOLD_NOT_FOUND" ) {
|
||
alert( __("Unable to suspend hold, hold not found") );
|
||
holdsTable.api().ajax.reload();
|
||
}
|
||
}
|
||
});
|
||
});
|
||
});
|