From 168a52e02e7febe79e298b2fea59010fbbf5a434 Mon Sep 17 00:00:00 2001 From: miker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4> Date: Mon, 28 Dec 2009 21:40:02 +0000 Subject: [PATCH] Patch from Lebbeous Fogle-Weekley to wire up more of the booking functionality and provide more interface integration git-svn-id: svn://svn.open-ils.org/ILS/trunk@15236 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- .../src/perlmods/OpenILS/Application/Booking.pm | 62 ++++- .../Application/Storage/Publisher/action.pm | 2 +- Open-ILS/web/css/skin/default/booking.css | 9 + .../web/js/dojo/openils/booking/nls/reservation.js | 37 ++- Open-ILS/web/js/ui/default/booking/reservation.js | 303 +++++++++++++++++---- Open-ILS/web/opac/locale/en-US/lang.dtd | 14 +- .../web/templates/default/booking/reservation.tt2 | 23 +- .../staff_client/chrome/content/util/functional.js | 10 +- .../xul/staff_client/server/cat/copy_browser.js | 17 +- .../xul/staff_client/server/cat/copy_browser.xul | 6 +- Open-ILS/xul/staff_client/server/cat/util.js | 2 +- .../xul/staff_client/server/circ/copy_status.js | 54 ++++ .../xul/staff_client/server/circ/copy_status.xul | 2 + .../server/circ/copy_status_overlay.xul | 6 + Open-ILS/xul/staff_client/server/patron/display.js | 21 ++ .../xul/staff_client/server/patron/display.xul | 1 + .../staff_client/server/patron/display_horiz.xul | 1 + .../server/patron/display_horiz_overlay.xul | 7 + .../staff_client/server/patron/display_overlay.xul | 7 + 19 files changed, 503 insertions(+), 81 deletions(-) diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm b/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm index d3b41db165..436d51fe82 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm @@ -521,6 +521,66 @@ by the top-level filters ('user', 'type', 'resource'). NOTES ); + +sub get_pull_list { + my ($self, $client, $auth, $range, $pickup_lib) = @_; + + my $e = new_editor(xact => 1, authtoken => $auth); + return $e->die_event unless $e->checkauth; + return $e->die_event unless $e->allowed('RETRIEVE_RESERVATION_PULL_LIST'); + return $e->die_event unless ref($range) eq 'ARRAY'; + + my $query = { + "select" => {"bresv" => ["id"]}, + "from" => "bresv", + "where" => { + "-and" => [ + json_query_ranges_overlap( + $range->[0], $range->[1], "start_time", "end_time" + ), + {"current_resource" => {"!=" => undef}}, + {"capture_time" => undef}, + {"cancel_time" => undef}, + {"return_time" => undef}, + {"pickup_time" => undef} + ], + } + }; + if ($pickup_lib) { + push @{$query->{"where"}->{"-and"}}, {"pickup_lib" => $pickup_lib}; + } + + my $ids = [ map { $_->{id} } @{$e->json_query($query)} ]; + if (@$ids) { + my $bresv_list = $e->search_booking_reservation([ + {"id" => $ids}, { + flesh => 1, + flesh_fields => { + bresv => [qw/usr target_resource_type current_resource/] + } + } + ]); + return $bresv_list ? $bresv_list : []; + } else { + return $ids; # empty list + } +} +__PACKAGE__->register_method( + method => "get_pull_list", + api_name => "open-ils.booking.reservations.get_pull_list", + argc => 2, + signature=> { + params => [ + {type => 'string', desc => 'Authentication token'}, + {type => 'array', desc => 'Date/time range for reservations'}, + {type => 'number', desc => '(Optional) Pickup library'} + ], + return => { desc => "An array of reservations, fleshed with usr, " . + "current_resource, and target_resource_type" } + } +); + + sub capture_reservation { my $self = shift; my $client = shift; @@ -599,7 +659,7 @@ sub capture_reservation { } __PACKAGE__->register_method( method => "capture_reservation", - api_name => "open-ils.booking.reservation.capture", + api_name => "open-ils.booking.reservations.capture", argc => 2, signature=> { params => [ diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm index 1894456acd..88056c0d1c 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm @@ -1411,7 +1411,7 @@ sub reservation_targeter { next; } - my $copy = asset::copy->search( deleted => f, barcode => $res->barcode )->[0]; + my $copy = [ asset::copy->search( deleted => f, barcode => $res->barcode )]->[0]; unless ($copy) { push @good_resources, $res; diff --git a/Open-ILS/web/css/skin/default/booking.css b/Open-ILS/web/css/skin/default/booking.css index ca59b8e14e..07a73e25ee 100644 --- a/Open-ILS/web/css/skin/default/booking.css +++ b/Open-ILS/web/css/skin/default/booking.css @@ -46,3 +46,12 @@ div.nice_vertical_padding { span.two_buttons { text-align: center; } +option.forced_unavailable { + background-color: #ffcccc; + color: #990000; + font-weight: bold; + font-style: italic; +} +div#preselected_patron { + font-size: 12pt; +} diff --git a/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js b/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js index 4f1144b68d..b2aeae8a41 100644 --- a/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js +++ b/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js @@ -5,14 +5,16 @@ 'SELECT_A_BRSRC_THEN': "Select a resource from the big list above.", 'CREATE_BRESV_LOCAL_ERROR': "Exception trying to create reservation: ", 'CREATE_BRESV_SERVER_ERROR': "Server error trying to create reservation: ", - 'CREATE_BRESV_SERVER_NO_RESPONSE': "No response from server after trying " + - "to create reservation: ", + 'CREATE_BRESV_SERVER_NO_RESPONSE': + "No response from server after trying to create reservation: ", /* FIXME: Users aren't likely to be able to do anything with the following * message. Figure out a way to do something more helpful. */ 'CREATE_BRESV_OK_MISSING_TARGET': function(n, m) { return "Created " + n + " reservation(s), but " + m + " of these " + - "couldn't target any resources."; + "couldn't target any resources.\n\n" + + "This means that it won't be possible to fulfill some of these\n" + + "reservations until a suitable resource becomes available."; }, 'CREATE_BRESV_OK': function(n) { return "Created " + n + " reservation" + (n == 1 ? "" : "s") + "."; @@ -20,25 +22,35 @@ 'WHERES_THE_BARCODE': "Enter a patron's barcode to make a reservation.", 'ACTOR_CARD_NOT_FOUND': "Patron barcode not found. Please try again.", 'GET_BRESV_LIST_ERR': "Error while retrieving reservation list: ", - 'GET_BRESV_LIST_NO_RESULT': "No results from server " + - "retrieving reservation list.", + 'GET_BRESV_LIST_NO_RESULT': + "No results from server retrieving reservation list.", 'OUTSTANDING_BRESV': "Outstanding reservations for patron", 'UNTARGETED': "None targeted", - 'GET_PATRON_NO_RESULT': "No server response after attempting to " + - "look up patron by barcode.", + 'GET_PATRON_NO_RESULT': + "No server response after attempting to look up patron by barcode.", 'HERE_ARE_EXISTING_BRESV': "Existing reservations for", + 'NO_EXISTING_BRESV': "This user has no existing reservations at this time.", + 'NO_USABLE_BRSRC': + "No reservable resources. Adjust start and end time\n" + + "until a resource is available for reservation.", 'CXL_BRESV_SUCCESS': function(n) { return ("Canceled " + n + " reservation" + (n == 1 ? "" : "s") + "."); }, 'CXL_BRESV_FAILURE': "Error canceling reservations.", - 'CXL_BRESV_SELECT_SOMETHING': "You have not selected any reservations to " + - "cancel.", + 'CXL_BRESV_SELECT_SOMETHING': + "You have not selected any reservations to cancel.", + 'NEED_EXACTLY_ONE_BRT_PASSED_IN': + "Can't book multiple resource types at once", + 'COULD_NOT_RETRIEVE_BRT_PASSED_IN': + "Error retrieving booking resource type", + 'INVALID_TS_RANGE': + "You must choose a valid start and end time for the reservation.", 'ANY': "ANY", 'AUTO_choose_a_brt': "Choose a Bookable Resource Type", 'AUTO_i_need_this_resource': "I need this resource...", - 'AUTO_starting_at': "Starting at", - 'AUTO_ending_at': "and ending at", + 'AUTO_starting_at': "Between", + 'AUTO_ending_at': "and", 'AUTO_with_these_attr': "With these attributes:", 'AUTO_patron_barcode': "Reserve to patron barcode:", 'AUTO_ATTR_VALUE_next': "Next", @@ -49,5 +61,6 @@ 'AUTO_bresv_grid_type': "Type", 'AUTO_bresv_grid_resource': "Resource", 'AUTO_bresv_grid_start_time': "Start time", - 'AUTO_bresv_grid_end_time': "End time" + 'AUTO_bresv_grid_end_time': "End time", + 'AUTO_brt_noncat_only': "Show only non-cataloged bookable resource types" } diff --git a/Open-ILS/web/js/ui/default/booking/reservation.js b/Open-ILS/web/js/ui/default/booking/reservation.js index 612a54e110..94cdd9c964 100644 --- a/Open-ILS/web/js/ui/default/booking/reservation.js +++ b/Open-ILS/web/js/ui/default/booking/reservation.js @@ -13,9 +13,11 @@ dojo.requireLocalization("openils.booking", "reservation"); */ var localeStrings = dojo.i18n.getLocalization("openils.booking", "reservation"); var pcrud = new openils.PermaCrud(); +var opts; var our_brt; var brsrc_index = {}; var bresv_index = {}; +var just_reserved_now = {}; function AttrValueTable() { this.t = {}; } AttrValueTable.prototype.set = function(attr, value) { this.t[attr] = value; }; @@ -36,46 +38,126 @@ AttrValueTable.prototype.get_all_values = function() { var attr_value_table = new AttrValueTable(); function TimestampRange() { - this.start = {"date": undefined, "time": undefined}; - this.end = {"date": undefined, "time": undefined}; + this.start = new Date(); + this.end = new Date(); + + this.validity = {"start": false, "end": false}; + this.nodes = { + "start": {"date": undefined, "time": undefined}, + "end": {"date": undefined, "time": undefined} + }; + this.saved_style_properties = {}; + this.invalid_style_properties = { + "backgroundColor": "#ffcccc", + "color": "#990000", + "borderColor": "#990000", + "fontWeight": "bold" + }; } TimestampRange.prototype.get_timestamp = function(when) { - return (this[when].date + " " + this[when].time); + return this.any_widget.serialize(this[when]). + replace("T", " ").substr(0, 19); }; TimestampRange.prototype.get_range = function() { return this.is_backwards() ? [this.get_timestamp("end"), this.get_timestamp("start")] : [this.get_timestamp("start"), this.get_timestamp("end")]; }; -TimestampRange.prototype.split_time = function(s) { - /* We're not interested in seconds for our purposes, - * so we floor everything to :00. - * - * Also, notice that following discards all time zone information - * from the timestamp string represenation. This should probably - * stay the way it is, even when this code is improved to support - * selecting time zones (it currently just assumes server's local - * time). The easy way to add support will be to add a drop-down - * selector from which the user can pick a time zone, then use - * that timezone literal in an "AT TIME ZONE" clause in SQL on - * the server side. - */ - return s.split("T")[1].replace(/(\d{2}:\d{2}:)(\d{2})(.*)/, "$100"); -}; -TimestampRange.prototype.split_date = function(s) { - return s.split("T")[0]; -}; TimestampRange.prototype.update_from_widget = function(widget) { var when = widget.id.match(/(start|end)/)[1]; var which = widget.id.match(/(date|time)/)[1]; + if (this.any_widget == undefined) + this.any_widget = widget; + if (this.nodes[when][which] == undefined) + this.nodes[when][which] = widget.domNode; /* We'll need this later */ + if (when && which) { - this[when][which] = - this["split_" + which](widget.serialize(widget.value)); + this.update_timestamp(when, which, widget.value); + } + + this.compute_validity(); + this.paint_validity(); +}; +TimestampRange.prototype.compute_validity = function() { + if (Math.abs(this.start - this.end) < 1000) { + this.validity.end = false; + } else { + if (this.start < this.current_minimum()) + this.validity.start = false; + else + this.validity.start = true; + + if (this.end < this.current_minimum()) + this.validity.end = false; + else + this.validity.end = true; + } +}; +/* This method provides the minimum timestamp that is considered valid. For + * now it's arbitrarily "now + 15 minutes", meaning that all reservations + * must be made at least 15 minutes in the future. + * + * For reasons of keeping the middle layer happy, this should always return + * a time that is at least somewhat in the future. The ML isn't able to target + * any resources for a reservation with a start date that isn't in the future. + */ +TimestampRange.prototype.current_minimum = function() { + /* XXX This is going to be a problem with local clocks that are off. */ + var n = new Date(); + n.setTime(n.getTime() + 1000 * 900); /* XXX 15 minutes; stop hardcoding! */ + return n; +}; +TimestampRange.prototype.update_timestamp = function(when, which, value) { + if (which == "date") { + this[when].setFullYear(value.getFullYear()); + this[when].setMonth(value.getMonth()); + this[when].setDate(value.getDate()); + } else { /* "time" */ + this[when].setHours(value.getHours()); + this[when].setMinutes(value.getMinutes()); + this[when].setSeconds(0); } }; TimestampRange.prototype.is_backwards = function() { - return (this.get_timestamp("start") > this.get_timestamp("end")); + return (this.start > this.end); +}; +TimestampRange.prototype.paint_validity = function() { + for (var when in this.validity) { + if (this.validity[when]) { + this.paint_valid_node(this.nodes[when].date); + this.paint_valid_node(this.nodes[when].time); + } else { + this.paint_invalid_node(this.nodes[when].date); + this.paint_invalid_node(this.nodes[when].time); + } + } +}; +TimestampRange.prototype.paint_invalid_node = function(node) { + if (node) { + /* Just toggling the class of something would be better than + * manually setting style here, but I haven't been able to get that + * to play nicely with dojo's styling of the date/time textboxen. + */ + if (this.saved_style_properties.backgroundColor == undefined) { + for (var k in this.invalid_style_properties) { + this.saved_style_properties[k] = node.style[k]; + } + } + for (var k in this.invalid_style_properties) { + node.style[k] = this.invalid_style_properties[k]; + } + } +}; +TimestampRange.prototype.paint_valid_node = function(node) { + if (node) { + for (var k in this.saved_style_properties) { + node.style[k] = this.saved_style_properties[k]; + } + } +}; +TimestampRange.prototype.is_valid = function() { + return (this.validity.start && this.validity.end); }; var reserve_timestamp_range = new TimestampRange(); @@ -93,7 +175,8 @@ SelectorMemory.prototype.save = function() { SelectorMemory.prototype.restore = function() { for (var i = 0; i < this.selector.options.length; i++) { if (this.memory[this.selector.options[i].value]) { - this.selector.options[i].selected = true; + if (!this.selector.options[i].disabled) + this.selector.options[i].selected = true; } } }; @@ -148,6 +231,10 @@ function get_all_noncat_brt() { ); } +function get_brt_by_id(id) { + return pcrud.retrieve("brt", id); +} + function get_brsrc_id_list() { var options = {"type": our_brt.id()}; @@ -168,36 +255,56 @@ function get_brsrc_id_list() { ); } -// FIXME: We need failure checking after pcrud.retrieve() -function sync_brsrc_index_from_ids(id_list) { - /* One pass to populate the cache with anything that's missing. */ - for (var i in id_list) { - if (!brsrc_index[id_list[i]]) { - brsrc_index[id_list[i]] = pcrud.retrieve("brsrc", id_list[i]); +/* FIXME: We need failure checking after pcrud.retrieve() */ +function add_brsrc_to_index_if_needed(list, further) { + for (var i in list) { + if (!brsrc_index[list[i]]) { + brsrc_index[list[i]] = pcrud.retrieve("brsrc", list[i]); } - brsrc_index[id_list[i]].isdeleted(false); // See NOTE below. + if (further) + further(brsrc_index[list[i]]); } - /* A second pass to indicate any entries in the cache to be hidden. */ +} + +function sync_brsrc_index_from_ids(available_list, additional_list) { + /* Default states for everything in the index. Read the further comments. */ for (var i in brsrc_index) { - if (id_list.indexOf(Number(i)) < 0) { // Number() is important. - brsrc_index[i].isdeleted(true); // See NOTE below. - } + brsrc_index[i].isdeleted(true); + brsrc_index[i].ischanged(false); } - /* NOTE: We lightly abuse the isdeleted() magic attribute of the brsrcs - * in our cache. Because we're not going to pass back any brsrcs to - * the middle layer, it doesn't really matter what we set this attribute - * to. What we're using it for is to indicate in our little brsrc cache - * whether a given brsrc should be displayed in this UI's current state - * (based on whether it was returned by the last call to the middle layer, - * i.e., whether it matches the currently selected attributes). + + /* Populate the cache with anything that's missing and tag everything + * in the "available" list as *not* deleted, and tag everything in the + * additional list as "changed." See below. */ + add_brsrc_to_index_if_needed( + available_list, function(o) { o.isdeleted(false); } + ); + add_brsrc_to_index_if_needed( + additional_list, + function(o) { + if (!(o.id() in just_reserved_now)) o.ischanged(true); + } + ); + /* NOTE: We lightly abuse the isdeleted() and ischanged() magic fieldmapper + * attributes of the brsrcs in our cache. Because we're not going to + * pass back any brsrcs to the middle layer, it doesn't really matter + * what we set this attribute to. What we're using it for is to indicate + * in our little brsrc cache how a given brsrc should be displayed in this + * UI's current state (based on whether the brsrc matches timestamp range + * availability (isdeleted(false)) and whether the brsrc has been forced + * into the list because it was selected in a previous interface (like + * the catalog) (ischanged(true))). */ } function check_bresv_targeting(results) { var missing = 0; for (var i in results) { - if (!(results[i].targeting && results[i].targeting.current_resource)) + if (!(results[i].targeting && results[i].targeting.current_resource)) { missing++; + } else { + just_reserved_now[results[i].targeting.current_resource] = true; + } } return missing; } @@ -207,6 +314,9 @@ function create_bresv(resource_list) { if (barcode == "") { alert(localeStrings.WHERES_THE_BARCODE); return; + } else if (!reserve_timestamp_range.is_valid()) { + alert(localeStrings.INVALID_TS_RANGE); + return; } var results; try { @@ -285,7 +395,12 @@ function create_bresv_on_brsrc() { alert(localeStrings.SELECT_A_BRSRC_THEN); } -function create_bresv_on_brt() { create_bresv(); } +function create_bresv_on_brt() { + if (any_usable_brsrc()) + create_bresv(); + else + alert(localeStrings.NO_USABLE_BRSRC); +} function get_actor_by_barcode(barcode) { var usr = fieldmapper.standardRequest( @@ -329,11 +444,24 @@ function init_bresv_grid(barcode) { alert(my_ils_error(localeStrings.GET_BRESV_LIST_ERR, result)); } } else { + if (result.length < 1) { + document.getElementById("bresv_grid_alt_explanation").innerHTML = + localeStrings.NO_EXISTING_BRESV; + hide_dom_element(document.getElementById("bresv_grid")); + reveal_dom_element(document.getElementById("reserve_under")); + } else { + document.getElementById("bresv_grid_alt_explanation").innerHTML = + ""; + reveal_dom_element(document.getElementById("bresv_grid")); + reveal_dom_element(document.getElementById("reserve_under")); + } + /* May as well do the following in either case... */ bresvGrid.setStore( new dojo.data.ItemFileReadStore( {"data": flatten_to_dojo_data(result)} ) ); + bresv_index = {}; for (var i in result) { bresv_index[result[i].id()] = result[i]; } @@ -369,6 +497,9 @@ function provide_brt_selector(targ_div) { targ_div.appendChild( document.createTextNode(localeStrings.NO_BRT_RESULTS) ); + document.getElementById( + "brt_select_other_controls" + ).style.display = "none"; } else { var selector = document.createElement("select"); selector.setAttribute("id", "brt_selector"); @@ -383,6 +514,7 @@ function provide_brt_selector(targ_div) { option.appendChild(document.createTextNode(brt_list[i].name())); selector.appendChild(option); } + targ_div.innerHTML = ""; targ_div.appendChild(selector); } } @@ -396,7 +528,8 @@ function init_reservation_interface(f) { reveal_dom_element(reserve_block); /* Save a global reference to the brt we're going to reserve */ - our_brt = xulG.brt_list[f.brt_selector.selectedIndex]; + if (f) + our_brt = xulG.brt_list[f.brt_selector.selectedIndex]; /* Get a list of attributes that can apply to that brt. */ var bra_list = pcrud.search("bra", {"resource_type": our_brt.id()}); @@ -411,6 +544,10 @@ function init_reservation_interface(f) { brav_by_bra[o.id()] = pcrud.search("brav", {"attr": o.id()}); }); + /* Hide the label over the attributes widgets if we have nothing to show. */ + var domf = (bra_list.length < 1) ? hide_dom_element : reveal_dom_element; + domf(document.getElementById("bra_and_brav_header")); + /* Create DOM widgets to represent each attribute/values set. */ for (var i in bra_list) { var bra_div = document.createElement("div"); @@ -451,12 +588,20 @@ function init_reservation_interface(f) { * asking about. */ document.getElementById("brsrc_list_header").innerHTML = our_brt.name(); + if (opts.patron_barcode) { + document.getElementById("holds_patron_barcode").style.display = "none"; + document.getElementById("patron_barcode").value = opts.patron_barcode; + document.getElementById("patron_barcode").onchange(); + } update_brsrc_list(); } function update_brsrc_list() { var brsrc_id_list = get_brsrc_id_list(); - sync_brsrc_index_from_ids(brsrc_id_list); + var force_list = (opts.booking_results && opts.booking_results.brsrc) ? + opts.booking_results.brsrc.map(function(o) { return o[0]; }) : []; + + sync_brsrc_index_from_ids(brsrc_id_list, force_list); var target_selector = document.getElementById("brsrc_list"); var selector_memory = new SelectorMemory(target_selector); @@ -464,18 +609,32 @@ function update_brsrc_list() { target_selector.innerHTML = ""; for (var i in brsrc_index) { - if (brsrc_index[i].isdeleted()) { + if (brsrc_index[i].isdeleted() && (!brsrc_index[i].ischanged())) continue; - } + var opt = document.createElement("option"); opt.setAttribute("value", brsrc_index[i].id()); opt.appendChild(document.createTextNode(brsrc_index[i].barcode())); + + if (brsrc_index[i].isdeleted() && (brsrc_index[i].ischanged())) { + opt.setAttribute("class", "forced_unavailable"); + opt.setAttribute("disabled", "disabled"); + } + target_selector.appendChild(opt); } selector_memory.restore(); } +function any_usable_brsrc() { + for (var i in brsrc_index) { + if (!brsrc_index[i].isdeleted()) + return true; + } + return false; +} + function update_bresv_grid() { var widg = document.getElementById("patron_barcode"); if (widg.value != "") { @@ -494,8 +653,6 @@ function update_bresv_grid() { } }, 0); setTimeout(function() { init_bresv_grid(widg.value); }, 0); - - reveal_dom_element(document.getElementById("reserve_under")); } } @@ -536,6 +693,10 @@ function cancel_selected_bresv(bresv_dojo_items) { cancel_reservations( bresv_dojo_items.map(function(o) { return bresv_index[o.id]; }) ); + /* After some delay to allow the cancellations a chance to get + * committed, refresh the brsrc list as it might reflect newly + * available resources now. */ + setTimeout(update_brsrc_list, 2000); } else { alert(localeStrings.CXL_BRESV_SELECT_SOMETHING); } @@ -576,6 +737,41 @@ function init_auto_l10n(el) { } } +/* The following function should return true if the reservation interface + * should start normally (show a list of brt to choose from) or false if + * it should not (because we've "started" it some other way by setting up + * and displaying other widgets). + */ +function early_action_passthru() { + if (opts.booking_results) { + if (opts.booking_results.brt.length != 1) { + alert(localeStrings.NEED_EXACTLY_ONE_BRT_PASSED_IN); + return true; + } else if (!(our_brt = get_brt_by_id(opts.booking_results.brt[0][0]))) { + alert(localeStrings.COULD_NOT_RETRIEVE_BRT_PASSED_IN); + return true; + } + + init_reservation_interface(); + return false; + } + + if (opts.patron_barcode) { + try { + var patron = get_actor_by_barcode(opts.patron_barcode); + if (patron) { + document.getElementById("preselected_patron").innerHTML = + "Patron targeted for reservation: <strong>" + + formal_name(patron) + "</strong>"; + } + } catch (E) { + ; /* XXX ignorable? perhaps. */ + } + } + + return true; +} + /* * my_init */ @@ -583,7 +779,10 @@ function my_init() { hide_dom_element(document.getElementById("brt_reserve_block")); reveal_dom_element(document.getElementById("brt_search_block")); hide_dom_element(document.getElementById("reserve_under")); - provide_brt_selector(document.getElementById("brt_selector_here")); init_auto_l10n(document.getElementById("auto_l10n_start_here")); init_timestamp_widgets(); + + if (!(opts = xulG.bresv_interface_opts)) opts = {}; + if (early_action_passthru()) + provide_brt_selector(document.getElementById("brt_selector_here")); } diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index cacdf2413a..713d946ef9 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -1380,6 +1380,8 @@ <!ENTITY staff.patron_navbar.holds.accesskey 'H'> <!ENTITY staff.patron_navbar.alert 'Display Alert and Messages'> <!ENTITY staff.patron_navbar.alert.accesskey 'A'> +<!ENTITY staff.patron_navbar.booking 'Booking'> +<!ENTITY staff.patron_navbar.booking.accesskey 'k'> <!ENTITY staff.patron_navbar.other 'Other'> <!ENTITY staff.patron_navbar.other.accesskey 'o'> <!ENTITY staff.patron_navbar.items 'Items Out'> @@ -1946,6 +1948,10 @@ <!ENTITY staff.circ.copy_status_overlay.sel_patron.accesskey "L"> <!ENTITY staff.circ.copy_status_overlay.cmd_triggered_events.label "Show Triggered Events"> <!ENTITY staff.circ.copy_status_overlay.cmd_triggered_events.accesskey "T"> +<!ENTITY staff.circ.copy_status_overlay.cmd_book_item_now.label "Book Item Now"> +<!ENTITY staff.circ.copy_status_overlay.cmd_book_item_now.accesskey "N"> +<!ENTITY staff.circ.copy_status_overlay.cmd_create_brt.label "Make Item Bookable"> +<!ENTITY staff.circ.copy_status_overlay.cmd_create_brt.accesskey "K"> <!ENTITY staff.circ.copy_status_overlay.sel_edit.label "Edit Item Attributes"> <!ENTITY staff.circ.copy_status_overlay.sel_edit.accesskey "E"> <!ENTITY staff.circ.copy_status_overlay.sel_mark_items_damaged.label "Mark Item Damaged"> @@ -2156,10 +2162,10 @@ <!ENTITY staff.cat.copy_browser.actions.cmd_add_items_to_buckets.accesskey "B"> <!ENTITY staff.cat.copy_browser.actions.sel_copy_details.label "Show Item Details"> <!ENTITY staff.cat.copy_browser.actions.sel_copy_details.accesskey "I"> -<!ENTITY staff.cat.copy_browser.actions.cmd_book_item_now.label "Book This Item"> -<!ENTITY staff.cat.copy_browser.actions.cmd_book_item_now.accesskey "K"> -<!ENTITY staff.cat.copy_browser.actions.cmd_create_brt.label "Make This Item Bookable"> -<!ENTITY staff.cat.copy_browser.actions.cmd_create_brt.accesskey "Y"> +<!ENTITY staff.cat.copy_browser.actions.cmd_book_item_now.label "Book Item Now"> +<!ENTITY staff.cat.copy_browser.actions.cmd_book_item_now.accesskey "N"> +<!ENTITY staff.cat.copy_browser.actions.cmd_create_brt.label "Make Item Bookable"> +<!ENTITY staff.cat.copy_browser.actions.cmd_create_brt.accesskey "K"> <!ENTITY staff.cat.copy_browser.actions.sel_patron.label "Show Last Few Circulations"> <!ENTITY staff.cat.copy_browser.actions.sel_patron.accesskey "L"> <!ENTITY staff.cat.copy_browser.actions.cmd_edit_items.label "Edit Item Attributes"> diff --git a/Open-ILS/web/templates/default/booking/reservation.tt2 b/Open-ILS/web/templates/default/booking/reservation.tt2 index fd337a8d0a..ae80b33e65 100644 --- a/Open-ILS/web/templates/default/booking/reservation.tt2 +++ b/Open-ILS/web/templates/default/booking/reservation.tt2 @@ -10,10 +10,20 @@ <h1 class="booking AUTO_choose_a_brt"></h1> <form onsubmit="init_reservation_interface(this); return false;"> <div id="brt_selector_here" class="nice_vertical_padding"></div> - <div class="nice_vertical_padding"> - <input type="submit" class="AUTO_ATTR_VALUE_next" /> + <div id="brt_select_other_controls"> + <!-- <div class="nice_vertical_padding"> + <input type="checkbox" name="brt_noncat_only" + id="brt_noncat_only" checked="checked" /> + <label for="brt_noncat_only" + class="AUTO_brt_noncat_only"></label> + </div> --> + <div class="nice_vertical_padding"> + <input type="submit" + class="AUTO_ATTR_VALUE_next" /> + </div> </div> </form> + <div id="preselected_patron"></div> </div> <div id="brt_reserve_block" class="container"> @@ -25,7 +35,7 @@ anything in CSS. --> <select id="brsrc_list" name="brsrc_list" multiple="multiple" size="12"></select> - <div class="nice_vertical_padding"> + <div id="holds_patron_barcode" class="nice_vertical_padding"> <label class="AUTO_patron_barcode" for="patron_barcode" /></label> <input name="patron_barcode" id="patron_barcode" @@ -57,13 +67,14 @@ <input id="reserve_date_end" /> <input id="reserve_time_end" /> </div> - <h2 class="booking AUTO_with_these_attr"></h2> - <div id="bra_and_brav"> - </div> + <h2 id="bra_and_brav_header" + class="booking AUTO_with_these_attr"></h2> + <div id="bra_and_brav"></div> </div> <div id="reserve_under"> <hr /> <h2 class="booking" id="existing_reservation_patron_line"></h2> + <div id="bresv_grid_alt_explanation"></div> <table id="bresv_grid" jsId="bresvGrid" dojoType="dojox.grid.DataGrid" query="{id: '*'}" rowSelector="20px" autoHeight="true"> diff --git a/Open-ILS/xul/staff_client/chrome/content/util/functional.js b/Open-ILS/xul/staff_client/chrome/content/util/functional.js index c9ed325a9c..776a7f49fc 100644 --- a/Open-ILS/xul/staff_client/chrome/content/util/functional.js +++ b/Open-ILS/xul/staff_client/chrome/content/util/functional.js @@ -6,7 +6,7 @@ util.functional = {}; util.functional.EXPORT_OK = [ 'filter_list', 'filter_object', 'find_list', 'find_object', 'map_list', 'map_flat_list', 'map_object', 'map_object_to_list', 'convert_object_list_to_hash', 'find_id_object_in_list', - 'find_attr_object_in_list', 'walk_tree_preorder', + 'find_attr_object_in_list', 'walk_tree_preorder', 'unique_list_values', ]; util.functional.EXPORT_TAGS = { ':all' : util.functional.EXPORT_OK }; @@ -134,4 +134,12 @@ util.functional.find_attr_object_in_list = function(list,attr,value) { return null; } +util.functional.unique_list_values = function(list) { + var obj = {}; + var finished_list = []; + for (var i in list) { obj[list[i]] = true; } + for (var i in obj) { finished_list.push(i); } + return finished_list; +} + dump('exiting util/functional.js\n'); diff --git a/Open-ILS/xul/staff_client/server/cat/copy_browser.js b/Open-ILS/xul/staff_client/server/cat/copy_browser.js index 48bfefa5d2..e36e2d3d71 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_browser.js +++ b/Open-ILS/xul/staff_client/server/cat/copy_browser.js @@ -1637,6 +1637,7 @@ cat.copy_browser.prototype = { try { var found_aou = false; var found_acn = false; var found_acp = false; var found_aou_with_can_have_vols = false; + var sel_copy_libs = {}; for (var i = 0; i < obj.sel_list.length; i++) { var type = obj.sel_list[i].split(/_/)[0]; switch(type) { @@ -1646,7 +1647,15 @@ cat.copy_browser.prototype = { if ( get_bool( obj.data.hash.aout[ org.ou_type() ].can_have_vols() ) ) found_aou_with_can_have_vols = true; break; case 'acn' : found_acn = true; break; - case 'acp' : found_acp = true; break; + case 'acp' : + found_acp = true; + sel_copy_libs[ + obj.map_acn[ + "acn_" + + obj.map_acp[obj.sel_list[i]].call_number() + ].owning_lib() + ] = true; + break; } } obj.controller.view.cmd_add_items.setAttribute('disabled','true'); @@ -1664,6 +1673,7 @@ cat.copy_browser.prototype = { obj.controller.view.cmd_transfer_items.setAttribute('disabled','true'); obj.controller.view.sel_copy_details.setAttribute('disabled','true'); obj.controller.view.cmd_create_brt.setAttribute('disabled','true'); + obj.controller.view.cmd_book_item_now.setAttribute('disabled','true'); obj.controller.view.sel_patron.setAttribute('disabled','true'); obj.controller.view.sel_mark_items_damaged.setAttribute('disabled','true'); obj.controller.view.sel_mark_items_missing.setAttribute('disabled','true'); @@ -1690,6 +1700,11 @@ cat.copy_browser.prototype = { obj.controller.view.sel_copy_details.setAttribute('disabled','false'); obj.controller.view.cmd_create_brt.setAttribute('disabled','false'); obj.controller.view.sel_patron.setAttribute('disabled','false'); + + var L = 0; for (var k in sel_copy_libs) L++; + if (L < 2) { + obj.controller.view.cmd_book_item_now.setAttribute('disabled','false'); + } } } catch(E) { obj.error.standard_unexpected_error_alert(document.getElementById('catStrings').getString('staff.cat.copy_browser.actions.error'),E); diff --git a/Open-ILS/xul/staff_client/server/cat/copy_browser.xul b/Open-ILS/xul/staff_client/server/cat/copy_browser.xul index 09a961d098..6c895a5ad7 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_browser.xul +++ b/Open-ILS/xul/staff_client/server/cat/copy_browser.xul @@ -73,7 +73,7 @@ vim:noet:sw=4:ts=4: <command id="cmd_broken" /> <command id="sel_copy_details"/> <command id="cmd_create_brt"/> - <!-- <command id="cmd_book_item_now"/> --> + <command id="cmd_book_item_now"/> <command id="sel_patron"/> <command id="sel_clip" /> <command id="cmd_clear" /> @@ -104,8 +104,10 @@ vim:noet:sw=4:ts=4: <menuitem command="sel_clip" label="&staff.cat.copy_browser.actions.sel_clip.label;" accesskey="&staff.cat.copy_browser.actions.sel_clip.accesskey;"/> <menuitem command="cmd_add_items_to_buckets" label="&staff.cat.copy_browser.actions.cmd_add_items_to_buckets.label;" accesskey="&staff.cat.copy_browser.actions.cmd_add_items_to_buckets.accesskey;"/> <menuitem command="sel_copy_details" label="&staff.cat.copy_browser.actions.sel_copy_details.label;" accesskey="&staff.cat.copy_browser.actions.sel_copy_details.label;" /> + <menuseparator/> <menuitem command="cmd_create_brt" label="&staff.cat.copy_browser.actions.cmd_create_brt.label;" accesskey="&staff.cat.copy_browser.actions.cmd_create_brt.accesskey;" /> - <!-- <menuitem command="cmd_book_item_now" label="&staff.cat.copy_browser.actions.cmd_book_item_now.label;" accesskey="&staff.cat.copy_browser.actions.cmd_book_item_now.accesskey;" /> --> + <menuitem command="cmd_book_item_now" label="&staff.cat.copy_browser.actions.cmd_book_item_now.label;" accesskey="&staff.cat.copy_browser.actions.cmd_book_item_now.accesskey;" /> + <menuseparator/> <menuitem command="sel_patron" label="&staff.cat.copy_browser.actions.sel_patron.label;" accesskey="&staff.cat.copy_browser.actions.sel_patron.accesskey;"/> <menuseparator/> <menuitem command="cmd_edit_items" label="&staff.cat.copy_browser.actions.cmd_edit_items.label;" accesskey="&staff.cat.copy_browser.actions.cmd_edit_items.accesskey;"/> diff --git a/Open-ILS/xul/staff_client/server/cat/util.js b/Open-ILS/xul/staff_client/server/cat/util.js index c360baf2e2..26329542fd 100644 --- a/Open-ILS/xul/staff_client/server/cat/util.js +++ b/Open-ILS/xul/staff_client/server/cat/util.js @@ -570,7 +570,7 @@ cat.util.edit_new_bresv = function(booking_results) { if (xulG.auth == undefined) { xulG.auth = {"session": {"key": ses()}}; } - xulG.booking_results = booking_results; + xulG.bresv_interface_opts = {"booking_results": booking_results}; xulG.new_tab( xulG.url_prefix("/eg/booking/reservation"), { diff --git a/Open-ILS/xul/staff_client/server/circ/copy_status.js b/Open-ILS/xul/staff_client/server/circ/copy_status.js index 0f22cbf20c..e2271d4f3a 100644 --- a/Open-ILS/xul/staff_client/server/circ/copy_status.js +++ b/Open-ILS/xul/staff_client/server/circ/copy_status.js @@ -64,6 +64,8 @@ circ.copy_status.prototype = { obj.controller.view.sel_mark_items_missing.setAttribute('disabled','true'); obj.controller.view.sel_patron.setAttribute('disabled','true'); obj.controller.view.cmd_triggered_events.setAttribute('disabled','true'); + obj.controller.view.cmd_create_brt.setAttribute('disabled','true'); + obj.controller.view.cmd_book_item_now.setAttribute('disabled','true'); obj.controller.view.sel_spine.setAttribute('disabled','true'); obj.controller.view.sel_transit_abort.setAttribute('disabled','true'); obj.controller.view.sel_clip.setAttribute('disabled','true'); @@ -88,6 +90,12 @@ circ.copy_status.prototype = { obj.controller.view.sel_copy_details.setAttribute('disabled','false'); obj.controller.view.sel_mark_items_damaged.setAttribute('disabled','false'); obj.controller.view.sel_mark_items_missing.setAttribute('disabled','false'); + if (obj.selected_one_unique_owning_lib()) { + obj.controller.view.cmd_book_item_now.setAttribute('disabled','false'); + } else { + obj.controller.view.cmd_book_item_now.setAttribute('disabled','true'); + } + obj.controller.view.cmd_create_brt.setAttribute('disabled','false'); obj.controller.view.sel_spine.setAttribute('disabled','false'); obj.controller.view.sel_transit_abort.setAttribute('disabled','false'); obj.controller.view.sel_clip.setAttribute('disabled','false'); @@ -184,6 +192,42 @@ circ.copy_status.prototype = { } } ], + 'cmd_create_brt' : [ + ['command'], + function() { + JSAN.use("cat.util"); + JSAN.use("util.functional"); + + var results = cat.util.make_bookable( + util.functional.map_list( + obj.selection_list, function (o) { + return o.copy_id; + } + ) + ); + if (results && results["brsrc"]) { + cat.util.edit_new_brsrc(results["brsrc"]); + } + } + ], + 'cmd_book_item_now' : [ + ['command'], + function() { + JSAN.use("cat.util"); + JSAN.use("util.functional"); + + var results = cat.util.make_bookable( + util.functional.map_list( + obj.selection_list, function (o) { + return o.copy_id; + } + ) + ); + if (results) { + cat.util.edit_new_bresv(results); + } + } + ], 'sel_checkin' : [ ['command'], function() { @@ -1008,6 +1052,15 @@ circ.copy_status.prototype = { }, + 'selected_one_unique_owning_lib': function () { + JSAN.use('util.functional'); + var list = util.functional.map_list( + this.selection_list, + function(o) { return o.owning_lib; } + ); + return util.functional.unique_list_values(list).length == 1; + }, + 'test_barcode' : function(bc) { var obj = this; var good = util.barcode.check(bc); @@ -1100,6 +1153,7 @@ circ.copy_status.prototype = { 'renewable' : details.circ ? 't' : 'f', 'copy_id' : details.copy.id(), 'acn_id' : details.volume ? details.volume.id() : -1, + 'owning_lib' : details.volume ? details.volume.owning_lib() : -1, 'barcode' : barcode, 'doc_id' : details.mvr ? details.mvr.doc_id() : null } diff --git a/Open-ILS/xul/staff_client/server/circ/copy_status.xul b/Open-ILS/xul/staff_client/server/circ/copy_status.xul index 1c7f65de3f..fdb1433bff 100644 --- a/Open-ILS/xul/staff_client/server/circ/copy_status.xul +++ b/Open-ILS/xul/staff_client/server/circ/copy_status.xul @@ -114,6 +114,8 @@ <command id="cmd_alt_view" /> <command id="cmd_triggered_events" /> <command id="save_columns" /> + <command id="cmd_create_brt" disabled="true"/> + <command id="cmd_book_item_now" disabled="true"/> <command id="sel_copy_details" disabled="true"/> <command id="sel_mark_items_damaged" disabled="true"/> <command id="sel_mark_items_missing" disabled="true"/> diff --git a/Open-ILS/xul/staff_client/server/circ/copy_status_overlay.xul b/Open-ILS/xul/staff_client/server/circ/copy_status_overlay.xul index a419f41291..5b8586df91 100644 --- a/Open-ILS/xul/staff_client/server/circ/copy_status_overlay.xul +++ b/Open-ILS/xul/staff_client/server/circ/copy_status_overlay.xul @@ -17,6 +17,9 @@ <menuitem command="sel_copy_details" label="&staff.circ.copy_status_overlay.sel_copy_details.label;" accesskey="&staff.circ.copy_status_overlay.sel_copy_details.accesskey;" /> <menuitem command="sel_patron" label="&staff.circ.copy_status_overlay.sel_patron.label;" accesskey="&staff.circ.copy_status_overlay.sel_patron.accesskey;"/> <menuseparator/> + <menuitem command="cmd_create_brt" label="&staff.circ.copy_status_overlay.cmd_create_brt.label;" accesskey="&staff.circ.copy_status_overlay.cmd_create_brt.accesskey;"/> + <menuitem command="cmd_book_item_now" label="&staff.circ.copy_status_overlay.cmd_book_item_now.label;" accesskey="&staff.circ.copy_status_overlay.cmd_book_item_now.accesskey;"/> + <menuseparator/> <menuitem command="sel_edit" label="&staff.circ.copy_status_overlay.sel_edit.label;" accesskey="&staff.circ.copy_status_overlay.sel_edit.accesskey;" /> <menuseparator/> <menuitem command="sel_mark_items_damaged" label="&staff.circ.copy_status_overlay.sel_mark_items_damaged.label;" accesskey="&staff.circ.copy_status_overlay.sel_mark_items_damaged.accesskey;"/> @@ -150,6 +153,9 @@ <menuitem command="sel_patron" label="&staff.circ.copy_status_overlay.sel_patron.label;" accesskey="&staff.circ.copy_status_overlay.sel_patron.accesskey;"/> <menuitem command="cmd_triggered_events" label="&staff.circ.copy_status_overlay.cmd_triggered_events.label;" accesskey="&staff.circ.copy_status_overlay.cmd_triggered_events.accesskey;"/> <menuseparator /> + <menuitem command="cmd_create_brt" label="&staff.circ.copy_status_overlay.cmd_create_brt.label;" accesskey="&staff.circ.copy_status_overlay.cmd_create_brt.accesskey;"/> + <menuitem command="cmd_book_item_now" label="&staff.circ.copy_status_overlay.cmd_book_item_now.label;" accesskey="&staff.circ.copy_status_overlay.cmd_book_item_now.accesskey;"/> + <menuseparator /> <menuitem command="sel_edit" label="&staff.circ.copy_status_overlay.sel_edit.label;" accesskey="&staff.circ.copy_status_overlay.sel_edit.accesskey;" /> <menuseparator /> <menuitem command="sel_mark_items_damaged" label="&staff.circ.copy_status_overlay.sel_mark_items_damaged.label;" accesskey="&staff.circ.copy_status_overlay.sel_mark_items_damaged.accesskey;"/> diff --git a/Open-ILS/xul/staff_client/server/patron/display.js b/Open-ILS/xul/staff_client/server/patron/display.js index ee137af67c..d6d652f44e 100644 --- a/Open-ILS/xul/staff_client/server/patron/display.js +++ b/Open-ILS/xul/staff_client/server/patron/display.js @@ -375,6 +375,27 @@ patron.display.prototype = { } } ], + 'cmd_patron_reservation' : [ + ['command'], + function(ev) { + if (xulG.auth == undefined) { + xulG.auth = {"session": {"key": ses()}}; + } + xulG.bresv_interface_opts = { + "patron_barcode": obj.patron.card().barcode() + }; + xulG.new_tab( + "/eg/booking/reservation", + { + "tab_name": offlineStrings.getString( + "menu.cmd_booking_reservation.tab" + ), + "browser": false + }, + xulG + ); + } + ], 'cmd_patron_exit' : [ ['command'], function(ev) { diff --git a/Open-ILS/xul/staff_client/server/patron/display.xul b/Open-ILS/xul/staff_client/server/patron/display.xul index 7c2931c0b3..441cb062ba 100644 --- a/Open-ILS/xul/staff_client/server/patron/display.xul +++ b/Open-ILS/xul/staff_client/server/patron/display.xul @@ -102,6 +102,7 @@ <command id="cmd_patron_info_groups" /> <command id="cmd_patron_other" /> <command id="cmd_patron_alert" /> + <command id="cmd_patron_reservation" /> <command id="cmd_patron_exit" /> <command id="cmd_patron_retrieve" /> <command id="cmd_patron_merge" /> diff --git a/Open-ILS/xul/staff_client/server/patron/display_horiz.xul b/Open-ILS/xul/staff_client/server/patron/display_horiz.xul index 97814a7822..587c8cce4a 100644 --- a/Open-ILS/xul/staff_client/server/patron/display_horiz.xul +++ b/Open-ILS/xul/staff_client/server/patron/display_horiz.xul @@ -102,6 +102,7 @@ <command id="cmd_patron_info_groups" /> <command id="cmd_patron_other" /> <command id="cmd_patron_alert" /> + <command id="cmd_patron_reservation" /> <command id="cmd_patron_exit" /> <command id="cmd_patron_retrieve" /> <command id="cmd_patron_merge" /> diff --git a/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul b/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul index 55d071f0ae..aff557587b 100644 --- a/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul +++ b/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul @@ -86,6 +86,13 @@ <button id="PatronNavBar_edit" command="cmd_patron_edit" class="nav" label="&staff.patron_navbar.edit;" accesskey="&staff.patron_navbar.edit.accesskey;"/> <button id="PatronNavBar_messages" label="&staff.patron_navbar.actions.menu.standing_penalties.label;" accesskey="&staff.patron_navbar.actions.menu.standing_penalties.accesskey;" command="cmd_standing_penalties" class="nav"/> + + <button id="PatronNavBar_booking" command="cmd_patron_booking" class="nav" label="&staff.patron_navbar.booking;" accesskey="&staff.patron_navbar.booking.accesskey;" type="menu"> + <menupopup> + <menuitem label="&staff.main.menu.booking.reservation.label;" accesskey="&staff.main.menu.booking.reservation.accesskey;" command="cmd_patron_reservation" /> + </menupopup> + </button> + <button id="PatronNavBar_other" command="cmd_patron_other" class="nav" label="&staff.patron_navbar.other;" accesskey="&staff.patron_navbar.other.accesskey;" type="menu"> <menupopup> <menuitem label="&staff.patron_navbar.alert;" accesskey="&staff.patron_navbar.alert.accesskey;" command="cmd_patron_alert"/> diff --git a/Open-ILS/xul/staff_client/server/patron/display_overlay.xul b/Open-ILS/xul/staff_client/server/patron/display_overlay.xul index 7796cb96ca..afbc23ce92 100644 --- a/Open-ILS/xul/staff_client/server/patron/display_overlay.xul +++ b/Open-ILS/xul/staff_client/server/patron/display_overlay.xul @@ -86,6 +86,13 @@ <button id="PatronNavBar_edit" command="cmd_patron_edit" class="nav" label="&staff.patron_navbar.edit;" accesskey="&staff.patron_navbar.edit.accesskey;"/> <button id="PatronNavBar_messages" label="&staff.patron_navbar.actions.menu.standing_penalties.label;" accesskey="&staff.patron_navbar.actions.menu.standing_penalties.accesskey;" command="cmd_standing_penalties" class="nav"/> + + <button id="PatronNavBar_booking" command="cmd_patron_booking" class="nav" label="&staff.patron_navbar.booking;" accesskey="&staff.patron_navbar.booking.accesskey;" type="menu"> + <menupopup> + <menuitem label="&staff.main.menu.booking.reservation.label;" accesskey="&staff.main.menu.booking.reservation.accesskey;" command="cmd_patron_reservation" /> + </menupopup> + </button> + <button id="PatronNavBar_other" command="cmd_patron_other" class="nav" label="&staff.patron_navbar.other;" accesskey="&staff.patron_navbar.other.accesskey;" type="menu"> <menupopup> <menuitem label="&staff.patron_navbar.alert;" accesskey="&staff.patron_navbar.alert.accesskey;" command="cmd_patron_alert"/> -- 2.11.0