[open-ils-commits] r15247 - in trunk/Open-ILS: src/perlmods/OpenILS/Application web/css/skin/default web/js/dojo/openils/booking/nls web/js/ui/default/booking web/opac/locale/en-US web/templates/default/booking xul/staff_client/chrome/content/main xul/staff_client/chrome/locale/en-US xul/staff_client/server/patron (miker)

svn at svn.open-ils.org svn at svn.open-ils.org
Wed Dec 30 10:43:35 EST 2009


Author: miker
Date: 2009-12-30 10:43:32 -0500 (Wed, 30 Dec 2009)
New Revision: 15247

Added:
   trunk/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js
   trunk/Open-ILS/web/js/ui/default/booking/common.js
   trunk/Open-ILS/web/js/ui/default/booking/pull_list.js
   trunk/Open-ILS/web/templates/default/booking/pull_list.tt2
Modified:
   trunk/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm
   trunk/Open-ILS/web/css/skin/default/booking.css
   trunk/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js
   trunk/Open-ILS/web/js/ui/default/booking/reservation.js
   trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
   trunk/Open-ILS/web/templates/default/booking/reservation.tt2
   trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js
   trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
   trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
   trunk/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul
   trunk/Open-ILS/xul/staff_client/server/patron/display_overlay.xul
Log:
Patch from Lebbeous Fogle-Weekley adding a pull list interface for booking reservations

Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm	2009-12-30 15:43:32 UTC (rev 15247)
@@ -3,6 +3,7 @@
 use strict;
 use warnings;
 
+use POSIX qw/strftime/;
 use OpenILS::Application;
 use base qw/OpenILS::Application/;
 
@@ -478,7 +479,10 @@
     } ];
     $cstore->disconnect;
 
-    return $ids if not $whole_obj;
+    if (not $whole_obj) {
+        $e->disconnect;
+        return $ids;
+    }
 
     my $bresv_list = $e->search_booking_reservation([
         {"id" => $ids},
@@ -489,6 +493,7 @@
             }
         }]
     );
+    $e->disconnect;
     return $bresv_list ? $bresv_list : [];
 }
 __PACKAGE__->register_method(
@@ -522,27 +527,48 @@
 );
 
 
+sub naive_ts_string { strftime("%F %T", localtime(shift)); }
+
 sub get_pull_list {
-    my ($self, $client, $auth, $range, $pickup_lib) = @_;
+    my ($self, $client, $auth, $range, $interval_secs, $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';
+    return $e->die_event unless (
+        ref($range) eq 'ARRAY' or
+        ($interval_secs = int($interval_secs)) > 0
+    );
 
+    $range = [ naive_ts_string(time), naive_ts_string(time + $interval_secs) ]
+        if not $range;
+
+    my @fundamental_constraints = (
+        {"current_resource" => {"!=" => undef}},
+        {"capture_time" => undef},
+        {"cancel_time" => undef},
+        {"return_time" => undef},
+        {"pickup_time" => undef}
+    );
+
     my $query = {
-        "select" => {"bresv" => ["id"]},
+        "select" => {
+            "bresv" => [
+                "current_resource",
+                {
+                    "column" => "start_time",
+                    "transform" => "min",
+                    "aggregate" => 1
+                }
+            ]
+        },
         "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}
+                @fundamental_constraints
             ],
         }
     };
@@ -550,33 +576,128 @@
         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/]
