[open-ils-commits] r15207 - in trunk/Open-ILS: examples src/perlmods/OpenILS/Application src/perlmods/OpenILS/Application/Storage/CDBI src/perlmods/OpenILS/Application/Storage/Publisher src/perlmods/OpenILS/Utils web/css/skin/default web/js/dojo/openils web/js/dojo/openils/booking web/js/dojo/openils/booking/nls web/js/ui/default web/js/ui/default/booking web/opac/locale/en-US web/templates/default web/templates/default/booking web/templates/default/conify/global/booking xul/staff_client/chrome/content/main xul/staff_client/chrome/locale/en-US xul/staff_client/server/cat xul/staff_client/server/locale/en-US (miker)

svn at svn.open-ils.org svn at svn.open-ils.org
Mon Dec 21 11:21:50 EST 2009


Author: miker
Date: 2009-12-21 11:21:48 -0500 (Mon, 21 Dec 2009)
New Revision: 15207

Added:
   trunk/Open-ILS/web/css/skin/default/booking.css
   trunk/Open-ILS/web/js/dojo/openils/booking/
   trunk/Open-ILS/web/js/dojo/openils/booking/nls/
   trunk/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js
   trunk/Open-ILS/web/js/ui/default/booking/
   trunk/Open-ILS/web/js/ui/default/booking/reservation.js
   trunk/Open-ILS/web/templates/default/booking/
   trunk/Open-ILS/web/templates/default/booking/reservation.tt2
Removed:
   trunk/Open-ILS/web/templates/default/conify/global/booking/reservation.tt2
   trunk/Open-ILS/web/templates/default/conify/global/booking/reservation_attr_value_map.tt2
Modified:
   trunk/Open-ILS/examples/fm_IDL.xml
   trunk/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm
   trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm
   trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm
   trunk/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm
   trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
   trunk/Open-ILS/web/templates/default/conify/global/booking/resource.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/content/main/menu_frame_overlay.xul
   trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
   trunk/Open-ILS/xul/staff_client/server/cat/copy_browser.js
   trunk/Open-ILS/xul/staff_client/server/cat/copy_browser.xul
   trunk/Open-ILS/xul/staff_client/server/cat/util.js
   trunk/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
Log:
Patch from Lebbeous Fogle-Weekley to add booking reservation interfaces, supporting backend changes and IDL cleanup.  It's ... big.



Modified: trunk/Open-ILS/examples/fm_IDL.xml
===================================================================
--- trunk/Open-ILS/examples/fm_IDL.xml	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/examples/fm_IDL.xml	2009-12-21 16:21:48 UTC (rev 15207)
@@ -2536,8 +2536,8 @@
 			<link field="payment_total" reltype="might_have" key="xact" map="" class="rxpt"/>
 			<link field="summary" reltype="might_have" key="id" map="" class="mbts"/>
 			<link field="target_resource_type" reltype="has_a" key="id" map="" class="brt"/>
-			<link field="target_resource" reltype="might_have" key="id" map="" class="brsrc"/>
-			<link field="current_resource" reltype="might_have" key="id" map="" class="brsrc"/>
+			<link field="target_resource" reltype="has_a" key="id" map="" class="brsrc"/>
+			<link field="current_resource" reltype="has_a" key="id" map="" class="brsrc"/>
 			<link field="request_lib" reltype="has_a" key="id" map="" class="aou"/>
 			<link field="pickup_lib" reltype="might_have" key="id" map="" class="aou"/>
 			<link field="capture_staff" reltype="might_have" key="id" map="" class="au"/>

Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm	2009-12-21 16:21:48 UTC (rev 15207)
@@ -12,8 +12,6 @@
 my $U = "OpenILS::Application::AppUtils";
 
 use OpenSRF::Utils::Logger qw/$logger/;
-use DateTime;
-use DateTime::Format::ISO8601;
 
 sub prepare_new_brt {
     my ($record_id, $owning_lib, $mvr) = @_;
@@ -34,7 +32,7 @@
         {name => $mvr->title, owner => $owning_lib, record => $record_id}
     );
 
-    return $results->[0] if (length @$results > 0);
+    return $results->[0] if scalar(@$results) > 0;
     return undef;
 }
 
@@ -72,21 +70,26 @@
     return $record_id;
 }
 
