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;
}
__PACKAGE__->register_method(
method => "capture_reservation",
- api_name => "open-ils.booking.reservation.capture",
+ api_name => "open-ils.booking.reservations.capture",
argc => 2,
signature=> {
params => [
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;
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;
+}
'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") + ".";
'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",
'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"
}
*/
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; };
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();
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;
}
}
};
);
}
+function get_brt_by_id(id) {
+ return pcrud.retrieve("brt", id);
+}
+
function get_brsrc_id_list() {
var options = {"type": our_brt.id()};
);
}
-// 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;
}
if (barcode == "") {
alert(localeStrings.WHERES_THE_BARCODE);
return;
+ } else if (!reserve_timestamp_range.is_valid()) {
+ alert(localeStrings.INVALID_TS_RANGE);
+ return;
}
var results;
try {
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(
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];
}
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");
option.appendChild(document.createTextNode(brt_list[i].name()));
selector.appendChild(option);
}
+ targ_div.innerHTML = "";
targ_div.appendChild(selector);
}
}
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()});
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");
* 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);
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 != "") {
}
}, 0);
setTimeout(function() { init_bresv_grid(widg.value); }, 0);
-
- reveal_dom_element(document.getElementById("reserve_under"));
}
}
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);
}
}
}
+/* 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
*/
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"));
}
<!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'>
<!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">
<!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">
<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">
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"
<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">
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 };
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');
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) {
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');
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');
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);
<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" />
<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;"/>
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"),
{
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');
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');
}
}
],
+ '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() {
},
+ '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);
'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
}
<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"/>
<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;"/>
<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;"/>
}
}
],
+ '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) {
<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" />
<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" />
<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"/>
<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"/>