-                }
+    my $rows = $e->json_query($query);
+    my %resource_id_map = ();
+    my @all_ids = ();
+    if (@$rows) {
+        my $id_query = {
+            "select" => {"bresv" => ["id"]},
+            "from" => "bresv",
+            "where" => {
+                "-and" => [
+                    {"current_resource" => "PLACEHOLDER"},
+                    {"start_time" => "PLACEHOLDER"},
+                ]
             }
-        ]);
-        return $bresv_list ? $bresv_list : [];
+        };
+        if ($pickup_lib) {
+            push @{$id_query->{"where"}->{"-and"}},
+                {"pickup_lib" => $pickup_lib};
+        }
+
+        foreach (@$rows) {
+            $id_query->{"where"}->{"-and"}->[0]->{"current_resource"} =
+                $_->{"current_resource"};
+            $id_query->{"where"}->{"-and"}->[1]->{"start_time"} =
+                $_->{"start_time"};
+
+            my $results = $e->json_query($id_query);
+            if (@$results) {
+                my @these_ids = map { $_->{"id"} } @$results;
+                push @all_ids, @these_ids;
+
+                $resource_id_map{$_->{"current_resource"}} = [@these_ids];
+            }
+        }
+    }
+    if (@all_ids) {
+        my %bresv_lookup = (
+            map { $_->id => $_ } @{
+                $e->search_booking_reservation([{"id" => [@all_ids]}, {
+                    flesh => 1,
+                    flesh_fields => { bresv => [
+                            "usr",
+                            "target_resource_type",
+                            "current_resource"
+                    ]}
+                }])
+            }
+        );
+        $e->disconnect;
+        return [ map {
+            my $key = $_;
+            my $one = $bresv_lookup{$resource_id_map{$key}->[0]};
+            my $result = {
+                "current_resource" => $one->current_resource,
+                "target_resource_type" => $one->target_resource_type,
+                "reservations" => [
+                    map { $bresv_lookup{$_} } @{$resource_id_map{$key}}
+                ]
+            };
+            foreach (@{$result->{"reservations"}}) {    # deflesh
+                $_->current_resource($_->current_resource->id);
+                $_->target_resource_type($_->target_resource_type->id);
+            }
+            $result;
+        } keys %resource_id_map ];
     } else {
-        return $ids;    # empty list
+        $e->disconnect;
+        return [];
     }
 }
 __PACKAGE__->register_method(
     method   => "get_pull_list",
     api_name => "open-ils.booking.reservations.get_pull_list",
+    argc     => 4,
+    signature=> {
+        params => [
+            {type => "string", desc => "Authentication token"},
+            {type => "array", desc =>
+                "range: Date/time range for reservations (opt)"},
+            {type => "int", desc =>
+                "interval: Seconds from now (instead of range)"},
+            {type => "number", desc => "(Optional) Pickup library"}
+        ],
+        return => { desc => "An array of hashes, each containing key/value " .
+            "pairs describing resource, resource type, and a list of " .
+            "reservations that claim the given resource." }
+    }
+);
+
+
+sub get_copy_fleshed_just_right {
+    my ($self, $client, $auth, $barcode) = @_;
+
+    my $e = new_editor(authtoken => $auth);
+    my $results = $e->search_asset_copy([
+        {"barcode" => $barcode},
+        {
+            "flesh" => 1,
+            "flesh_fields" => {"acp" => [qw/call_number location/]}
+        }
+    ]);
+
+    if (ref($results) eq 'ARRAY') {
+        $e->disconnect;
+        return $results->[0] unless ref $barcode;
+        return +{ map { $_->barcode => $_ } @$results };
+    } else {
+        return $e->die_event;
+    }
+}
+__PACKAGE__->register_method(
+    method   => "get_copy_fleshed_just_right",
+    api_name => "open-ils.booking.asset.get_copy_fleshed_just_right",
     argc     => 2,
     signature=> {
         params => [
-            {type => 'string', desc => 'Authentication token'},
-            {type => 'array', desc => 'Date/time range for reservations'},
-            {type => 'number', desc => '(Optional) Pickup library'}
+            {type => "string", desc => "Authentication token"},
+            {type => "mixed", desc => "One barcode or an array of them"},
         ],
-        return => { desc => "An array of reservations, fleshed with usr, " .
-            "current_resource, and target_resource_type" }
+        return => { desc =>
+            "A copy, or a hash of copies keyed by barcode if an array of " .
+            "barcodes was given"
+        }
     }
 );
 

Modified: trunk/Open-ILS/web/css/skin/default/booking.css
===================================================================
--- trunk/Open-ILS/web/css/skin/default/booking.css	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/web/css/skin/default/booking.css	2009-12-30 15:43:32 UTC (rev 15247)
@@ -52,6 +52,23 @@
     font-weight: bold;
     font-style: italic;
 }
-div#preselected_patron {
-    font-size: 12pt;
+input#arbitrary_resource { margin-left: 8px; margin-right: 8px; }
+div#or { font-size: 12pt; font-weight: bold; }
+input#interval_in_days { width: 75px; }
+table#the_table thead tr th {
+    vertical-align: top;
+    background-color: #dddddd;
+    color: #000000;
+    font-weight: bold;
+    padding: 0 6px 0 6px;
+    border-left: 1px #333333 solid;
+    border-right: 1px #333333 solid;
 }
+tbody#the_table_body td {
+    vertical-align: top;
+    padding: 2px;
+    border-top: 1px #cccccc solid;
+    border-left: 1px #cccccc solid;
+    border-bottom: 1px #333333 solid;
+    border-right: 1px #333333 solid;
+}

Added: trunk/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js
===================================================================
--- trunk/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js	                        (rev 0)
+++ trunk/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js	2009-12-30 15:43:32 UTC (rev 15247)
@@ -0,0 +1,19 @@
+{
+    'PULL_LIST_NO_RESPONSE': "No response from server trying to get pull list!",
+    'PULL_LIST_ERROR': "Error trying to fetch pull list: ",
+    'COPY_LOOKUP_NO_RESPONSE': "No response looking up copies by barcode",
+    'COPY_LOOKUP_ERROR': "Error looking up copies by barcode: ",
+    'COPY_MISSING': "Unexpected error: No information for copy: ",
+
+    'AUTO_pickup_lib_selector': "Select a location for pickup:",
+    'AUTO_pull_list_title': "Booking Pull List",
+    'AUTO_interval_in_days': "Generate list for this many days hence: ",
+    'AUTO_ATTR_VALUE_fetch': "Fetch",
+    'AUTO_th_title_or_name': "Title or name",
+    'AUTO_th_barcode': "Barcode",
+    'AUTO_th_call_number': "Call number",
+    'AUTO_th_copy_location': "Copy location",
+    'AUTO_th_copy_number': "Copy number",
+    'AUTO_th_resv_details': "Reservation details",
+    'AUTO_ATTR_VALUE_print': "Print",
+}

Modified: trunk/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js
===================================================================
--- trunk/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js	2009-12-30 15:43:32 UTC (rev 15247)
@@ -45,6 +45,8 @@
         "Error retrieving booking resource type",
     'INVALID_TS_RANGE':
         "You must choose a valid start and end time for the reservation.",