-__PACKAGE__->register_method(
-    method   => "create_brt_and_brsrc",
-    api_name => "open-ils.booking.create_brt_and_brsrc_from_copies",
-    signature => {
-        params => [
-            {type => 'string', desc => 'Authentication token'},
-            {type => 'array', desc => 'Copy IDs'},
-        ],
-        return => { desc => "A two-element hash. The 'brt' element " .
-            "is a list of created booking resource types described by " .
-            "id/copyid pairs.  The 'brsrc' element is a similar " .
-            "list of created booking resources described by copy/recordid " .
-            "pairs"}
-    }
-);
+# This function generates the correct json_query clause for determining
+# whether two given ranges overlap.  Each range is composed of a start
+# and an end point.  All four points should be the same type (could be int,
+# date, time, timestamp, or perhaps other types).
+#
+# The first range (or the first two points) should be specified as
+# literal values.  The second range (or the last two points) should be
+# specified as the names of columns, the values of which in a given row
+# will constitute the second range in the comparison.
+#
+# ALSO: PostgreSQL includes an OVERLAPS operator which provides the same
+# functionality in a much more concise way, but json_query does not (yet).
+sub json_query_ranges_overlap {
+    +{ '-or' => [
+        { '-and' => [{$_[2] => {'>=', $_[0]}}, {$_[2] => {'<',  $_[1]}}]},
+        { '-and' => [{$_[3] => {'>',  $_[0]}}, {$_[3] => {'<',  $_[1]}}]},
+        { '-and' => { $_[3] => {'>',  $_[0]},   $_[2] => {'<=', $_[0]}}},
+        { '-and' => { $_[3] => {'>',  $_[1]},   $_[2] => {'<',  $_[1]}}},
+    ]};
+}
 
 sub create_brt_and_brsrc {
     my ($self, $conn, $authtoken, $copy_ids) = @_;
@@ -106,39 +109,59 @@
     }
 
     while (my ($owning_lib, $brt) = each %brt_table) {
+        my $pre_existing = 1;
         if ($brt->isnew) {
-            if ($e->allowed('CREATE_BOOKING_RESOURCE_TYPE', $owning_lib)) {
-                # We can/should abort if this creation fails, because the
-                # logic isn't going to be trying to create any redundnat
-                # brt's, therefore any error will be more serious than
-                # that.  See the different take on creating brsrc's below.
+            if ($e->allowed('ADMIN_BOOKING_RESOURCE_TYPE', $owning_lib)) {
+                $pre_existing = 0;
                 return $e->die_event unless (
                     #    v-- Important: assignment modifies original hash
                     $brt = $e->create_booking_resource_type($brt)
                 );
             }
-            push @created_brt, [$brt->id, $brt->record];
         }
+        push @created_brt, [$brt->id, $brt->record, $pre_existing];
     }
 
     foreach (@copies) {
-        if (
-            $e->allowed('CREATE_BOOKING_RESOURCE', $_->call_number->owning_lib)
-        ) {
+        if ($e->allowed(
+            'ADMIN_BOOKING_RESOURCE', $_->call_number->owning_lib
+        )) {
+            # This block needs to disregard any cstore failures and just
+            # return what results it can.
             my $brsrc = new Fieldmapper::booking::resource;
             $brsrc->isnew(1);
             $brsrc->type($brt_table{$_->call_number->owning_lib}->id);
             $brsrc->owner($_->call_number->owning_lib);
             $brsrc->barcode($_->barcode);
 
-            # We don't want to abort the transaction or do anything dramatic if
-            # this fails, because quite possibly a user selected many copies on
-            # which to perform this "create booking resource" operation, and
-            # among those copies there may be some that we still need to
-            # create, and some that we don't.  So we just do what we can.
-            push @created_brsrc, [$brsrc->id, $_->id] if
-                ($brsrc = $e->create_booking_resource($brsrc));
-            #           ^--- Important: assignment.
+            $e->set_savepoint("alpha");
+            my $pre_existing = 0;
+            my $usable_result = undef;
+            if (!($usable_result = $e->create_booking_resource($brsrc))) {
+                $e->rollback_savepoint("alpha");
+                if (($usable_result = $e->search_booking_resource(
+                    +{ map { ($_, $brsrc->$_()) } qw/type owner barcode/ }
+                ))) {
+                    $usable_result = $usable_result->[0];
+                    $pre_existing = 1;
+                } else {
+                    # So we failed to create a booking resource for this copy.
+                    # For now, let's just keep going.  If the calling app wants
+                    # to consider this an error, it can notice the absence
+                    # of a booking resource for the copy in the returned
+                    # results.
+                    $logger->warn(
+                        "Couldn't create or find brsrc for acp #" .  $_->id
+                    );
+                }
+            } else {
+                $e->release_savepoint("alpha");
+            }
+
+            if ($usable_result) {
+                push @created_brsrc,
+                    [$usable_result->id, $_->id, $pre_existing];
+            }
         }
     }
 
@@ -146,18 +169,120 @@
         return {brt => \@created_brt, brsrc => \@created_brsrc} or
         return $e->die_event;
 }
+__PACKAGE__->register_method(
+    method   => "create_brt_and_brsrc",
+    api_name => "open-ils.booking.resources.create_from_copies",
+    signature => {
+        params => [
+            {type => 'string', desc => 'Authentication token'},
+            {type => 'array', desc => 'Copy IDs'},
+        ],
+        return => { desc => "A two-element hash. The 'brt' element " .
+            "is a list of created booking resource types described by " .
+            "3-tuples (id, copy id, was pre-existing).  The 'brsrc' " .
+            "element is a similar list of created booking resources " .
+            "described by (id, record id, was pre-existing) 3-tuples."}
+    }
+);
 
+
+sub create_bresv {
+    my ($self, $client, $authtoken,
+        $target_user_barcode, $datetime_range,
+        $brt, $brsrc_list, $attr_values) = @_;
+
+    $brsrc_list = [ undef ] if not defined $brsrc_list;
+    return undef if scalar(@$brsrc_list) < 1; # Empty list not ok.
+
+    my $e = new_editor(xact => 1, authtoken => $authtoken);
+    return $e->die_event unless (
+        $e->checkauth and
+        $e->allowed("ADMIN_BOOKING_RESERVATION") and
+        $e->allowed("ADMIN_BOOKING_RESERVATION_ATTR_MAP")
+    );
+
+    my $usr = $U->fetch_user_by_barcode($target_user_barcode);
+    return $usr if ref($usr) eq 'HASH' and exists($usr->{"ilsevent"});
+
+    my $results = [];
+    foreach my $brsrc (@$brsrc_list) {
+        my $bresv = new Fieldmapper::booking::reservation;
+        $bresv->usr($usr->id);
+        $bresv->request_lib($e->requestor->ws_ou);
+        $bresv->pickup_lib($e->requestor->ws_ou);
+        $bresv->start_time($datetime_range->[0]);
+        $bresv->end_time($datetime_range->[1]);
+
+        # A little sanity checking: don't agree to put a reservation on a
+        # brsrc and a brt when they don't match.  In fact, bomb out of
+        # this transaction entirely.
+        if ($brsrc) {
+            my $brsrc_itself = $e->retrieve_booking_resource($brsrc) or
+                return $e->die_event;
+            return $e->die_event if ($brsrc_itself->type != $brt);
+        }
+        $bresv->target_resource($brsrc);    # undef is ok here
+        $bresv->target_resource_type($brt);
+
+        ($bresv = $e->create_booking_reservation($bresv)) or
+            return $e->die_event;
+
+        # We could/should do some sanity checking on this too: namely, on
+        # whether the attribute values given actually apply to the relevant
+        # brt.  Not seeing any grievous side effects of not checking, though.
+        my @bravm = ();
+        foreach my $value (@$attr_values) {
+            my $bravm = new Fieldmapper::booking::reservation_attr_value_map;
+            $bravm->reservation($bresv->id);
+            $bravm->attr_value($value);
+            $bravm = $e->create_booking_reservation_attr_value_map($bravm) or
+                return $e->die_event;
+            push @bravm, $bravm;
+        }
+        push @$results, {
+            "bresv" => $bresv->id,
+            "bravm" => \@bravm,
+        };
+    }
+
+    $e->commit or return $e->die_event;
+
+    # Targeting must be tacked on _after_ committing the transaction where the
+    # reservations are actually created.
+    foreach (@$results) {
+        $_->{"targeting"} = $U->storagereq(
+            "open-ils.storage.booking.reservation.resource_targeter",
+            $_->{"bresv"}
+        )->[0];
+    }
+    return $results;
+}
+__PACKAGE__->register_method(
+    method   => "create_bresv",
+    api_name => "open-ils.booking.reservations.create",
+    signature => {
+        params => [
+            {type => 'string', desc => 'Authentication token'},
+            {type => 'string', desc => 'Barcode of user for whom to reserve'},
+            {type => 'array', desc => 'Two elements: start and end timestamp'},
+            {type => 'int', desc => 'Booking resource type'},
+            {type => 'list', desc => 'Booking resource (undef ok; empty not ok)'},
+            {type => 'array', desc => 'Attribute values selected'},
+        ],
+        return => { desc => "A hash containing the new bresv and a list " .
+            "of new bravm"}
+    }
+);
+
+
 sub resource_list_by_attrs {
     my $self = shift;
     my $client = shift;
-    my $auth = shift;
+    my $auth = shift; # Keep as argument, though not used just now.
     my $filters = shift;
 
     return undef unless ($filters->{type} || $filters->{attribute_values});
 
-    my $e = new_editor(authtoken=>$auth);
-    return $e->event unless $e->checkauth;
-
     my $query = {
         'select'   => { brsrc => [ 'id' ] },
         'from'     => { brsrc => {} },
@@ -165,8 +290,9 @@
         'distinct' => 1
     };
 
+    $query->{where} = {"-and" => []};
     if ($filters->{type}) {
-        $query->{where}->{type} = $filters->{type};
+        push @{$query->{where}->{"-and"}}, {"type" => $filters->{type}};
     }
 
     if ($filters->{attribute_values}) {
@@ -178,95 +304,82 @@
 
         $query->{having}->{'+bram'}->{value}->{'@>'} = {
             transform => 'array_accum',
-            value => '$'.$$.'${'.join(',', @{ $filters->{attribute_values} } ).'}$'.$$.'$'
+            value => '$_' . $$ . '${' .
+                join(',', @{$filters->{attribute_values}}) .
+                '}$_' . $$ . '$'
         };
     }
 
     if ($filters->{available}) {
-        $query->{from}->{brsrc}->{bresv} = { field => 'current_resource' };
+        # If only one timestamp has been provided, make it into a range.
+        if (!ref($filters->{available})) {
+            $filters->{available} = [($filters->{available}) x 2];
+        }
 
-        if (!ref($filters->{available})) { # just one time, start perhaps
-            $query->{where}->{'+bresv'} = {
-                '-or' => [
-                    { '+brsrc' => {'overbook' => 't'} },
-                    { '-or' =>
-                        {   start_time => { '>=' => $filters->{available} },
-                            end_time   => { '<=' => $filters->{available} },
-                        }
-                    }
-                ]
-            };
-        } else { # start and end times
-            $query->{where}->{'+bresv'} = {
-                '-or' => [
-                    { '+brsrc' => {'overbook' => 't'} },
-                    { '-and' =>
-                        [{ '-or' =>
-                            {   start_time => { '>=' => $filters->{available}->[0] },
-                                end_time   => { '<=' => $filters->{available}->[0] },
-                            }
-                        },{'-or' =>
-                            {   start_time => { '>=' => $filters->{available}->[1] },
-                                end_time   => { '<=' => $filters->{available}->[1] },
-                            }
-                        }]
-                    }
-                ]
-            };
-        }
+        push @{$query->{where}->{"-and"}}, {
+            "-or" => [
+                {"overbook" => "t"},
+                {"-not-exists" => {
+                    "select" => {"bresv" => ["id"]},
+                    "from" => "bresv",
+                    "where" => {"-and" => [
+                        json_query_ranges_overlap(
+                            $filters->{available}->[0],
+                            $filters->{available}->[1],
+                            "start_time",
+                            "end_time"
+                        ),
+                        {"cancel_time" => undef},
+                        {"current_resource" => {"=" => {"+brsrc" => "id"}}}
+                    ]},
+                }}
+            ]
+        };
     }
-
     if ($filters->{booked}) {
-        $query->{from}->{brsrc}->{bresv} = { field => 'current_resource' };
+        # If only one timestamp has been provided, make it into a range.
+        if (!ref($filters->{booked})) {
+            $filters->{booked} = [($filters->{booked}) x 2];
+        }
 
-        if (!ref($filters->{booked})) { # just one time, start perhaps
-            $query->{where}->{'+bresv'} = {
-                start_time => { '<=' => $filters->{booked} },
-                end_time   => { '>=' => $filters->{booked} },
-            };
-        } else { # start and end times
-            $query->{where}->{'+bresv'} = {
-                '-or' => {
-                    '-and' => {
-                        start_time => { '<=' => $filters->{booked}->[0] },
-                        end_time   => { '>=' => $filters->{booked}->[0] },
-                    },
-                    '-and' => {
-                        start_time => { '<=' => $filters->{booked}->[1] },
-                        end_time   => { '>=' => $filters->{booked}->[1] },
-                    }
-                }
-            };
-        }
+        push @{$query->{where}->{"-and"}}, {
+            "-exists" => {
+                "select" => {"bresv" => ["id"]},
+                "from" => "bresv",
+                "where" => {"-and" => [
+                    json_query_ranges_overlap(
+                        $filters->{booked}->[0],
+                        $filters->{booked}->[1],
+                        "start_time",
+                        "end_time"
+                    ),
+                    {"cancel_time" => undef},
+                    {"current_resource" => { "=" => {"+brsrc" => "id"}}}
+                ]},
+            }
+        };
+        # I think that the "booked" case could be done with a JOIN instead of
+        # an EXISTS, but I'm leaving it this way for symmetry with the
+        # "available" case for now.  The available case cannot be done with a
+        # join.
     }
 
     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
-    my $ids = $cstore->request( 'open-ils.cstore.json_query.atomic', $query )->gather(1);
+    my $rows = $cstore->request( 'open-ils.cstore.json_query.atomic', $query )->gather(1);
     $cstore->disconnect;
 
-    if (@$ids) {
-        $ids = [ map { $_->{id} } @$ids ];
-
-        my $pcrud = OpenSRF::AppSession->connect('open-ils.pcrud');
-        my $allowed_ids = $pcrud->request(
-            'open-ils.pcrud.id_list.brsrc.atomic',
-            $auth => { id => $ids }
-        )->gather(1);
-        $pcrud->disconnect;
-
-        return $allowed_ids;
-    } else {
-        return $ids; # empty []
-    }
+    return @$rows ? [map { $_->{id} } @$rows] : [];
 }
 __PACKAGE__->register_method(
     method   => "resource_list_by_attrs",
     api_name => "open-ils.booking.resources.filtered_id_list",
-    argc     => 2,
+    argc     => 3,
     signature=> {
         params => [
-            {type => 'string', desc => 'Authentication token'},
-            {type => 'object', desc => 'Filter object -- see notes for details'}
+            {type => 'string', desc => 'Authentication token (unused for now,' .
+               ' but at least pass undef here)'},
+            {type => 'object', desc => 'Filter object: see notes for details'},
+            {type => 'bool', desc => 'Return whole objects instead of IDs?'}
         ],
         return => { desc => "An array of brsrc ids matching the requested filters." },
     },
@@ -285,23 +398,24 @@
 Note that at least one of 'type' or 'attribute_values' is required.
 
 NOTES
-
 );
 
+
 sub reservation_list_by_filters {
     my $self = shift;
     my $client = shift;
     my $auth = shift;
     my $filters = shift;
+    my $whole_obj = shift;
 
-    return undef unless ($filters->{user} || $filters->{resource} || $filters->{type} || $filters->{attribute_values});
+    return undef unless ($filters->{user} || $filters->{user_barcode} || $filters->{resource} || $filters->{type} || $filters->{attribute_values});
 
     my $e = new_editor(authtoken=>$auth);
     return $e->event unless $e->checkauth;
     return $e->event unless $e->allowed('VIEW_TRANSACTION');
 
     my $query = {
-        'select'   => { bresv => [ 'id' ] },
+        'select'   => { bresv => [ 'id', 'start_time' ] },
         'from'     => { bresv => {} },
         'where'    => {},
         'order_by' => [{ class => bresv => field => start_time => direction => 'asc' }],
@@ -316,7 +430,13 @@
     if ($filters->{user}) {
         $query->{where}->{usr} = $filters->{user};
     }
+    elsif ($filters->{user_barcode}) {  # just one of user and user_barcode
+        my $usr = $U->fetch_user_by_barcode($filters->{user_barcode});
+        return $usr if ref($usr) eq 'HASH' and exists($usr->{"ilsevent"});
+        $query->{where}->{usr} = $usr->id;
+    }
 
+
     if ($filters->{type}) {
         $query->{where}->{target_resource_type} = $filters->{type};
     }
@@ -334,12 +454,13 @@
 
         $query->{having}->{'+bravm'}->{attr_value}->{'@>'} = {
             transform => 'array_accum',
-            value => '$'.$$.'${'.join(',', @{ $filters->{attribute_values} } ).'}$'.$$.'$'
+            value => '$_' . $$ . '${' .
+                join(',', @{$filters->{attribute_values}}) .
+                '}$_' . $$ . '$'
         };
     }
 
     if ($filters->{search_start} || $filters->{search_end}) {
-        
         $query->{where}->{'-or'} = {};
 
         $query->{where}->{'-or'}->{start_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] }
@@ -350,11 +471,25 @@
     }
 
     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
-    my $ids = $cstore->request( 'open-ils.cstore.json_query.atomic', $query )->gather(1);
-    $ids = [ map { $_->{id} } @$ids ];
+    my $ids = [ map { $_->{id} } @{
+        $cstore->request(
+            'open-ils.cstore.json_query.atomic', $query
+        )->gather(1)
+    } ];
     $cstore->disconnect;
 
-    return $ids;
+    return $ids if not $whole_obj;
+
+    my $bresv_list = $e->search_booking_reservation([
+        {"id" => $ids},
+        {"flesh" => 1,
+            "flesh_fields" => {
+                "bresv" =>
+                    [qw/target_resource current_resource target_resource_type/]
+            }
+        }]
+    );
+    return $bresv_list ? $bresv_list : [];
 }
 __PACKAGE__->register_method(
     method   => "reservation_list_by_filters",
@@ -365,12 +500,13 @@
             {type => 'string', desc => 'Authentication token'},
             {type => 'object', desc => 'Filter object -- see notes for details'}
         ],
