From: miker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4> Date: Mon, 21 Dec 2009 16:21:48 +0000 (+0000) Subject: Patch from Lebbeous Fogle-Weekley to add booking reservation interfaces, supporting... X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=f2f9636c6f7bb3b0e88664281b473f92ce55ef45;p=evergreen%2Fmasslnc.git Patch from Lebbeous Fogle-Weekley to add booking reservation interfaces, supporting backend changes and IDL cleanup. It's ... big. git-svn-id: svn://svn.open-ils.org/ILS/trunk@15207 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 505c3f1f68..5da71239fe 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2536,8 +2536,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA <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"/> diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm b/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm index 51ffdb0374..d2fc7d72a6 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm @@ -12,8 +12,6 @@ use OpenILS::Application::AppUtils; 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 @@ sub get_existing_brt { {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 @@ sub get_single_record_id { 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 @@ sub create_brt_and_brsrc { } 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 @@ sub create_brt_and_brsrc { 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 @@ sub resource_list_by_attrs { '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 @@ sub resource_list_by_attrs { $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 (!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] }, - } - }] - } - ] - }; + # If only one timestamp has been provided, make it into a range. + if (!ref($filters->{available})) { + $filters->{available} = [($filters->{available}) x 2]; } - } + 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 (!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] }, - } - } - }; + # If only one timestamp has been provided, make it into a range. + if (!ref($filters->{booked})) { + $filters->{booked} = [($filters->{booked}) x 2]; } + + 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 @@ The filter object parameter can contain the following keys: 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,6 +430,12 @@ sub reservation_list_by_filters { 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 @@ sub reservation_list_by_filters { $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 @@ sub reservation_list_by_filters { } 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 @@ __PACKAGE__->register_method( {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 @@ then the result includes any reservations that overlap with that time range. An by the top-level filters ('user', 'type', 'resource'). NOTES - ); - 1; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm index 811ad378f6..e2b60fa5e1 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm @@ -28,7 +28,7 @@ package booking::reservation; 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 diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm index f3bdf2602d..1894456acd 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm @@ -1332,7 +1332,7 @@ sub reservation_targeter { 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 @@ sub reservation_targeter { 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 @@ sub reservation_targeter { 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); - - my $ses = OpenSRF::AppSession->create('open-ils.booking'); - $possible_resources = $ses->request('open-ils.booking.resources.filtered_id_list', $filter)->gather(1); + $filter->{attribute_values} = [ map { $_->attr_value } @$attr_maps ] if (@$attr_maps); + $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 @@ sub reservation_targeter { } $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); } diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm b/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm index 12e85af0c2..63aa193c38 100644 --- a/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm +++ b/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm @@ -258,7 +258,7 @@ sub set_savepoint { 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 @@ sub release_savepoint { 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 @@ sub rollback_savepoint { 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; } diff --git a/Open-ILS/web/css/skin/default/booking.css b/Open-ILS/web/css/skin/default/booking.css new file mode 100644 index 0000000000..ca59b8e14e --- /dev/null +++ b/Open-ILS/web/css/skin/default/booking.css @@ -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; +} diff --git a/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js b/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js new file mode 100644 index 0000000000..4f1144b68d --- /dev/null +++ b/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js @@ -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" +} diff --git a/Open-ILS/web/js/ui/default/booking/reservation.js b/Open-ILS/web/js/ui/default/booking/reservation.js new file mode 100644 index 0000000000..612a54e110 --- /dev/null +++ b/Open-ILS/web/js/ui/default/booking/reservation.js @@ -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(); +} diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index 4912923d93..e54cbdc52b 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -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"> diff --git a/Open-ILS/web/templates/default/booking/reservation.tt2 b/Open-ILS/web/templates/default/booking/reservation.tt2 new file mode 100644 index 0000000000..fd337a8d0a --- /dev/null +++ b/Open-ILS/web/templates/default/booking/reservation.tt2 @@ -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();" /> + + <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 %] diff --git a/Open-ILS/web/templates/default/conify/global/booking/reservation.tt2 b/Open-ILS/web/templates/default/conify/global/booking/reservation.tt2 deleted file mode 100644 index 40a2a5c14c..0000000000 --- a/Open-ILS/web/templates/default/conify/global/booking/reservation.tt2 +++ /dev/null @@ -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 %] diff --git a/Open-ILS/web/templates/default/conify/global/booking/reservation_attr_value_map.tt2 b/Open-ILS/web/templates/default/conify/global/booking/reservation_attr_value_map.tt2 deleted file mode 100644 index a879e3f688..0000000000 --- a/Open-ILS/web/templates/default/conify/global/booking/reservation_attr_value_map.tt2 +++ /dev/null @@ -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 %] diff --git a/Open-ILS/web/templates/default/conify/global/booking/resource.tt2 b/Open-ILS/web/templates/default/conify/global/booking/resource.tt2 index 7742bc80f3..e5ae269401 100644 --- a/Open-ILS/web/templates/default/conify/global/booking/resource.tt2 +++ b/Open-ILS/web/templates/default/conify/global/booking/resource.tt2 @@ -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> diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js index c5bcbdf6af..e394d1f898 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js +++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js @@ -116,7 +116,6 @@ main.menu.prototype = { ); } - var cmd_map = { 'cmd_broken' : [ ['oncommand'], @@ -646,14 +645,6 @@ main.menu.prototype = { ['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 @@ main.menu.prototype = { ['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() { diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul index 0f00d91c35..72beb44352 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul +++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul @@ -88,6 +88,10 @@ <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"/> @@ -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> diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul index 0358de29cf..4723b60505 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul +++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul @@ -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" /> <!-- diff --git a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties index 46b433a2dd..25b58dc816 100644 --- a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties +++ b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties @@ -232,6 +232,8 @@ menu.cmd_acq_view_provider.tab=Providers 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 diff --git a/Open-ILS/xul/staff_client/server/cat/copy_browser.js b/Open-ILS/xul/staff_client/server/cat/copy_browser.js index 1a5b3b92d6..48bfefa5d2 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_browser.js +++ b/Open-ILS/xul/staff_client/server/cat/copy_browser.js @@ -145,63 +145,50 @@ cat.copy_browser.prototype = { '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(); - } - ); - // 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 && results["brsrc"]) { + cat.util.edit_new_brsrc(results["brsrc"]); } - 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_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"; } + ); + var results = cat.util.make_bookable( + util.functional.map_list( + list, function (o) { + return obj.map_acp[o].id(); + } + ) + ); + if (results) { + cat.util.edit_new_bresv(results); } } ], diff --git a/Open-ILS/xul/staff_client/server/cat/copy_browser.xul b/Open-ILS/xul/staff_client/server/cat/copy_browser.xul index 0f09ad51e3..09a961d098 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_browser.xul +++ b/Open-ILS/xul/staff_client/server/cat/copy_browser.xul @@ -73,6 +73,7 @@ vim:noet:sw=4:ts=4: <command id="cmd_broken" /> <command id="sel_copy_details"/> <command id="cmd_create_brt"/> + <!-- <command id="cmd_book_item_now"/> --> <command id="sel_patron"/> <command id="sel_clip" /> <command id="cmd_clear" /> @@ -104,6 +105,7 @@ vim:noet:sw=4:ts=4: <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;"/> diff --git a/Open-ILS/xul/staff_client/server/cat/util.js b/Open-ILS/xul/staff_client/server/cat/util.js index a32b9946b0..fbcd3d7794 100644 --- a/Open-ILS/xul/staff_client/server/cat/util.js +++ b/Open-ILS/xul/staff_client/server/cat/util.js @@ -517,4 +517,77 @@ cat.util.fast_item_add = function(doc_id,cn_label,cp_barcode) { 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'); diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties b/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties index 13dc92ae29..3ce3d7416d 100644 --- a/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties +++ b/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties @@ -12,10 +12,10 @@ staff.cat.bib_brief.noncat.alert=Item not cataloged. 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