+    'BRSRC_NOT_FOUND': "Could not locate that resource.",
+    'BRSRC_RETRIVE_ERROR': "Error retrieving resource: ",
     'ANY': "ANY",
 
     'AUTO_choose_a_brt': "Choose a Bookable Resource Type",
@@ -62,5 +64,12 @@
     'AUTO_bresv_grid_resource': "Resource",
     'AUTO_bresv_grid_start_time': "Start time",
     'AUTO_bresv_grid_end_time': "End time",
-    'AUTO_brt_noncat_only': "Show only non-cataloged bookable resource types"
+    'AUTO_brt_noncat_only': "Show only non-cataloged bookable resource types",
+    'AUTO_arbitrary_resource':
+        "Enter the barcode of a cataloged, bookable resource:",
+    'AUTO_explain_bookable':
+        "To reserve an item that is not yet registered as a bookable " +
+        "resource, find it in the catalog or under <em>Display Item</em>, and "+
+        "select <em>Make Item Bookable</em> or <em>Book Item Now</em> there.",
+    'AUTO_or': '- Or -'
 }

Added: trunk/Open-ILS/web/js/ui/default/booking/common.js
===================================================================
--- trunk/Open-ILS/web/js/ui/default/booking/common.js	                        (rev 0)
+++ trunk/Open-ILS/web/js/ui/default/booking/common.js	2009-12-30 15:43:32 UTC (rev 15247)
@@ -0,0 +1,64 @@
+/* Quick and dirty way to localize some strings; not recommended for reuse.
+ * I'm sure dojo provides a better mechanism for this, but at the moment
+ * this is faster to implement anew than figuring out the Right way to do
+ * the same thing w/ dojo.
+ */
+function init_auto_l10n(el) {
+    function do_it(myel, cls) {
+        if (cls) {
+            var clss = cls.split(" ");
+            for (var k in clss) {
+                var parts = clss[k].match(/^AUTO_ATTR_([A-Z]+)_.+$/);
+                if (parts && localeStrings[clss[k]]) {
+                    myel.setAttribute(
+                        parts[1].toLowerCase(), localeStrings[clss[k]]
+                    );
+                } else if (clss[k].match(/^AUTO_/) && localeStrings[clss[k]]) {
+                    myel.innerHTML = localeStrings[clss[k]];
+                }
+            }
+        }
+    }
+
+    for (var i in el.attributes) {
+        if (el.attributes[i].nodeName == "class") {
+            do_it(el, el.attributes[i].value);
+            break;
+        }
+    }
+    for (var i in el.childNodes) {
+        if (el.childNodes[i].nodeType == 1) { // element node?
+            init_auto_l10n(el.childNodes[i]); // recurse!
+        }
+    }
+}
+
+function get_keys(L) { var K = []; for (var k in L) K.push(k); return K; }
+function hide_dom_element(e) { e.style.display = "none"; };
+function reveal_dom_element(e) { e.style.display = ""; };
+function formal_name(u) {
+    var name = u.family_name() + ", " + u.first_given_name();
+    if (u.second_given_name())
+        name += (" " + u.second_given_name());
+    return name;
+}
+function humanize_timestamp_string(ts) {
+    /* For now, this discards time zones. */
+    var parts = ts.split("T");
+    var timeparts = parts[1].split("-")[0].split(":");
+    return parts[0] + " " + timeparts[0] + ":" + timeparts[1];
+}
+function is_ils_error(e) { return (e.ilsevent != undefined); }
+function is_ils_actor_card_error(e) {
+    return (e.textcode == "ACTOR_CARD_NOT_FOUND");
+}
+function my_ils_error(leader, e) {
+    var s = leader + "\n";
+    var keys = [
+        "ilsevent", "desc", "textcode", "servertime", "pid", "stacktrace"
+    ];
+    for (var i in keys) {
+        if (e[keys[i]]) s += ("\t" + keys[i] + ": " + e[keys[i]] + "\n");
+    }
+    return s;
+}