-        return => { desc => "An array of brsrc ids matching the requested filters." },
+        return => { desc => "An array of bresv ids matching the requested filters." },
     },
     notes    => <<'NOTES'
 
 The filter object parameter can contain the following keys:
  * user             => The id of a user that has requested a bookable item -- filters on bresv.usr
+ * barcode          => The barcode of a user that has requested a bookable item
  * type             => The id of a booking resource type (brt) -- filters on bresv.target_resource_type
  * resource         => The id of a booking resource (brsrc) -- filters on bresv.target_resource
  * attribute_values => The ids of booking resource type attribute values that the resource must have assigned to it (brav)
@@ -383,8 +519,6 @@
 by the top-level filters ('user', 'type', 'resource').
 
 NOTES
-
 );
 
-
 1;

Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm	2009-12-21 16:21:48 UTC (rev 15207)
@@ -28,7 +28,7 @@
 use base qw/booking/;
 __PACKAGE__->table('booking_reservation');
 __PACKAGE__->columns(Primary => 'id');
-__PACKAGE__->columns(Essential => qw/xact_start usr current_copy circ_lib
+__PACKAGE__->columns(Essential => qw/xact_start usr current_resource
 				     fine_amount max_fine fine_interval xact_finish 
 				     capture_staff pickup_lib request_time start_time end_time
                      capture_time cancel_time pickup_time return_time

Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm	2009-12-21 16:21:48 UTC (rev 15207)
@@ -1332,7 +1332,7 @@
 	try {
 		if ($one_reservation) {
 			$self->method_lookup('open-ils.storage.transaction.begin')->run( $client );
-			$reservations = booking::reservation->search_where( { id => $one_reservation, capture_time => undef, cancel_time => undef } );
+			$reservations = [ booking::reservation->search_where( { id => $one_reservation, capture_time => undef, cancel_time => undef } ) ];
 		} else {
 
 			# find all the reservations needing targeting
@@ -1351,6 +1351,7 @@
 		die "Could not retrieve reservation requests:\n\n$e\n";
 	};
 
+	my @successes = ();
 	for my $bresv (@$reservations) {
 		try {
 			#start a transaction if needed
@@ -1383,27 +1384,27 @@
 				die "OK\n";
 			}
 
-            my $possible_resources;
+			my $possible_resources;
 
 			# find all the potential resources
 			if (!$bresv->target_resource) {
-                my $filter = { type => $bresv->target_resource_type };
-                my $attr_maps = booking::reservations_attr_value_map->search( reservation => $bresv->id );
+				my $filter = { type => $bresv->target_resource_type };
+				my $attr_maps = [ booking::reservation_attr_value_map->search( reservation => $bresv->id) ];
 
-                $filter->{attribute_values} = [ map { $_->attr_value } @$attr_maps ] if (@$attr_maps);
+				$filter->{attribute_values} = [ map { $_->attr_value } @$attr_maps ] if (@$attr_maps);
 
-   				my $ses = OpenSRF::AppSession->create('open-ils.booking');
-                $possible_resources = $ses->request('open-ils.booking.resources.filtered_id_list', $filter)->gather(1);
-
+				$filter->{available} = [$bresv->start_time, $bresv->end_time];
+				my $ses = OpenSRF::AppSession->create('open-ils.booking');
+				$possible_resources = $ses->request('open-ils.booking.resources.filtered_id_list', undef, $filter)->gather(1);
 			} else {
 				$possible_resources = $bresv->target_resource;
 			}
 
-            my $all_resources = booking::resource->search( id => $possible_resources );
+            my $all_resources = [ booking::resource->search( id => $possible_resources ) ];
 			@$all_resources = grep { isTrue($_->type->transferable) || $_->owner.'' eq $bresv->pickup_lib.'' } @$all_resources;
 
 
-            my @good_resources;
+            my @good_resources = ();
             for my $res (@$all_resources) {
                 unless (isTrue($res->type->catalog_item)) {
                     push @good_resources, $res;
@@ -1495,16 +1496,16 @@
 			}
 
 			$self->method_lookup('open-ils.storage.transaction.commit')->run;
-			$log->info("\tProcessing of hold ".$hold->id." complete.");
+			$log->info("\tProcessing of bresv ".$bresv->id." complete.");
 
 			push @successes,
-				{ reservation => $hold->id,
+				{ reservation => $bresv->id,
 				  current_resource => ($best ? $best->id : undef) };
 
 		} otherwise {
 			my $e = shift;
 			if ($e !~ /^OK/o) {
-				$log->error("Processing of hold failed:  $e");
+				$log->error("Processing of bresv failed:  $e");
 				$self->method_lookup('open-ils.storage.transaction.rollback')->run;
 				throw $e if ($e =~ /IS NOT CONNECTED TO THE NETWORK/o);
 			}

Modified: trunk/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm	2009-12-21 16:21:48 UTC (rev 15207)
@@ -258,7 +258,7 @@
     my $name = shift || 'savepoint';
     return unless $self->{session} and $self->{xact_id};
 	$self->log(I, "setting savepoint '$name'");
-	my $stat = $self->request($self->app.".savepoint.set")
+	my $stat = $self->request($self->app.".savepoint.set", $name)
 	    or $self->log(E, "error setting savepoint '$name'");
     return $stat;
 }
@@ -268,7 +268,7 @@
     my $name = shift || 'savepoint';
     return unless $self->{session} and $self->{xact_id};
 	$self->log(I, "releasing savepoint '$name'");
-	my $stat = $self->request($self->app.".savepoint.release")
+	my $stat = $self->request($self->app.".savepoint.release", $name)
         or $self->log(E, "error releasing savepoint '$name'");
     return $stat;
 }
@@ -278,7 +278,7 @@
     my $name = shift || 'savepoint';
     return unless $self->{session} and $self->{xact_id};
 	$self->log(I, "rollback savepoint '$name'");
-	my $stat = $self->request($self->app.".savepoint.rollback")
+	my $stat = $self->request($self->app.".savepoint.rollback", $name)
         or $self->log(E, "error rolling back savepoint '$name'");
     return $stat;
 }

Added: trunk/Open-ILS/web/css/skin/default/booking.css
===================================================================
--- trunk/Open-ILS/web/css/skin/default/booking.css	                        (rev 0)
+++ trunk/Open-ILS/web/css/skin/default/booking.css	2009-12-21 16:21:48 UTC (rev 15207)
@@ -0,0 +1,48 @@
+div#brsrc_available_outer {
+    width: 50%;
+    float: left;
+    border-right: 1px solid #999999;
+}
+div#bra_and_brav {
+}
+div#reserve_right_side {
+    float: right;
+    width: 49%;
+    padding-left: 4px;
+}
+div#reserve_under {
+    clear: both;
+}
+div#reserve_datetime_start {
+    padding-bottom: 6px;
+}
+div#reserve_datetime_end {
+    padding-bottom: 6px;
+    border-bottom: 1px solid #999999;
+}
+label.bra {
+    font-style: italic;
+    padding-right: 12px;
+}
+h1.booking, h2.booking {
+    margin: 0;
+    padding-top: 0;
+    padding-bottom: 8px;
+}
+select#brsrc_list {
+    width: 90%;
+}
+label.reserve_datetime {
+    font-style: italic;
+    margin-bottom: 2px;
+}
+id#patron_barcode {
+    width: 150px;
+}
+div.nice_vertical_padding {
+    padding-top: 6px;
+    padding-bottom: 6px;
+}
+span.two_buttons {
+    text-align: center;
+}