Added: trunk/Open-ILS/web/js/ui/default/booking/pull_list.js
===================================================================
--- trunk/Open-ILS/web/js/ui/default/booking/pull_list.js	                        (rev 0)
+++ trunk/Open-ILS/web/js/ui/default/booking/pull_list.js	2009-12-30 15:43:32 UTC (rev 15247)
@@ -0,0 +1,190 @@
+dojo.require("openils.User");
+dojo.require("openils.PermaCrud");
+dojo.require("fieldmapper.OrgUtils");
+dojo.require("openils.widget.OrgUnitFilteringSelect");
+dojo.requireLocalization("openils.booking", "pull_list");
+
+var localeStrings = dojo.i18n.getLocalization("openils.booking", "pull_list");
+var pcrud = new openils.PermaCrud();
+
+var pickup_lib_selected;
+var acp_cache = {};
+
+function init_pickup_lib_selector() {
+    var User = new openils.User();
+    User.buildPermOrgSelector(
+        "RETRIEVE_RESERVATION_PULL_LIST", pickup_lib_selector, null,
+        function() {
+            pickup_lib_selected = pickup_lib_selector.getValue();
+            dojo.connect(pickup_lib_selector, "onChange",
+                function() { pickup_lib_selected = this.getValue(); }
+            )
+        }
+    );
+}
+
+function retrieve_pull_list(ivl_in_days) {
+    var secs = Number(ivl_in_days) * 86400;
+
+    if (isNaN(secs) || secs < 1)
+        throw new Error("Invalid interval");
+
+    return fieldmapper.standardRequest(
+        ["open-ils.booking", "open-ils.booking.reservations.get_pull_list"],
+        [xulG.auth.session.key, null, secs, pickup_lib_selected]
+    );
+}
+
+function dom_table_rowid(resource_id) {
+    return "pull_list_resource_" + resource_id;
+}
+
+function generate_result_row(one) {
+    function cell(id, content) {
+        var td = document.createElement("td");
+        if (id != undefined) td.setAttribute("id", id);
+        td.appendChild(document.createTextNode(content));
+        return td;
+    }
+
+    function reservation_info_cell(one) {
+        var td = document.createElement("td");
+        for (var i in one.reservations) {
+            var one_resv = one.reservations[i];
+            var div = document.createElement("div");
+            var s = humanize_timestamp_string(one_resv.start_time()) + " - " +
+                humanize_timestamp_string(one_resv.end_time()) + " " +
+                formal_name(one_resv.usr());
+            /* FIXME: The above need patron barcode instead of name, but
+             * that requires a fix in the middle layer to flesh on the
+             * right stuff. */
+            div.appendChild(document.createTextNode(s));
+            td.appendChild(div);
+        }
+        return td;
+    }
+
+    var baseid = dom_table_rowid(one.current_resource.id());
+
+    var cells = [];
+    cells.push(cell(undefined, one.target_resource_type.name()));
+    cells.push(cell(undefined, one.current_resource.barcode()));
+    cells.push(cell(baseid + "_call_number", "-"));
+    cells.push(cell(baseid + "_copy_location", "-"));
+    cells.push(cell(baseid + "_copy_number", "-"));
+    cells.push(reservation_info_cell(one));
+
+    var row = document.createElement("tr");
+    row.setAttribute("id", baseid);
+
+    for (var i in cells) row.appendChild(cells[i]);
+    return row;
+}
+
+function render_pull_list_fundamentals(list) {
+    var rows = [];
+
+    for (var i in list)
+        rows.push(generate_result_row(list[i]));
+
+    document.getElementById("the_table_body").innerHTML = "";
+
+    for (var i in rows)
+        document.getElementById("the_table_body").appendChild(rows[i]);
+}
+
+function get_all_relevant_acp(list) {
+    var barcodes = [];
+    for (var i in list) {
+        if (list[i].target_resource_type.catalog_item()) {
+            /* There shouldn't be any duplicates. No need to worry bout that */
+            barcodes.push(list[i].current_resource.barcode());
+        }
+    }
+    var results = fieldmapper.standardRequest(
+        [
+            "open-ils.booking",
+            "open-ils.booking.asset.get_copy_fleshed_just_right"
+        ],
+        [xulG.auth.session.key, barcodes]
+    );
+
+    if (!results) {
+        alert(localeStrings.COPY_LOOKUP_NO_RESPONSE);
+        return null;
+    } else if (is_ils_error(results)) {
+        alert(my_ils_error(localeStrings.COPY_LOOKUP_ERROR, results));
+        return null;
+    } else {
+        return results;
+    }
+}
+
+function fill_in_pull_list_details(list, acp_cache) {
+    for (var i in list) {
+        var one = list[i];
+        if (one.target_resource_type.catalog_item() == "t") {
+            /* FIXME: This block could stand to be a lot more elegant. */
+            var call_number_el = document.getElementById(
+                dom_table_rowid(one.current_resource.id()) + "_call_number"
+            );
+            var copy_location_el = document.getElementById(
+                dom_table_rowid(one.current_resource.id()) + "_copy_location"
+            );
+            var copy_number_el = document.getElementById(
+                dom_table_rowid(one.current_resource.id()) + "_copy_number"
+            );
+
+            var bc = one.current_resource.barcode();
+
+            if (acp_cache[bc]) {
+                if (call_number_el && acp_cache[bc].call_number()) {
+                    var value = acp_cache[bc].call_number().label();
+                    if (value) call_number_el.innerHTML = value;
+                }
+                if (copy_location_el && acp_cache[bc].location()) {
+                    var value = acp_cache[bc].location().name();
+                    if (value) copy_location_el.innerHTML = value;
+                }
+                if (copy_number_el) {
+                    var value = acp_cache[bc].copy_number();
+                    if (value) copy_number_el.innerHTML = value;
+                }
+            } else {
+                alert(localeStrings.COPY_MISSING + bc);
+            }
+        }
+    }
+}
+
+function populate_pull_list(form) {
+    /* Step 1: get the pull list from the server. */
+    try {
+        var results = retrieve_pull_list(form.interval_in_days.value);
+    } catch (E) {
+        alert(localeStrings.PULL_LIST_ERROR + E);
+        return;
+    }
+    if (results == null) {
+        alert(localeStrings.PULL_LIST_NO_RESPONSE);
+        return;
+    } else if (is_ils_error(results)) {
+        alert(my_ils_error(localeStrings.PULL_LIST_ERROR, results));
+        return;
+    }
+
+    /* Step 2: render the table with the pull list */
+    render_pull_list_fundamentals(results);
+
+    /* Step 3: asynchronously fill in the copy details we're missing */
+    setTimeout(function() {
+        var acp_cache = {};
+        if ((acp_cache = get_all_relevant_acp(results)))
+            fill_in_pull_list_details(results, acp_cache);
+    }, 0);
+}
+
+function my_init() {
+    init_pickup_lib_selector();
+    init_auto_l10n(document.getElementById("auto_l10n_start_here"));
+}