Added: trunk/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js
===================================================================
--- trunk/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js	                        (rev 0)
+++ trunk/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js	2009-12-21 16:21:48 UTC (rev 15207)
@@ -0,0 +1,53 @@
+{
+    'NO_BRT_RESULTS': "There are no bookable resource types registered.",
+    'NO_TARG_DIV': "Could not find target div",
+    'NO_BRA_RESULTS': "Couldn't retrieve booking resource attributes.",
+    '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: ",
+    /* 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.";
+    },
+    '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.",
+    'OUTSTANDING_BRESV': "Outstanding reservations for patron",
+    'UNTARGETED': "None targeted",
+    'GET_PATRON_NO_RESULT': "No server response after attempting to " +
+        "look up patron by barcode.",
+    'HERE_ARE_EXISTING_BRESV': "Existing reservations for",
+    '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.",
+    '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_with_these_attr': "With these attributes:",
+    'AUTO_patron_barcode': "Reserve to patron barcode:",
+    'AUTO_ATTR_VALUE_next': "Next",
+    'AUTO_ATTR_VALUE_reserve_brsrc': "Reserve Selected",
+    'AUTO_ATTR_VALUE_reserve_brt': "Reserve Any",
+    'AUTO_ATTR_VALUE_button_edit_existing': "Edit selected",
+    'AUTO_ATTR_VALUE_button_cancel_existing': "Cancel selcted",
+    'AUTO_bresv_grid_type': "Type",
+    'AUTO_bresv_grid_resource': "Resource",
+    'AUTO_bresv_grid_start_time': "Start time",
+    'AUTO_bresv_grid_end_time': "End time"
+}

Added: trunk/Open-ILS/web/js/ui/default/booking/reservation.js
===================================================================
--- trunk/Open-ILS/web/js/ui/default/booking/reservation.js	                        (rev 0)
+++ trunk/Open-ILS/web/js/ui/default/booking/reservation.js	2009-12-21 16:21:48 UTC (rev 15207)
@@ -0,0 +1,589 @@
+/*
+ * Details, details...
+ */
+dojo.require("fieldmapper.OrgUtils");
+dojo.require("openils.PermaCrud");
+dojo.require("dojo.data.ItemFileReadStore");
+dojo.require("dijit.form.DateTextBox");
+dojo.require("dijit.form.TimeTextBox");
+dojo.requireLocalization("openils.booking", "reservation");
+
+/*
+ * Globals; prototypes and their instances
+ */
+var localeStrings = dojo.i18n.getLocalization("openils.booking", "reservation");
+var pcrud = new openils.PermaCrud();
+var our_brt;
+var brsrc_index = {};
+var bresv_index = {};
+
+function AttrValueTable() { this.t = {}; }
+AttrValueTable.prototype.set = function(attr, value) { this.t[attr] = value; };
+AttrValueTable.prototype.update_from_selector = function(selector) {
+    var attr  = selector.name.match(/_(\d+)$/)[1];
+    var value = selector.options[selector.selectedIndex].value;
+    if (attr)
+        attr_value_table.set(attr, value);
+};
+AttrValueTable.prototype.get_all_values = function() {
+    var values = [];
+    for (var k in this.t) {
+        if (this.t[k] != undefined && this.t[k] != "")
+            values.push(this.t[k]);
+    }
+    return values;
+};
+var attr_value_table =  new AttrValueTable();
+
+function TimestampRange() {
+    this.start = {"date": undefined, "time": undefined};
+    this.end = {"date": undefined, "time": undefined};
+}
+TimestampRange.prototype.get_timestamp = function(when) {
+    return (this[when].date + " " + this[when].time);
+};
+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 (when && which) {
+        this[when][which] =
+            this["split_" + which](widget.serialize(widget.value));
+    }
+};
+TimestampRange.prototype.is_backwards = function() {
+    return (this.get_timestamp("start") > this.get_timestamp("end"));
+};
+var reserve_timestamp_range = new TimestampRange();
+
+function SelectorMemory(selector) {
+    this.selector = selector;
+    this.memory = {};
+}
+SelectorMemory.prototype.save = function() {
+    for (var i = 0; i < this.selector.options.length; i++) {
+        if (this.selector.options[i].selected) {
+            this.memory[this.selector.options[i].value] = true;
+        }
+    }
+};
+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;
+        }
+    }
+};
+
+/*
+ * 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(
+            {"data": flatten_to_dojo_data([])}
+        )
+    );
+}
+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.
+ */
+function get_all_noncat_brt() {
+    return pcrud.search("brt",
+        {"id": {"!=": null}, "catalog_item": "f"},
+        {"order_by": {"brt":"name"}}
+    );
+}
+
+function get_brsrc_id_list() {
+    var options = {"type": our_brt.id()};
+
+    /* This mechanism for avoiding the passing of an empty 'attribute_values'
+     * option is essential because if you pass such an option to the
+     * middle layer API at all, it won't return any IDs for brsrcs that
+     * don't have at least one attribute of some kind.
+     */
+    var attribute_values = attr_value_table.get_all_values();
+    if (attribute_values.length > 0)
+        options.attribute_values = attribute_values;
+
+    options.available = reserve_timestamp_range.get_range();
+
+    return fieldmapper.standardRequest(
+        ["open-ils.booking", "open-ils.booking.resources.filtered_id_list"],
+        [xulG.auth.session.key, options]
+    );
+}
+
+// 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]);
+        }
+        brsrc_index[id_list[i]].isdeleted(false); // See NOTE below.
+    }
+    /* A second pass to indicate any entries in the cache to be hidden. */
+    for (var i in brsrc_index) {
+        if (id_list.indexOf(Number(i)) < 0) { // Number() is important.
+            brsrc_index[i].isdeleted(true); // See NOTE below.
+        }
+    }
+    /* 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).
+     */
+}
+
+function check_bresv_targeting(results) {
+    var missing = 0;
+    for (var i in results) {
+        if (!(results[i].targeting && results[i].targeting.current_resource))
+            missing++;
+    }
+    return missing;
+}
+
+function create_bresv(resource_list) {
+    var barcode = document.getElementById("patron_barcode").value;
+    if (barcode == "") {
+        alert(localeStrings.WHERES_THE_BARCODE);
+        return;
+    }
+    var results;
+    try {
+        results = fieldmapper.standardRequest(
+            ["open-ils.booking", "open-ils.booking.reservations.create"],
+            [
+                xulG.auth.session.key,
+                barcode,
+                reserve_timestamp_range.get_range(),
+                our_brt.id(),
+                resource_list,
+                attr_value_table.get_all_values()
+            ]
+        );
+    } catch (E) {
+        alert(localeStrings.CREATE_BRESV_LOCAL_ERROR + E);
+    }
+    if (results) {
+        if (is_ils_error(results)) {
+            if (is_ils_actor_card_error(results)) {
+                alert(localeStrings.ACTOR_CARD_NOT_FOUND);
+            } else {
+                alert(my_ils_error(
+                    localeStrings.CREATE_BRESV_SERVER_ERROR, results
+                ));
+            }
+        } else {
+            var missing;
+            alert((missing = check_bresv_targeting(results)) ?
+                localeStrings.CREATE_BRESV_OK_MISSING_TARGET(
+                    results.length, missing
+                ) :
+                localeStrings.CREATE_BRESV_OK(results.length)
+            );
+            update_brsrc_list();
+            update_bresv_grid();
+        }
+    } else {
+        alert(localeStrings.CREATE_BRESV_SERVER_NO_RESPONSE);
+    }
+}
+
+function flatten_to_dojo_data(obj_list) {
+    return {
+        "label": "id",
+        "identifier": "id",
+        "items": obj_list.map(function(o) {
+            var new_obj = {
+                "id": o.id(),
+                "type": o.target_resource_type().name(),
+                "start_time": humanize_timestamp_string(o.start_time()),
+                "end_time": humanize_timestamp_string(o.end_time()),
+            };
+
+            if (o.current_resource())
+                new_obj["resource"] = o.current_resource().barcode();
+            else if (o.target_resource())
+                new_obj["resource"] = "* " + o.target_resource().barcode();
+            else
+                new_obj["resource"] = "* " + localeStrings.UNTARGETED + " *";
+            return new_obj;
+        })
+    };
+}
+
+function create_bresv_on_brsrc() {
+    var selector = document.getElementById("brsrc_list");
+    var selected_values = [];
+    for (var i in selector.options) {
+        if (selector.options[i].selected)
+            selected_values.push(selector.options[i].value);
+    }
+    if (selected_values.length > 0)
+        create_bresv(selected_values);
+    else
+        alert(localeStrings.SELECT_A_BRSRC_THEN);
+}
+
+function create_bresv_on_brt() { create_bresv(); }
+
+function get_actor_by_barcode(barcode) {
+    var usr = fieldmapper.standardRequest(
+        ["open-ils.actor", "open-ils.actor.user.fleshed.retrieve_by_barcode"],
+        [xulG.auth.session.key, barcode]
+    );
+    if (usr == null) {
+        alert(localeStrings.GET_PATRON_NO_RESULT);
+    } else if (is_ils_error(usr)) {
+        return null; /* XXX inelegant: this function is quiet about errors
+                        here because to report them would be redundant with
+                        another function that gets called right after this one.
+                      */
+    } else {
+        return usr;
+    }
+}
+
+function init_bresv_grid(barcode) {
+    var result = fieldmapper.standardRequest(
+        ["open-ils.booking",
+            "open-ils.booking.reservations.filtered_id_list"
+        ],
+        [xulG.auth.session.key, {
+            "user_barcode": barcode,
+            "fields": {
+                "pickup_time": null,
+                "cancel_time": null,
+                "return_time": null
+            }
+        }, /* whole_obj */ true]
+    );
+    if (result == null) {
+        set_datagrid_empty_store(bresvGrid);
+        alert(localeStrings.GET_BRESV_LIST_NO_RESULT);
+    } else if (is_ils_error(result)) {
+        set_datagrid_empty_store(bresvGrid);
+        if (is_ils_actor_card_error(result)) {
+            alert(localeStrings.ACTOR_CARD_NOT_FOUND);
+        } else {
+            alert(my_ils_error(localeStrings.GET_BRESV_LIST_ERR, result));
+        }
+    } else {
+        bresvGrid.setStore(
+            new dojo.data.ItemFileReadStore(
+                {"data": flatten_to_dojo_data(result)}
+            )
+        );
+        for (var i in result) {
+            bresv_index[result[i].id()] = result[i];
+        }
+    }
+}
+
+function cancel_reservations(bresv_list) {
+    for (var i in bresv_list) { bresv_list[i].cancel_time("now"); }
+    pcrud.update(
+        bresv_list, {
+            "oncomplete": function() {
+                update_bresv_grid();
+                alert(localeStrings.CXL_BRESV_SUCCESS(bresv_list.length));
+            },
+            "onerror": function(o) {
+                update_bresv_grid();
+                alert(localeStrings.CXL_BRESV_FAILURE + "\n" + o);
+            }
+        }
+    );
+}
+
+/*
+ * These functions deal with interface tricks (populating widgets,
+ * changing the page, etc.).
+ */
+function provide_brt_selector(targ_div) {
+    if (!targ_div) {
+        alert(localeStrings.NO_TARG_DIV);
+    } else {
+        var brt_list = xulG.brt_list = get_all_noncat_brt();
+        if (!brt_list || brt_list.length < 1) {
+            targ_div.appendChild(
+                document.createTextNode(localeStrings.NO_BRT_RESULTS)
+            );
+        } else {
+            var selector = document.createElement("select");
+            selector.setAttribute("id", "brt_selector");
+            selector.setAttribute("name", "brt_selector");
+            /* I'm reluctantly hardcoding this "size" attribute as 8
+             * because you can't accomplish this with CSS anyway.
+             */
+            selector.setAttribute("size", 8);
+            for (var i in brt_list) {
+                var option = document.createElement("option");
+                option.setAttribute("value", brt_list[i].id());
+                option.appendChild(document.createTextNode(brt_list[i].name()));
+                selector.appendChild(option);
+            }
+            targ_div.appendChild(selector);
+        }
+    }
+}
+
+function init_reservation_interface(f) {
+    /* 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 */
+    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) {
+        alert(localeString.NO_BRA_LIST);
+        return;
+    }
+
+    /* Get a table of values that can apply to the above attributes. */
+    var brav_by_bra = {};
+    bra_list.map(function(o) {
+        brav_by_bra[o.id()] = pcrud.search("brav", {"attr": o.id()});
+    });
+
+    /* Create DOM widgets to represent each attribute/values set. */
+    for (var i in bra_list) {
+        var bra_div = document.createElement("div");
+        bra_div.setAttribute("class", "nice_vertical_padding");
+
+        var bra_select = document.createElement("select");
+        bra_select.setAttribute("name", "bra_" + bra_list[i].id());
+        bra_select.setAttribute(
+            "onchange",
+            "attr_value_table.update_from_selector(this); update_brsrc_list();"
+        );
+
+        var bra_opt_any = document.createElement("option");
+        bra_opt_any.appendChild(document.createTextNode(localeStrings.ANY));
+        bra_opt_any.setAttribute("value", "");
+
+        bra_select.appendChild(bra_opt_any);
+
+        var bra_label = document.createElement("label");
+        bra_label.setAttribute("class", "bra");
+        bra_label.appendChild(document.createTextNode(bra_list[i].name()));
+
+        var j = bra_list[i].id();
+        for (var k in brav_by_bra[j]) {
+            var bra_opt = document.createElement("option");
+            bra_opt.setAttribute("value", brav_by_bra[j][k].id());
+            bra_opt.appendChild(
+                document.createTextNode(brav_by_bra[j][k].valid_value())
+            );
+            bra_select.appendChild(bra_opt);
+        }
+
+        bra_div.appendChild(bra_label);
+        bra_div.appendChild(bra_select);
+        document.getElementById("bra_and_brav").appendChild(bra_div);
+    }
+    /* Add a prominent label reminding the user what resource type they're
+     * asking about. */
+    document.getElementById("brsrc_list_header").innerHTML = our_brt.name();
+
+    update_brsrc_list();
+}
+
+function update_brsrc_list() {
+    var brsrc_id_list = get_brsrc_id_list();
+    sync_brsrc_index_from_ids(brsrc_id_list);
+
+    var target_selector = document.getElementById("brsrc_list");
+    var selector_memory = new SelectorMemory(target_selector);
+    selector_memory.save();
+    target_selector.innerHTML = "";
+
+    for (var i in brsrc_index) {
+        if (brsrc_index[i].isdeleted()) {
+            continue;
+        }
+        var opt = document.createElement("option");
+        opt.setAttribute("value", brsrc_index[i].id());
+        opt.appendChild(document.createTextNode(brsrc_index[i].barcode()));
+        target_selector.appendChild(opt);
+    }
+
+    selector_memory.restore();
+}
+
+function update_bresv_grid() {
+    var widg = document.getElementById("patron_barcode");
+    if (widg.value != "") {
+        setTimeout(function() {
+            var target = document.getElementById(
+                "existing_reservation_patron_line"
+            );
+            var patron = get_actor_by_barcode(widg.value);
+            if (patron) {
+                target.innerHTML = (
+                    localeStrings.HERE_ARE_EXISTING_BRESV + " " +
+                    formal_name(patron) + ": "
+                );
+            } else {
+                target.innerHTML = "";
+            }
+        }, 0);
+        setTimeout(function() { init_bresv_grid(widg.value); }, 0);
+
+        reveal_dom_element(document.getElementById("reserve_under"));
+    }
+}
+
+function init_timestamp_widgets() {
+    var when = ["start", "end"];
+    for (var i in when) {
+        reserve_timestamp_range.update_from_widget(
+            new dijit.form.TimeTextBox({
+                name: "reserve_time_" + when[i],
+                value: new Date(),
+                constraints: {
+                    timePattern: "HH:mm",
+                    clickableIncrement: "T00:15:00",
+                    visibleIncrement: "T00:15:00",
+                    visibleRange: "T01:30:00",
+                },
+                onChange: function() {
+                    reserve_timestamp_range.update_from_widget(this);
+                    update_brsrc_list();
+                }
+            }, "reserve_time_" + when[i])
+        );
+        reserve_timestamp_range.update_from_widget(
+            new dijit.form.DateTextBox({
+                name: "reserve_date_" + when[i],
+                value: new Date(),
+                onChange: function() {
+                    reserve_timestamp_range.update_from_widget(this);
+                    update_brsrc_list();
+                }
+            }, "reserve_date_" + when[i])
+        );
+    }
+}
+
+function cancel_selected_bresv(bresv_dojo_items) {
+    if (bresv_dojo_items && bresv_dojo_items.length > 0) {
+        cancel_reservations(
+            bresv_dojo_items.map(function(o) { return bresv_index[o.id]; })
+        );
+    } else {
+        alert(localeStrings.CXL_BRESV_SELECT_SOMETHING);
+    }
+}
+
+/* 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!
+        }
+    }
+}
+
+/*
+ * my_init
+ */
+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();
+}

Modified: trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
===================================================================
--- trunk/Open-ILS/web/opac/locale/en-US/lang.dtd	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/web/opac/locale/en-US/lang.dtd	2009-12-21 16:21:48 UTC (rev 15207)
@@ -273,7 +273,7 @@
 <!ENTITY staff.cat.opac.undelete_record.accesskey "U">
 <!ENTITY staff.cat.opac.undelete_record.label "Undelete Record">
 <!ENTITY staff.cat.opac.create_brt_from_record.accesskey "T">
-<!ENTITY staff.cat.opac.create_brt_from_record.label "Create Booking Resource From Copy">
+<!ENTITY staff.cat.opac.create_brt_from_record.label "Make Item Bookable">
 <!ENTITY staff.cat.opac.menu.accesskey "A">
 <!ENTITY staff.cat.opac.menu.label "Actions for this Record">
 <!ENTITY staff.cat.opac.opac_view.accesskey "O">
@@ -680,10 +680,6 @@
 <!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr_value.accesskey "V">
 <!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr_map.label "Resource Attribute Maps">
 <!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr_map.accesskey "M">
-<!ENTITY staff.main.menu.admin.server_admin.booking.reservation.label "Reservations">
-<!ENTITY staff.main.menu.admin.server_admin.booking.reservation.accesskey "N">
-<!ENTITY staff.main.menu.admin.server_admin.booking.reservation_attr_value_map.label "Reservation Attribute Value Maps">
-<!ENTITY staff.main.menu.admin.server_admin.booking.reservation_attr_value_map.accesskey "B">
 
 <!ENTITY staff.main.menu.admin.developer.label "For developers...">
 <!ENTITY staff.main.menu.admin.download_patrons.accesskey "D">