Modified: trunk/Open-ILS/web/js/ui/default/booking/reservation.js
===================================================================
--- trunk/Open-ILS/web/js/ui/default/booking/reservation.js	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/web/js/ui/default/booking/reservation.js	2009-12-30 15:43:32 UTC (rev 15247)
@@ -15,6 +15,7 @@
 var pcrud = new openils.PermaCrud();
 var opts;
 var our_brt;
+var brt_list = [];
 var brsrc_index = {};
 var bresv_index = {};
 var just_reserved_now = {};
@@ -184,21 +185,6 @@
 /*
  * Misc helper functions
  */
-function hide_dom_element(e) { e.style.display = "none"; };
-function reveal_dom_element(e) { e.style.display = ""; };
-function get_keys(L) { var K = []; for (var k in L) K.push(k); return K; }
-function formal_name(u) {
-    var name = u.family_name() + ", " + u.first_given_name();
-    if (u.second_given_name())
-        name += (" " + u.second_given_name());
-    return name;
-}
-function humanize_timestamp_string(ts) {
-    /* For now, this discards time zones. */
-    var parts = ts.split("T");
-    var timeparts = parts[1].split("-")[0].split(":");
-    return parts[0] + " " + timeparts[0] + ":" + timeparts[1];
-}
 function set_datagrid_empty_store(grid) {
     grid.setStore(
         new dojo.data.ItemFileReadStore(
@@ -206,20 +192,6 @@
         )
     );
 }
-function is_ils_error(e) { return (e.ilsevent != undefined); }
-function is_ils_actor_card_error(e) {
-    return (e.textcode == "ACTOR_CARD_NOT_FOUND");
-}
-function my_ils_error(header, e) {
-    var s = header + "\n";
-    var keys = [
-        "ilsevent", "desc", "textcode", "servertime", "pid", "stacktrace"
-    ];
-    for (var i in keys) {
-        if (e[keys[i]]) s += ("\t" + keys[i] + ": " + e[keys[i]] + "\n");
-    }
-    return s;
-}
 
 /*
  * These functions communicate with the middle layer.
@@ -484,6 +456,27 @@
     );
 }
 
+function munge_specific_resource(barcode) {
+    try {
+        var brsrc_list = pcrud.search('brsrc', {'barcode': barcode});
+        if (brsrc_list && brsrc_list.length > 0) {
+            opts.booking_results = {
+                "brt": [[brsrc_list[0].type(), 0, 1]],
+                "brsrc": [[brsrc_list[0].id(), 0, 1]],
+            };
+            if (!(our_brt = get_brt_by_id(opts.booking_results.brt[0][0]))) {
+                alert(localeStrings.COULD_NOT_RETRIEVE_BRT_PASSED_IN);
+            } else {
+                init_reservation_interface();
+            }
+        } else {
+            alert(localeStrings.BRSRC_NOT_FOUND);
+        }
+    } catch (E) {
+        alert(localeStrings.BRSRC_RETRIEVE_ERROR + E);
+    }
+}
+
 /*
  * These functions deal with interface tricks (populating widgets,
  * changing the page, etc.).
@@ -492,14 +485,10 @@
     if (!targ_div) {
         alert(localeStrings.NO_TARG_DIV);
     } else {
-        var brt_list = xulG.brt_list = get_all_noncat_brt();
+        brt_list = get_all_noncat_brt();
         if (!brt_list || brt_list.length < 1) {
-            targ_div.appendChild(
-                document.createTextNode(localeStrings.NO_BRT_RESULTS)
-            );
-            document.getElementById(
-                "brt_select_other_controls"
-            ).style.display = "none";
+            document.getElementById("select_noncat_brt_block").
+                style.display = "none";
         } else {
             var selector = document.createElement("select");
             selector.setAttribute("id", "brt_selector");
@@ -520,17 +509,29 @@
     }
 }
 
-function init_reservation_interface(f) {
+function init_resv_iface_arb() {
+    init_reservation_interface(document.getElementById("arbitrary_resource"));
+}
+
+function init_resv_iface_sel() {
+    init_reservation_interface(document.getElementById("brt_selector"));
+}
+
+function init_reservation_interface(widget) {
+    /* Save a global reference to the brt we're going to reserve */
+    if (widget && (widget.selectedIndex != undefined)) {
+        our_brt = brt_list[widget.selectedIndex];
+    } else if (widget != undefined) {
+        if (!munge_specific_resource(widget.value))
+            return;
+    }
+
     /* Hide and reveal relevant divs. */
     var search_block = document.getElementById("brt_search_block");
     var reserve_block = document.getElementById("brt_reserve_block");
     hide_dom_element(search_block);
     reveal_dom_element(reserve_block);
 