@@ -765,6 +761,15 @@
 <!ENTITY staff.main.menu.acq.upload.label "Load Order Record">
 <!ENTITY staff.main.menu.acq.po.label "Purchase Orders">
 
+<!ENTITY staff.main.menu.booking.label "Booking">
+<!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.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">
+<!ENTITY staff.main.menu.booking.reservation_return.accesskey "R">
+
 <!ENTITY staff.main.menu.acq.fund.label "Funds">
 <!ENTITY staff.main.menu.acq.funding_source.label "Funding Sources">
 <!ENTITY staff.main.menu.acq.provider.label "Providers">
@@ -2151,7 +2156,9 @@
 <!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_create_brt.label "Create Booking Resource Type from this Record">
+<!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.sel_patron.label "Show Last Few Circulations">
 <!ENTITY staff.cat.copy_browser.actions.sel_patron.accesskey "L">
@@ -2200,7 +2207,7 @@
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.add_items_to_bucket.accesskey "B">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.sel_copy_details.label "Show Item Details">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.sel_copy_details.accesskey "I">
-<!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_create_brt.label "Create Booking Resource Type from this Record">
+<!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_create_brt.label "Make This Item Bookable">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_create_brt.accesskey "Y">
 
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.sel_patron.label "Show Last Few Circulations">

Added: trunk/Open-ILS/web/templates/default/booking/reservation.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/booking/reservation.tt2	                        (rev 0)
+++ trunk/Open-ILS/web/templates/default/booking/reservation.tt2	2009-12-21 16:21:48 UTC (rev 15207)
@@ -0,0 +1,93 @@
+[% WRAPPER "default/base.tt2" %]
+<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">
+    dojo.require("dojox.grid.DataGrid");
+    openils.Util.addOnLoad(my_init);
+</script>
+<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 class="nice_vertical_padding">
+                <input type="submit" class="AUTO_ATTR_VALUE_next" />
+            </div>
+        </form>
+    </div>
+
+    <div id="brt_reserve_block" class="container">
+        <form>
+            <div id="brsrc_available_outer">
+                <h1 class="booking" id="brsrc_list_header"></h1>
+                <!-- I'm reluctantly hardcoding the size attribute below to 12
+                    since you can't get the behavior of the size attribute with
+                    anything in CSS. -->
+                <select id="brsrc_list" name="brsrc_list" multiple="multiple"
+                    size="12"></select>
+                <div class="nice_vertical_padding">
+                    <label class="AUTO_patron_barcode"
+                        for="patron_barcode" /></label>
+                    <input name="patron_barcode" id="patron_barcode"
+                        onchange="update_bresv_grid();" />
+                </div>
+                <div class="nice_vertical_padding">
+                    <span class="two_buttons">
+                        <input type="button"
+                            class="AUTO_ATTR_VALUE_reserve_brsrc"
+                            onclick="create_bresv_on_brsrc();" />
+                        &nbsp;
+                        <input type="button"
+                            class="AUTO_ATTR_VALUE_reserve_brt"
+                            onclick="create_bresv_on_brt();" />
+                    </span>
+                </div>
+            </div>
+            <div id="reserve_right_side">
+                <h2 class="booking AUTO_i_need_this_resource"></h2>
+                <div id="reserve_datetime_start">
+                    <label class="reserve_datetime AUTO_starting_at"
+                        for="reserve_date_start"></label><br />
+                    <input id="reserve_date_start" />
+                    <input id="reserve_time_start" />
+                </div>
+                <div id="reserve_datetime_end">
+                    <label class="reserve_datetime AUTO_ending_at"
+                        for="reserve_date_end"></label><br />
+                    <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>
+            </div>
+            <div id="reserve_under">
+                <hr />
+                <h2 class="booking" id="existing_reservation_patron_line"></h2>
+                <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>
+[% END %]