-    /* Save a global reference to the brt we're going to reserve */
-    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()});
     if (!bra_list) {
@@ -587,12 +588,6 @@
     /* Add a prominent label reminding the user what resource type they're
      * 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();
 }
 
@@ -702,41 +697,6 @@
     }
 }
 
-/* Quick and dirty way to localize some strings; not recommended for reuse.
- * I'm sure dojo provides a better mechanism for this, but at the moment
- * this is faster to implement anew than figuring out the Right way to do
- * the same thing w/ dojo.
- */
-function init_auto_l10n(el) {
-    function do_it(myel, cls) {
-        if (cls) {
-            var clss = cls.split(" ");
-            for (var k in clss) {
-                var parts = clss[k].match(/^AUTO_ATTR_([A-Z]+)_.+$/);
-                if (parts && localeStrings[clss[k]]) {
-                    myel.setAttribute(
-                        parts[1].toLowerCase(), localeStrings[clss[k]]
-                    );
-                } else if (clss[k].match(/^AUTO_/) && localeStrings[clss[k]]) {
-                    myel.innerHTML = localeStrings[clss[k]];
-                }
-            }
-        }
-    }
-
-    for (var i in el.attributes) {
-        if (el.attributes[i].nodeName == "class") {
-            do_it(el, el.attributes[i].value);
-            break;
-        }
-    }
-    for (var i in el.childNodes) {
-        if (el.childNodes[i].nodeType == 1) { // element node?
-            init_auto_l10n(el.childNodes[i]); // recurse!
-        }
-    }
-}
-
 /* 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
@@ -757,16 +717,9 @@
     }
 
     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. */
-        }
+        document.getElementById("contain_patron_barcode").style.display="none";
+        document.getElementById("patron_barcode").value = opts.patron_barcode;
+        update_bresv_grid();
     }
 
     return true;

Modified: trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
===================================================================
--- trunk/Open-ILS/web/opac/locale/en-US/lang.dtd	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/web/opac/locale/en-US/lang.dtd	2009-12-30 15:43:32 UTC (rev 15247)
@@ -765,6 +765,8 @@
 <!ENTITY staff.main.menu.booking.accesskey "B">
 <!ENTITY staff.main.menu.booking.reservation.label "Create or Edit Reservations">
 <!ENTITY staff.main.menu.booking.reservation.accesskey "C">
+<!ENTITY staff.main.menu.booking.pull_list.label "Pull List">
+<!ENTITY staff.main.menu.booking.pull_list.accesskey "L">
 <!ENTITY staff.main.menu.booking.reservation_pickup.label "Pick Up Reservations">
 <!ENTITY staff.main.menu.booking.reservation_pickup.accesskey "P">
 <!ENTITY staff.main.menu.booking.reservation_return.label "Return Reservations">

Added: trunk/Open-ILS/web/templates/default/booking/pull_list.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/booking/pull_list.tt2	                        (rev 0)
+++ trunk/Open-ILS/web/templates/default/booking/pull_list.tt2	2009-12-30 15:43:32 UTC (rev 15247)
@@ -0,0 +1,49 @@
+[% WRAPPER "default/base.tt2" %]
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/common.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/pull_list.js"></script>
+<link rel="stylesheet" type="text/css" href="[% ctx.media_prefix %]/css/skin/[% ctx.skin %]/booking.css" />
+<script type="text/javascript">openils.Util.addOnLoad(my_init);</script>
+<div id="auto_l10n_start_here">
+    <h1 class="booking AUTO_pull_list_title"></h1>
+    <form onsubmit="populate_pull_list(this); return false;">
+        <div id="pickup_lib_selector_row" class="nice_vertical_padding">
+            <label for="pickup_lib_selector" class="AUTO_pickup_lib_selector">
+            </label>
+            <select dojoType="openils.widget.OrgUnitFilteringSelect"
+                id="pickup_lib_selector" jsId="pickup_lib_selector"
+                searchAttr="shortname" labelAttr="shortname"></select>
+        </div>
+        <div id="interval_input_row" class="nice_vertical_padding">
+            <label for="interval_in_days" class="AUTO_interval_in_days"></label>
+
+            <!-- XXX Hardcoded values (like the ones below) are bad. -->
+            <input id="interval_in_days" name="interval_in_days"
+                value="5" maxlength="2" />
+
+        </div>
+        <input type="submit" class="AUTO_ATTR_VALUE_fetch" />
+    </form>
+    <hr />
+    <div id="table_goes_here" class="nice_vertical_padding">
+        <table id="the_table" width="90%">
+            <thead>
+                <tr>
+                    <th width="30%" class="AUTO_th_title_or_name"></th>
+                    <th width="10%" class="AUTO_th_barcode"></th>
+                    <th width="10%" class="AUTO_th_call_number"></th>
+                    <th width="10%" class="AUTO_th_copy_location"></th>
+                    <th width="10%" class="AUTO_th_copy_number"></th>
+                    <th width="30%" class="AUTO_th_resv_details"></th>
+                </tr>
+            </thead>
+            <tbody id="the_table_body">
+            </tbody>
+        </table>
+    </div>
+    <div id="print_holder" class="nice_vertical_padding">
+        <!-- XXX Print button probably won't stay right here -->
+        <input type="button" class="AUTO_ATTR_VALUE_print"
+            onclick="window.print();" /><!-- XXX too simplistic? -->
+    </div>
+</div>
+[% END %]

Modified: trunk/Open-ILS/web/templates/default/booking/reservation.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/booking/reservation.tt2	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/web/templates/default/booking/reservation.tt2	2009-12-30 15:43:32 UTC (rev 15247)
@@ -1,4 +1,5 @@
 [% WRAPPER "default/base.tt2" %]
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/common.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/booking/reservation.js"></script>
 <link rel="stylesheet" type="text/css" href="[% ctx.media_prefix %]/css/skin/[% ctx.skin %]/booking.css" />
 <script type="text/javascript">
@@ -8,26 +9,31 @@
 <div id="auto_l10n_start_here">
     <div id="brt_search_block" class="container">
         <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 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> -->
+        <form onsubmit="return false;">
+            <div id="select_noncat_brt_block">
+                <div id="brt_selector_here" class="nice_vertical_padding"></div>
                 <div class="nice_vertical_padding">
-                    <input type="submit"
-                        class="AUTO_ATTR_VALUE_next" />
+                    <input type="button" class="AUTO_ATTR_VALUE_next"
+                        onclick="init_resv_iface_sel(); return false"
+                        />
                 </div>
+                <hr />
+                <div class="nice_vertical_padding AUTO_or" id="or"></div>
             </div>
+            <div id="arbitrary_resource_block">
+                <label for="arbitrary_resource" class="AUTO_arbitrary_resource">
+                </label>
+                <input id="arbitrary_resource" name="arbitrary_resource" />
+                <input type="button"
+                    onclick="init_resv_iface_arb(); return false;"
+                    class="AUTO_ATTR_VALUE_next" />
+                <p class="AUTO_explain_bookable"></p>
+            </div>
         </form>
-        <div id="preselected_patron"></div>
     </div>
 
     <div id="brt_reserve_block" class="container">
-        <form>
+        <form onsubmit="return false;">
             <div id="brsrc_available_outer">
                 <h1 class="booking" id="brsrc_list_header"></h1>
                 <!-- I'm reluctantly hardcoding the size attribute below to 12
@@ -35,7 +41,7 @@
                     anything in CSS. -->
                 <select id="brsrc_list" name="brsrc_list" multiple="multiple"
                     size="12"></select>
-                <div id="holds_patron_barcode" class="nice_vertical_padding">
+                <div id="contain_patron_barcode" class="nice_vertical_padding">
                     <label class="AUTO_patron_barcode"
                         for="patron_barcode" /></label>
                     <input name="patron_barcode" id="patron_barcode"
@@ -71,34 +77,35 @@
                     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">
-                    <thead>
-                        <tr><!-- FIXME: i18n problem: init_auto_l10n() runs
-                                too late to take care of the below elements. -->
-                            <th field="type">Type</th>
-                            <th field="resource">Resource</th>
-                            <th field="start_time">Start time</th>
-                            <th field="end_time">End time</th>
-                        </tr>
-                    </thead>
-                </table>
-                <div class="nice_vertical_padding"
-                    id="existing_bresv_under_buttons">
-                    <input type="button" id="button_edit_existing"
-                        class="AUTO_ATTR_VALUE_button_edit_existing"
-                        disabled="disabled" />
-                    <input type="button" id="button_cancel_existing"
-                        class="AUTO_ATTR_VALUE_button_cancel_existing"
-                        onclick="cancel_selected_bresv(bresvGrid.selection.getSelected());" />
-                </div>
-            </div>
         </form>
     </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" width="auto">
+            <thead>
+                <tr><!-- FIXME: i18n problem: init_auto_l10n() runs
+                        too late to take care of the below elements. -->
+                    <th width="35%" field="type">Type</th>
+                    <th width="25%" field="resource">Resource</th>
+                    <th width="20%" field="start_time">Start time</th>
+                    <th width="20%" field="end_time">End time</th>
+                </tr>
+            </thead>
+        </table>
+        <div class="nice_vertical_padding"
+            id="existing_bresv_under_buttons">
+            <input type="button" id="button_edit_existing"
+                class="AUTO_ATTR_VALUE_button_edit_existing"
+                disabled="disabled" />
+            <input type="button" id="button_cancel_existing"
+                class="AUTO_ATTR_VALUE_button_cancel_existing"
+                onclick="cancel_selected_bresv(bresvGrid.selection.getSelected());" />
+        </div>
+    </div>
 </div>
 [% END %]

Modified: trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js
===================================================================
--- trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js	2009-12-30 15:43:32 UTC (rev 15247)
@@ -704,6 +704,21 @@
                     );
                 }
             ],