Deleted: trunk/Open-ILS/web/templates/default/conify/global/booking/reservation.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/conify/global/booking/reservation.tt2	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/web/templates/default/conify/global/booking/reservation.tt2	2009-12-21 16:21:48 UTC (rev 15207)
@@ -1,39 +0,0 @@
-[% WRAPPER default/base.tt2 %]
-[% ctx.page_title = 'Reservations' %]
-
-<script type ="text/javascript">
-    dojo.require('dijit.form.FilteringSelect');
-    dojo.require('openils.widget.AutoGrid');
-
-    openils.Util.addOnLoad(
-        function() {
-            ustGrid.loadAll({order_by:{bresv : 'name'}});
-        }
-    );
-</script>
-
-
-
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client" class='oils-header-panel'>
-    <div>Reservations</div>
-    <div>
-        <button dojoType='dijit.form.Button' onClick='ustGrid.showCreateDialog()'>New Reservation</button>
-        <button dojoType='dijit.form.Button' onClick='ustGrid.deleteSelected()'>Delete Selected</button>
-    </div>
-</div>
-
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
-    <table  jsId="ustGrid"
-            autoHeight='true'
-            dojoType="openils.widget.AutoGrid"
-            fieldOrder="['name', 'fine_interval', 'fine_amount',
-                'owner', 'catalog_item', 'transferable', 'record']"
-            query="{name: '*'}"
-            defaultCellWidth='"auto"'
-            fmClass='bresv'
-            showPaginator='true'
-            editOnEnter='true'>
-    </table>
- </div>
-
-[% END %]

Deleted: trunk/Open-ILS/web/templates/default/conify/global/booking/reservation_attr_value_map.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/conify/global/booking/reservation_attr_value_map.tt2	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/web/templates/default/conify/global/booking/reservation_attr_value_map.tt2	2009-12-21 16:21:48 UTC (rev 15207)
@@ -1,39 +0,0 @@
-[% WRAPPER default/base.tt2 %]
-[% ctx.page_title = 'Reservation Attribute Value Maps' %]
-
-<script type ="text/javascript">
-    dojo.require('dijit.form.FilteringSelect');
-    dojo.require('openils.widget.AutoGrid');
-
-    openils.Util.addOnLoad(
-        function() {
-            ustGrid.loadAll({order_by:{bravm : 'name'}});
-        }
-    );
-</script>
-
-
-
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client" class='oils-header-panel'>
-    <div>Reservation Attribute Value Maps</div>
-    <div>
-        <button dojoType='dijit.form.Button' onClick='ustGrid.showCreateDialog()'>New Reservation Attribute Value Map</button>
-        <button dojoType='dijit.form.Button' onClick='ustGrid.deleteSelected()'>Delete Selected</button>
-    </div>
-</div>
-
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
-    <table  jsId="ustGrid"
-            autoHeight='true'
-            dojoType="openils.widget.AutoGrid"
-            fieldOrder="['name', 'fine_interval', 'fine_amount',
-                'owner', 'catalog_item', 'transferable', 'record']"
-            query="{name: '*'}"
-            defaultCellWidth='"auto"'
-            fmClass='bravm'
-            showPaginator='true'
-            editOnEnter='true'>
-    </table>
- </div>
-
-[% END %]

Modified: trunk/Open-ILS/web/templates/default/conify/global/booking/resource.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/conify/global/booking/resource.tt2	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/web/templates/default/conify/global/booking/resource.tt2	2009-12-21 16:21:48 UTC (rev 15207)
@@ -7,31 +7,17 @@
     dojo.require('openils.widget.AutoGrid');
     dojo.require('openils.XUL');
 
-    function get_brsrc_ids() {
-        var cgi = new CGI();
-        var results = JSON2js(cgi.param('results'));
-        if (!results) return undefined;
-        var brsrc_ids = [];
-        for (var i in results) {
-            brsrc_ids.push(results[i][0]);
-        }
-        return brsrc_ids;
-    }
-
     openils.Util.addOnLoad(
         function() {
             var search = undefined; // default to all objs
-            var brsrc_ids = get_brsrc_ids();
-            if (brsrc_ids) {
-                search = {id: brsrc_ids};
+            if (xulG && xulG.resultant_brsrc) {
+                search = {id: xulG.resultant_brsrc};
             }
             ustGrid.loadAll({order_by:{brsrc : 'barcode'}}, search);
         }
     );
 </script>
 
-
-
 <div dojoType="dijit.layout.ContentPane" layoutAlign="client" class='oils-header-panel'>
     <div>Resources</div>
     <div>

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-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js	2009-12-21 16:21:48 UTC (rev 15207)
@@ -116,7 +116,6 @@
             );
         }
 
-
         var cmd_map = {
             'cmd_broken' : [
                 ['oncommand'],
@@ -646,14 +645,6 @@
                 ['oncommand'],
                 function() { open_eg_web_page('conify/global/booking/resource_attr_map'); }
             ],
-            'cmd_server_admin_booking_reservation': [
-                ['oncommand'],
-                function() { open_eg_web_page('conify/global/booking/reservation'); }
-            ],
-            'cmd_server_admin_booking_reservation_attr_value_map': [
-                ['oncommand'],
-                function() { open_eg_web_page('conify/global/booking/reservation_attr_value_map'); }
-            ],
             'cmd_acq_view_picklist' : [
                 ['oncommand'],
                 function() { open_eg_web_page('acq/picklist/list', 'menu.cmd_acq_view_picklist.tab'); }
@@ -698,7 +689,51 @@
                 ['oncommand'],
                 function() { open_eg_web_page('conify/global/acq/distribution_formula', 'menu.cmd_acq_view_distrib_formula.tab'); }
             ],
-
+            'cmd_booking_reservation' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        "/eg/booking/reservation",
+                        {
+                            "tab_name": offlineStrings.getString(
+                                "menu.cmd_booking_reservation.tab"
+                            ),
+                            "browser": false
+                        },
+                        xulG
+                    );
+                }
+            ],
+            'cmd_booking_reservation_pickup' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        "/eg/booking/reservation_pickup",
+                        {
+                            "tab_name": offlineStrings.getString(
+                                "menu.cmd_booking_reservation.tab"
+                            ),
+                            "browser": false
+                        },
+                        xulG
+                    );
+                }
+            ],
+            'cmd_booking_reservation_return' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        "/eg/booking/reservation_return",
+                        {
+                            "tab_name": offlineStrings.getString(
+                                "menu.cmd_booking_reservation.tab"
+                            ),
+                            "browser": false
+                        },
+                        xulG
+                    );
+                }
+            ],
             'cmd_reprint' : [
                 ['oncommand'],
                 function() {

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-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul	2009-12-21 16:21:48 UTC (rev 15207)
@@ -88,7 +88,11 @@
     <command id="cmd_acq_view_exchange_rate" />
     <command id="cmd_acq_view_distrib_formula" />
 
+    <command id="cmd_booking_reservation" />
+    <command id="cmd_booking_reservation_pickup" />
+    <command id="cmd_booking_reservation_return" />
 
+
     <!-- local admin menu commands -->
     <command id="cmd_local_admin_fonts_and_sounds"/>
     <command id="cmd_local_admin_printer"/>
@@ -127,8 +131,6 @@
     <command id="cmd_server_admin_booking_resource_attr" />
     <command id="cmd_server_admin_booking_resource_attr_value" />
     <command id="cmd_server_admin_booking_resource_attr_map" />
-    <command id="cmd_server_admin_booking_reservation" />
-    <command id="cmd_server_admin_booking_reservation_attr_value_map" />
 </commandset>
 
 
@@ -258,6 +260,14 @@
     </menupopup>
 </menu>
 
+<!-- The Booking menu on the main menu -->
+<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.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>
+</menu>
 
 <!-- The Search menu on the main menu -->
 <menu id="main.menu.search" label="&staff.main.menu.search.label;" accesskey="&staff.main.menu.search.accesskey;">
@@ -331,8 +341,6 @@
                         <menuitem label="&staff.main.menu.admin.server_admin.booking.resource_attr.label;" command="cmd_server_admin_booking_resource_attr" accesskey="&staff.main.menu.admin.server_admin.booking.resource_attr.accesskey;"/>
                         <menuitem label="&staff.main.menu.admin.server_admin.booking.resource_attr_value.label;" command="cmd_server_admin_booking_resource_attr_value" accesskey="&staff.main.menu.admin.server_admin.booking.resource_attr_value.accesskey;"/>
                         <menuitem label="&staff.main.menu.admin.server_admin.booking.resource_attr_map.label;" command="cmd_server_admin_booking_resource_attr_map" accesskey="&staff.main.menu.admin.server_admin.booking.resource_attr_map.accesskey;"/>
-                        <menuitem label="&staff.main.menu.admin.server_admin.booking.reservation.label;" command="cmd_server_admin_booking_reservation" accesskey="&staff.main.menu.admin.server_admin.booking.reservation.accesskey;"/>
-                        <menuitem label="&staff.main.menu.admin.server_admin.booking.reservation_attr_value_map.label;" command="cmd_server_admin_booking_reservation_attr_value_map" accesskey="&staff.main.menu.admin.server_admin.booking.reservation_attr_value_map.accesskey;"/>
                     </menupopup>
                 </menu>
             </menupopup>

Modified: trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul	2009-12-21 16:21:48 UTC (rev 15207)
@@ -72,6 +72,7 @@
         <menu id="main.menu.circ" />
         <menu id="main.menu.cat" />
         <menu id="main.menu.acq" />
+        <menu id="main.menu.booking" />
         <spacer flex="1" />
         <menu id="main.menu.admin" />
         <!--

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-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties	2009-12-21 16:21:48 UTC (rev 15207)
@@ -232,6 +232,8 @@
 menu.cmd_acq_view_currency_type.tab=Currency Types
 menu.cmd_acq_view_exchange_rate.tab=Exchange Rates
 menu.cmd_acq_view_distrib_formula.tab=Distribution Formulas
+menu.cmd_booking_resource.tab=Resources
+menu.cmd_booking_reservation.tab=Reservations
 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/cat/copy_browser.js
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/cat/copy_browser.js	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/xul/staff_client/server/cat/copy_browser.js	2009-12-21 16:21:48 UTC (rev 15207)
@@ -145,64 +145,51 @@
                         'cmd_create_brt' : [
                             ['command'],
                             function() {
-                                JSAN.use('util.functional');
-                                var cs = document.getElementById('catStrings');
-                                // Filter out selected rows that aren't copies.
+                                JSAN.use("cat.util");
+                                JSAN.use("util.functional");
+
+                                /* Filter selected rows that aren"t copies. */
                                 var list = util.functional.filter_list(
                                     obj.sel_list,
                                     function (o) {
-                                        return o.split(/_/)[0] == 'acp';
+                                        return o.split(/_/)[0] == "acp";
                                     }
                                 );
-                                // Get the IDs of all copy rows.
-                                var copy_ids = util.functional.map_list(
-                                    list, function (o) {
-                                        return obj.map_acp[o].id();
+                                var results = cat.util.make_bookable(
+                                    util.functional.map_list(
+                                        list, function (o) {
+                                            return obj.map_acp[o].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");
+
+                                /* Filter selected rows that aren"t copies. */
+                                var list = util.functional.filter_list(
+                                    obj.sel_list,
+                                    function (o) {
+                                        return o.split(/_/)[0] == "acp";
                                     }
                                 );
-                                // Ask the ML to create brt's and brsrc's.
-                                var results = fieldmapper.standardRequest(
-                                    ['open-ils.booking', 'open-ils.booking.create_brt_and_brsrc_from_copies'],
-                                    [ses(), copy_ids]
+                                var results = cat.util.make_bookable(
+                                    util.functional.map_list(
+                                        list, function (o) {
+                                            return obj.map_acp[o].id();
+                                        }
+                                    )
                                 );
-                                if (results == null) {
-                                    alert(cs.getString('staff.cat.copy_browser.brt_and_brsrc.create_failed_silent'));
+                                if (results) {
+                                    cat.util.edit_new_bresv(results);
                                 }
-                                else if (typeof results.ilsevent != 'undefined') {
-                                    // FIXME Isn't there a more standardized
-                                    // way to show this error?
-                                    alert(cs.getFormattedString(
-                                        'staff.cat.copy_browser.brt_and_brsrc.create_failed',
-                                        [results.ilsevent, results.textcode,
-                                            results.desc, results.debug]
-                                    ));
-                                } else {
-                                    // Spawn new tab to allow editing new
-                                    // resources.
-                                    try {
-                                        var url = urls.XUL_BROWSER + '?url=' +
-                                            window.escape(
-                                                xulG.url_prefix(urls.EG_WEB_BASE) +
-                                                '/conify/global/booking/resource?results=' +
-                                                window.escape(js2JSON(results['brsrc']))
-                                            );
-                                        // Sorry about the CGI params, but I
-                                        // don't see another choice for
-                                        // passing data to conify pages. This
-                                        // has the obvious problem of a
-                                        // character length limit. FIXME
-                                        xulG.new_tab(url,
-                                            {'tab_name': cs.getString('staff.cat.copy_browser.brt_and_brsrc.newtab_name'),
-                                             'browser' : false},
-                                            {'no_xulG' : false}
-                                        );
-                                    } catch(E) {
-                                        JSAN.use('util.error');
-                                        var error = new util.error;
-                                        var f = error.standard_unexpected_error_alert;
-                                        f(cs.getString('staff.cat.copy_browser.brt_and_brsrc.newtab_failed'), E);
-                                    }
-                                }
                             }
                         ],
                         'cmd_add_items' : [

Modified: trunk/Open-ILS/xul/staff_client/server/cat/copy_browser.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/cat/copy_browser.xul	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/xul/staff_client/server/cat/copy_browser.xul	2009-12-21 16:21:48 UTC (rev 15207)
@@ -73,6 +73,7 @@
         <command id="cmd_broken" />
         <command id="sel_copy_details"/>
         <command id="cmd_create_brt"/>
+        <!-- <command id="cmd_book_item_now"/> -->
         <command id="sel_patron"/>
         <command id="sel_clip" />
         <command id="cmd_clear" />
@@ -104,6 +105,7 @@
             <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;" />
             <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="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;"/>

Modified: trunk/Open-ILS/xul/staff_client/server/cat/util.js
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/cat/util.js	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/xul/staff_client/server/cat/util.js	2009-12-21 16:21:48 UTC (rev 15207)
@@ -517,4 +517,77 @@
         if (error) error.standard_unexpected_error_alert('cat.util.fast_item_add',E); else alert('FIXME: ' + E);
     }
 }
+
+cat.util.make_bookable = function(copy_ids) {
+    var results = fieldmapper.standardRequest(
+        ["open-ils.booking", "open-ils.booking.resources.create_from_copies"],
+        [ses(), copy_ids]
+    );
+    if (results == null) {
+        alert(document.getElementById("catStrings").getString(
+            "staff.cat.copy_browser.make_bookable.create_failed_silent"
+        ));
+    }
+    else if (typeof results.ilsevent != "undefined") {
+        alert(document.getElementById("catStrings").getFormattedString(
+            "staff.cat.copy_browser.make_bookable.create_failed",
+            [results.ilsevent, results.textcode, results.desc, results.debug]
+        ));
+    }
+    return results;
+}
+
+cat.util.edit_new_brsrc = function(brsrc_list) {
+    /* Spawn new tab to allow editing new resources. */
+    try {
+        xulG.resultant_brsrc = brsrc_list.map(function(o) { return o[0]; });
+        xulG.new_tab(
+            urls.XUL_BROWSER + "?url=" + window.escape(
+                xulG.url_prefix("/eg/conify/global/booking/resource")
+            ), {
+                "tab_name": offlineStrings.getString(
+                    "menu.cmd_booking_resource.tab"
+                 ),
+                "browser" : true
+            }, {
+                "no_xulG": false,
+                "show_print_button": false,
+                "show_nav_buttons": true,
+                "passthru_content_params": xulG
+            }
+        );
+    } catch(E) {
+        alert(
+            document.getElementById("catStrings").getFormattedString(
+                "staff.cat.copy_browser.make_bookable.newtab_failed"
+            ), E
+        );
+    }
+}
+
+cat.util.edit_new_bresv = function(booking_results) {
+    /* Spawn new tab to allow editing new reservations. */
+    try {
+        if (xulG.auth == undefined) {
+            xulG.auth = {"session": {"key": ses()}};
+        }
+        xulG.booking_results = booking_results;
+        xulG.new_tab(
+            xulG.url_prefix("/eg/booking/reservation"),
+            {
+                "tab_name": offlineStrings.getString(
+                    "menu.cmd_booking_reservation.tab"
+                 ),
+                "browser" : false
+            }, xulG
+        );
+    } catch(E) {
+        alert(
+            document.getElementById("catStrings").getString(
+                "staff.cat.copy_browser.make_bookable.newtab_failed"
+            ) + E
+        );
+    }
+}
+
 dump('exiting cat/util.js\n');

Modified: trunk/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties	2009-12-21 15:44:40 UTC (rev 15206)
+++ trunk/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties	2009-12-21 16:21:48 UTC (rev 15207)
@@ -12,10 +12,10 @@
 staff.cat.copy_browser.add_item.title=Add Item
 staff.cat.copy_browser.add_item.error=copy browser -> add copies
 staff.cat.copy_browser.add_items_bucket.error=copy browser -> add copies to bucket
-staff.cat.copy_browser.brt_and_brsrc.create_failed_silent=No response from server
-staff.cat.copy_browser.brt_and_brsrc.create_failed=Error from server: %1$d %2$s\n%3$s\n%4$s
-staff.cat.copy_browser.brt_and_brsrc.newtab_failed=Could not launch Booking Resource Editor: %1$s
-staff.cat.copy_browser.brt_and_brsrc.newtab_name=Resources
+staff.cat.copy_browser.make_bookable.create_failed_silent=No response from server
+staff.cat.copy_browser.make_bookable.create_failed=Error from server: %1$d %2$s\n%3$s\n%4$s
+staff.cat.copy_browser.make_bookable.newtab_failed=Could not open new tab
+staff.cat.copy_browser.make_bookable.newtab_name=Resources
 staff.cat.copy_browser.replace_barcode.failed=Barcode %1$s not likely replaced.
 staff.cat.copy_browser.replace_barcode.error=copy browser -> replace barcode
 staff.cat.copy_browser.edit_items.error=Copy Browser -> Edit Items



More information about the open-ils-commits mailing list