+            'cmd_booking_pull_list' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        "/eg/booking/pull_list",
+                        {
+                            "tab_name": offlineStrings.getString(
+                                "menu.cmd_booking_pull_list.tab"
+                            ),
+                            "browser": false
+                        },
+                        xulG
+                    );
+                }
+            ],
             'cmd_booking_reservation_pickup' : [
                 ['oncommand'],
                 function() {
@@ -711,7 +726,7 @@
                         "/eg/booking/reservation_pickup",
                         {
                             "tab_name": offlineStrings.getString(
-                                "menu.cmd_booking_reservation.tab"
+                                "menu.cmd_booking_reservation_pickup.tab"
                             ),
                             "browser": false
                         },
@@ -726,7 +741,7 @@
                         "/eg/booking/reservation_return",
                         {
                             "tab_name": offlineStrings.getString(
-                                "menu.cmd_booking_reservation.tab"
+                                "menu.cmd_booking_reservation_return.tab"
                             ),
                             "browser": false
                         },

Modified: trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul	2009-12-30 15:43:32 UTC (rev 15247)
@@ -89,6 +89,7 @@
     <command id="cmd_acq_view_distrib_formula" />
 
     <command id="cmd_booking_reservation" />
+    <command id="cmd_booking_pull_list" />
     <command id="cmd_booking_reservation_pickup" />
     <command id="cmd_booking_reservation_return" />
 
@@ -264,6 +265,7 @@
 <menu id="main.menu.booking" label="&staff.main.menu.booking.label;" accesskey="&staff.main.menu.booking.accesskey;">
     <menupopup id="main.menu.booking.popup">
         <menuitem label="&staff.main.menu.booking.reservation.label;" accesskey="&staff.main.menu.booking.reservation.accesskey;" command="cmd_booking_reservation"/>
+        <menuitem label="&staff.main.menu.booking.pull_list.label;" accesskey="&staff.main.menu.booking.pull_list.accesskey;" command="cmd_booking_pull_list"/>
         <!-- <menuitem label="&staff.main.menu.booking.reservation_pickup.label;" accesskey="&staff.main.menu.booking.reservation_pickup.accesskey;" command="cmd_booking_reservation_pickup"/>
         <menuitem label="&staff.main.menu.booking.reservation_return.label;" accesskey="&staff.main.menu.booking.reservation_return.accesskey;" command="cmd_booking_reservation_return"/> -->
     </menupopup>

Modified: trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
===================================================================
--- trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties	2009-12-30 15:43:32 UTC (rev 15247)
@@ -234,6 +234,9 @@
 menu.cmd_acq_view_distrib_formula.tab=Distribution Formulas
 menu.cmd_booking_resource.tab=Resources
 menu.cmd_booking_reservation.tab=Reservations
+menu.cmd_booking_reservation_pickup.tab=Reservation Pickup
+menu.cmd_booking_reservation_return.tab=Reservation Return
+menu.cmd_booking_pull_list.tab=Booking Pull List
 menu.local_admin.circ_matrix_matchpoint.tab=Circulation Policies
 menu.local_admin.hold_matrix_matchpoint.tab=Hold Policies
 menu.local_admin.work_log.tab=Work Log

Modified: trunk/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul	2009-12-30 15:43:32 UTC (rev 15247)
@@ -86,19 +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"/>
                                     <menuitem label="&staff.patron.info.notes.label;" accesskey="&staff.patron.info.notes.accesskey;" command="cmd_patron_info_notes"/>
                                     <menuitem label="&staff.patron.info.triggered_events.label;" accesskey="&staff.patron.info.triggered_events.accesskey;" command="cmd_patron_info_triggered_events"/>
                                     <menuitem label="&staff.patron.info.stat_cats.label;" accesskey="&staff.patron.info.stat_cats.accesskey;" command="cmd_patron_info_stats"/>
+                                    <menuitem label="&staff.main.menu.booking.reservation.label;" accesskey="&staff.main.menu.booking.reservation.accesskey;" command="cmd_patron_reservation" />
                                     <menuitem label="&staff.patron.info.surveys.label;" accesskey="&staff.patron.info.surveys.accesskey;" command="cmd_patron_info_surveys"/>
                                     <menuitem label="&staff.patron.info.group.label;" accesskey="&staff.patron.info.group.accesskey;" command="cmd_patron_info_groups"/>
                                     <menuitem label="&staff.patron_display.verify_password.label;" accesskey="&staff.patron_display.verify_password.accesskey;" command="cmd_verify_credentials"/>

Modified: trunk/Open-ILS/xul/staff_client/server/patron/display_overlay.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/patron/display_overlay.xul	2009-12-30 15:24:33 UTC (rev 15246)
+++ trunk/Open-ILS/xul/staff_client/server/patron/display_overlay.xul	2009-12-30 15:43:32 UTC (rev 15247)
@@ -86,19 +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"/>
                                     <menuitem label="&staff.patron.info.notes.label;" accesskey="&staff.patron.info.notes.accesskey;" command="cmd_patron_info_notes"/>
                                     <menuitem label="&staff.patron.info.triggered_events.label;" accesskey="&staff.patron.info.triggered_events.accesskey;" command="cmd_patron_info_triggered_events"/>
                                     <menuitem label="&staff.patron.info.stat_cats.label;" accesskey="&staff.patron.info.stat_cats.accesskey;" command="cmd_patron_info_stats"/>
+                                    <menuitem label="&staff.main.menu.booking.reservation.label;" accesskey="&staff.main.menu.booking.reservation.accesskey;" command="cmd_patron_reservation" />
                                     <menuitem label="&staff.patron.info.surveys.label;" accesskey="&staff.patron.info.surveys.accesskey;" command="cmd_patron_info_surveys"/>
                                     <menuitem label="&staff.patron.info.group.label;" accesskey="&staff.patron.info.group.accesskey;" command="cmd_patron_info_groups"/>
                                     <menuitem label="&staff.patron_display.verify_password.label;" accesskey="&staff.patron_display.verify_password.accesskey;" command="cmd_verify_credentials"/>



More information about the open-ils-commits mailing list