From 3314498166392a7c654c03d47c3cfc6da3d4fdb9 Mon Sep 17 00:00:00 2001 From: senator Date: Tue, 29 Jun 2010 17:53:39 +0000 Subject: [PATCH] Booking: begin forward-porting code from rel_1_6. Booking (regrettably) was largely written directly against a 1.6 environment instead of written for trunk and backported. So now we have booking code in rel_1_6 and rel_1_6_1 that works, but that needs to be cleanly merged with trunk. There has been a lot of drift, and this won't be easy. Here is the first step (some of the easy stuff). These files have been updated wholesale with their contents from rel_1_6, since they don't affect anything other than booking itself. Just to be clear: this commit does not complete the booking foward-port. The booking module did not work in trunk before this commit, and it does not work after this commit. For the moment, booking only works in the rel_1_6 branch, in the rel_1_6_1 branch, and in 1.6.1.* releases. It still does not work in trunk, and it will take a few more hairy commits to get things in sync. Once that's finally done, any future Booking code can be written the Right Way (in trunk) and *back*ported thence to whatever other branches as needed. git-svn-id: svn://svn.open-ils.org/ILS/trunk@16827 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/fm_IDL.xml | 8 +- Open-ILS/src/extras/ils_events.xml | 3 + .../src/perlmods/OpenILS/Application/Booking.pm | 331 ++++++++++++++++----- .../web/js/dojo/openils/booking/nls/capture.js | 15 +- .../dojo/openils/booking/nls/pickup_and_return.js | 14 +- .../web/js/dojo/openils/booking/nls/pull_list.js | 6 +- .../web/js/dojo/openils/booking/nls/reservation.js | 26 +- Open-ILS/web/js/ui/default/booking/capture.js | 215 ++++++++----- Open-ILS/web/js/ui/default/booking/common.js | 7 + Open-ILS/web/js/ui/default/booking/populator.js | 67 ++++- Open-ILS/web/js/ui/default/booking/pull_list.js | 39 ++- Open-ILS/web/js/ui/default/booking/reservation.js | 108 +++++-- Open-ILS/web/templates/default/booking/capture.tt2 | 6 +- .../web/templates/default/booking/pull_list.tt2 | 7 +- .../default/conify/global/booking/resource.tt2 | 35 +-- .../conify/global/booking/resource_attr.tt2 | 32 +- .../conify/global/booking/resource_attr_map.tt2 | 35 +-- .../conify/global/booking/resource_attr_value.tt2 | 33 +- .../conify/global/booking/resource_type.tt2 | 33 +- 19 files changed, 678 insertions(+), 342 deletions(-) diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 0545152363..a722609e94 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2645,7 +2645,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + @@ -2678,7 +2678,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + @@ -2710,7 +2710,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + @@ -2736,7 +2736,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + diff --git a/Open-ILS/src/extras/ils_events.xml b/Open-ILS/src/extras/ils_events.xml index 44dcdd7f63..8bdf422d3a 100644 --- a/Open-ILS/src/extras/ils_events.xml +++ b/Open-ILS/src/extras/ils_events.xml @@ -882,6 +882,9 @@ Provided parameters describe unacceptable reservation. + + Both a hold and a reservation exist for t + diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm b/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm index 142fb23f10..97a1941091 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm @@ -7,6 +7,7 @@ use POSIX qw/strftime/; use OpenILS::Application; use base qw/OpenILS::Application/; +use OpenSRF::Utils qw/:datetime/; use OpenILS::Utils::CStoreEditor qw/:funcs/; use OpenILS::Utils::Fieldmapper; use OpenILS::Application::AppUtils; @@ -319,7 +320,7 @@ sub resource_list_by_attrs { return undef unless ($filters->{type} || $filters->{attribute_values}); my $query = { - "select" => {brsrc => ["id"]}, + "select" => {brsrc => [qw/id owner/], brt => ["elbow_room"]}, "from" => {brsrc => {"brt" => {}}}, "where" => {}, "distinct" => 1 @@ -373,6 +374,7 @@ sub resource_list_by_attrs { "end_time" ), {"cancel_time" => undef}, + {"return_time" => undef}, {"current_resource" => {"=" => {"+brsrc" => "id"}}} ]}, }} @@ -408,21 +410,58 @@ sub resource_list_by_attrs { } my $cstore = OpenSRF::AppSession->connect('open-ils.cstore'); - my $rows = $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; - return @$rows ? [map { $_->{id} } @$rows] : []; + return [] if not @$rows; + + if ($filters->{"pickup_lib"} && $filters->{"available"}) { + my @new_rows = (); + my $general_elbow_room = $U->ou_ancestor_setting_value( + $filters->{"pickup_lib"}, + "circ.booking_reservation.default_elbow_room" + ) || '0 seconds'; + my $would_start = $filters->{"available"}->[0]; + my $dt_parser = new DateTime::Format::ISO8601; + + $logger->info( + "general_elbow_room: '$general_elbow_room', " . + "would_start: '$would_start'" + ); + + # Here, elbow_room will double as required transit time padding. + foreach (@$rows) { + my $elbow_room = $_->{"elbow_room"} || $general_elbow_room; + if ($_->{"owner"} != $filters->{"pickup_lib"}) { + (my $ws = $would_start) =~ s/ /T/; + push @new_rows, $_ if DateTime->compare( + $dt_parser->parse_datetime($ws), + DateTime->now( + "time_zone" => DateTime::TimeZone->new( + "name" => "local" + ) + )->add(seconds => interval_to_seconds($elbow_room)) + ) >= 0; + } else { + push @new_rows, $_; + } + } + return [map { $_->{id} } @new_rows]; + } else { + return [map { $_->{id} } @$rows]; + } } __PACKAGE__->register_method( method => "resource_list_by_attrs", api_name => "open-ils.booking.resources.filtered_id_list", - argc => 3, + argc => 2, signature=> { params => [ {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." }, }, @@ -484,8 +523,15 @@ sub reservation_list_by_filters { $query->{where}->{target_resource_type} = $filters->{type}; } + $query->{where}->{"-and"} = []; if ($filters->{resource}) { - $query->{where}->{target_resource} = $filters->{resource}; +# $query->{where}->{target_resource} = $filters->{resource}; + push @{$query->{where}->{"-and"}}, { + "-or" => { + "target_resource" => $filters->{resource}, + "current_resource" => $filters->{resource} + } + }; } if ($filters->{attribute_values}) { @@ -504,13 +550,21 @@ sub reservation_list_by_filters { } if ($filters->{search_start} || $filters->{search_end}) { - $query->{where}->{'-or'} = {}; + my $or = {}; - $query->{where}->{'-or'}->{start_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] } - if ($filters->{search_start}); + $or->{start_time} = + {'between' => [ $filters->{search_start}, $filters->{search_end}]} + if $filters->{search_start}; + + $or->{end_time} = + {'between' =>[$filters->{search_start}, $filters->{search_end}]} + if $filters->{search_end}; + + push @{$query->{where}->{"-and"}}, {"-or" => $or}; + } - $query->{where}->{'-or'}->{end_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] } - if ($filters->{search_end}); + if (not scalar @{$query->{"where"}->{"-and"}}) { + delete $query->{"where"}->{"-and"}; } my $cstore = OpenSRF::AppSession->connect('open-ils.cstore'); @@ -541,11 +595,12 @@ sub reservation_list_by_filters { __PACKAGE__->register_method( method => "reservation_list_by_filters", api_name => "open-ils.booking.reservations.filtered_id_list", - argc => 2, + argc => 3, signature=> { params => [ {type => 'string', desc => 'Authentication token'}, - {type => 'object', desc => 'Filter object -- see notes for details'} + {type => "object", desc => "Filter object: see notes for details"}, + {type => "bool", desc => "Return whole object instead of ID? (default false)"} ], return => { desc => "An array of bresv ids matching the requested filters." }, }, @@ -572,7 +627,7 @@ NOTES sub naive_ts_string {strftime("%F %T", localtime($_[0] || time));} sub naive_start_of_day {strftime("%F", localtime($_[0] || time))." 00:00:00";} -# Return a list of bresv or an ilsevent on failure. +# Return a map of bresv or an ilsevent on failure. sub get_uncaptured_bresv_for_brsrc { my ($e, $o) = @_; # o's keys (all optional): owning_lib, barcode, range @@ -630,6 +685,10 @@ sub get_uncaptured_bresv_for_brsrc { "-and" => [ {"current_resource" => "PLACEHOLDER"}, {"start_time" => "PLACEHOLDER"}, + {"capture_time" => undef}, + {"cancel_time" => undef}, + {"return_time" => undef}, + {"pickup_time" => undef} ] } }; @@ -727,6 +786,75 @@ __PACKAGE__->register_method( ); +sub could_capture { + my ($self, $client, $auth, $barcode) = @_; + + my $e = new_editor("authtoken" => $auth); + return $e->die_event unless $e->checkauth; + return $e->die_event unless $e->allowed("CAPTURE_RESERVATION"); + + my $dt_parser = new DateTime::Format::ISO8601; + my $now = now DateTime; # sic + my $res = get_uncaptured_bresv_for_brsrc($e, {"barcode" => $barcode}); + + if ($res and keys %$res) { + my $id; + while ((undef, $id) = each %$res) { + my $bresv = $e->retrieve_booking_reservation([ + $id, { + "flesh" => 1, "flesh_fields" => { + "bresv" => [qw( + usr target_resource_type + target_resource current_resource + )] + } + } + ]); + my $elbow_room = interval_to_seconds( + $bresv->target_resource_type->elbow_room || + $U->ou_ancestor_setting_value( + $bresv->pickup_lib, + "circ.booking_reservation.default_elbow_room" + ) || + "0 seconds" + ); + + unless ($elbow_room) { + $client->respond($bresv); + } else { + my $start_time = $dt_parser->parse_datetime( + clense_ISO8601($bresv->start_time) + ); + + if ($now >= $start_time->subtract("seconds" => $elbow_room)) { + $client->respond($bresv); + } else { + $logger->info( + "not within elbow room: $elbow_room, " . + "else would have returned bresv " . $bresv->id + ); + } + } + } + } + $e->disconnect; + undef; +} +__PACKAGE__->register_method( + method => "could_capture", + api_name => "open-ils.booking.reservations.could_capture", + argc => 2, + streaming=> 1, + signature=> { + params => [ + {type => "string", desc => "Authentication token"}, + {type => "string", desc => "Resource barcode"} + ], + return => {desc => "One or zero reservations; event on error."} + } +); + + sub get_copy_fleshed_just_right { my ($self, $client, $auth, $barcode) = @_; @@ -771,7 +899,10 @@ sub best_bresv_candidate { my ($e, $id_list) = @_; # This will almost always be the case. - return $id_list->[0] if @$id_list == 1; + if (@$id_list == 1) { + $logger->info("best_bresv_candidate (only) " . $id_list->[0]); + return $id_list->[0]; + } my @here = (); my $this_ou = $e->requestor->ws_ou; @@ -791,40 +922,48 @@ sub best_bresv_candidate { push @here, $_->{"id"} if $_->{"pickup_lib"} == $this_ou; } + my $result; if (@here > 0) { - return pop @here if @here == 1; - return (sort @here)[0]; + $result = @here == 1 ? pop @here : (sort @here)[0]; } else { - return (sort @$id_list)[0]; + $result = (sort @$id_list)[0]; } + $logger->info( + "best_bresv_candidate from " . join(",", @$id_list) . ": $result" + ); + return $result; } sub capture_resource_for_reservation { - my ($self, $client, $auth, $barcode) = @_; + my ($self, $client, $auth, $barcode, $no_update_copy) = @_; - my $e = new_editor(xact => 1, authtoken => $auth); + my $e = new_editor(authtoken => $auth); return $e->die_event unless $e->checkauth; return $e->die_event unless $e->allowed("CAPTURE_RESERVATION"); my $uncaptured = get_uncaptured_bresv_for_brsrc( $e, {"barcode" => $barcode} ); - $e->disconnect; if (keys %$uncaptured) { # Note this will only capture one reservation at a time, even in # cases with overbooking (multiple "soonest" bresv's on a resource). - my $key = (sort(keys %$uncaptured))[0]; + my $bresv = best_bresv_candidate( + $e, $uncaptured->{ + (sort(keys %$uncaptured))[0] + } + ); + $e->disconnect; return capture_reservation( - $self, $client, $auth, best_bresv_candidate($e, $uncaptured->{$key}) + $self, $client, $auth, $bresv, $no_update_copy ); } else { return new OpenILS::Event( "RESERVATION_NOT_FOUND", - desc => "No capturable reservation found pertaining " . + "desc" => "No capturable reservation found pertaining " . "to a resource with barcode $barcode", - payload => {fail_cause => 'no-reservation', captured => 0} + "payload" => {"fail_cause" => "no-reservation", "captured" => 0} ); } } @@ -836,7 +975,7 @@ __PACKAGE__->register_method( params => [ {type => "string", desc => "Authentication token"}, {type => "string", desc => "Barcode of booked & targeted resource"}, - {type => "int", desc => "Pickup library (default to client ws_ou)"}, + {type => "number", desc => "(optional) 1 to not update copy"} ], return => { desc => "An OpenILS event describing the capture outcome" } } @@ -844,89 +983,125 @@ __PACKAGE__->register_method( sub capture_reservation { - my ($self, $client, $auth, $res_id) = @_; + my ($self, $client, $auth, $res_id, $no_update_copy) = @_; - my $e = new_editor(xact => 1, authtoken => $auth); - return $e->event unless $e->checkauth; - return $e->event unless $e->allowed('CAPTURE_RESERVATION'); + my $e = new_editor("xact" => 1, "authtoken" => $auth); + return $e->die_event unless $e->checkauth; + return $e->die_event unless $e->allowed('CAPTURE_RESERVATION'); my $here = $e->requestor->ws_ou; my $reservation = $e->retrieve_booking_reservation([ $res_id, { - flesh => 2, - flesh_fields => {"bresv" => ["usr"], "au" => ["card"]} + "flesh" => 2, "flesh_fields" => { + "bresv" => [qw/usr current_resource type/], + "au" => ["card"], + "brsrc" => ["type"] + } } ]); - return OpenILS::Event->new('RESERVATION_NOT_FOUND') unless $reservation; - - return OpenILS::Event->new('RESERVATION_CAPTURE_FAILED', payload => { captured => 0, fail_cause => 'no-resource' }) - if (!$reservation->current_resource); # no resource - return OpenILS::Event->new('RESERVATION_CAPTURE_FAILED', payload => { captured => 0, fail_cause => 'cancelled' }) - if ($reservation->cancel_time); # canceled + return new OpenILS::Event("RESERVATION_NOT_FOUND") unless $reservation; + return new OpenILS::Event( + "RESERVATION_CAPTURE_FAILED", + payload => {"captured" => 0, "fail_cause" => "no-resource"} + ) unless $reservation->current_resource; - my $resource = $e->retrieve_booking_resource( $reservation->current_resource ); - my $type = $e->retrieve_booking_resource_type( $resource->type ); + return new OpenILS::Event( + "RESERVATION_CAPTURE_FAILED", + "payload" => {"captured" => 0, "fail_cause" => "cancelled"} + ) if $reservation->cancel_time; - $reservation->capture_staff( $e->requestor->id ); - $reservation->capture_time( 'now' ); + $reservation->capture_staff($e->requestor->id); + $reservation->capture_time("now"); - my $reservation_id = undef; - return $e->event unless ( $e->update_booking_reservation( $reservation ) and $reservation_id = $e->data ); + $e->update_booking_reservation($reservation) or return $e->die_event; - $reservation->id($reservation_id); + my $ret = {"captured" => 1, "reservation" => $reservation}; - my $ret = { captured => 1, reservation => $reservation }; + my $search_acp_like_this = [ + { + "barcode" => $reservation->current_resource->barcode, + "deleted" => "f" + }, + {"flesh" => 1, "flesh_fields" => {"acp" => ["call_number"]}} + ]; if ($here != $reservation->pickup_lib) { - return OpenILS::Event->new('RESERVATION_CAPTURE_FAILED', payload => { captured => 0, fail_cause => 'not-transferable' }) - if (!$U->is_true($type->transferable)); # non-transferable resource + $logger->info("resource isn't at the reservation's pickup lib..."); + return new OpenILS::Event( + "RESERVATION_CAPTURE_FAILED", + "payload" => {"captured" => 0, "fail_cause" => "not-transferable"} + ) unless $U->is_true( + $reservation->current_resource->type->transferable + ); # need to transit the item ... is it already in transit? - my $transit = $e->search_action_reservation_transit_copy( { reservation => $res_id, dest_recv_time => undef } )->[0]; + my $transit = $e->search_action_reservation_transit_copy( + {"reservation" => $res_id, "dest_recv_time" => undef} + )->[0]; if (!$transit) { # not yet in transit $transit = new Fieldmapper::action::reservation_transit_copy; $transit->reservation($reservation->id); - $transit->target_copy($resource->id); + $transit->target_copy($reservation->current_resource->id); $transit->copy_status(15); - $transit->source_send_time('now'); + $transit->source_send_time("now"); $transit->source($here); $transit->dest($reservation->pickup_lib); - $e->create_action_reservation_transit_copy( $transit ); + $e->create_action_reservation_transit_copy($transit); - if ($U->is_true($type->catalog_item)) { - my $copy = $e->search_asset_copy( { barcode => $resource->barcode, deleted => 'f' } )->[0]; + if ($U->is_true( + $reservation->current_resource->type->catalog_item + )) { + my $copy = $e->search_asset_copy($search_acp_like_this)->[0]; if ($copy) { return new OpenILS::Event( "OPEN_CIRCULATION_EXISTS", - payload => { captured => 0, copy => $copy } - ) if $copy->status == 1; - $copy->status(6); - $e->update_asset_copy( $copy ); - $$ret{catalog_item} = $copy; # $e->data is just id (int) + "payload" => {"captured" => 0, "copy" => $copy} + ) if $copy->status == 1 and not $no_update_copy; + + $ret->{"mvr"} = get_mvr($copy->call_number->record); + if ($no_update_copy) { + $ret->{"new_copy_status"} = 6; + } else { + $copy->status(6); + $e->update_asset_copy($copy) or return $e->die_event; + } } } } - $$ret{transit} = $transit; - } elsif ($U->is_true($type->catalog_item)) { - my $copy = $e->search_asset_copy( { barcode => $resource->barcode, deleted => 'f' } )->[0]; + $ret->{"transit"} = $transit; + } elsif ($U->is_true($reservation->current_resource->type->catalog_item)) { + $logger->info("resource is a catalog item..."); + my $copy = $e->search_asset_copy($search_acp_like_this)->[0]; if ($copy) { - return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS', payload => { captured => 0, copy => $copy }) if ($copy->status == 1); - $copy->status(15); - $e->update_asset_copy( $copy ); - $$ret{catalog_item} = $copy; # $e->data is just id (int) + return new OpenILS::Event( + "OPEN_CIRCULATION_EXISTS", + "payload" => {"captured" => 0, "copy" => $copy} + ) if $copy->status == 1 and not $no_update_copy; + + $ret->{"mvr"} = get_mvr($copy->call_number->record); + if ($no_update_copy) { + $ret->{"new_copy_status"} = 15; + } else { + $copy->status(15); + $e->update_asset_copy($copy) or return $e->die_event; + } } } - $e->commit; + $e->commit or return $e->die_event; - return OpenILS::Event->new('SUCCESS', payload => $ret); + # XXX I'm not sure whether these last two elements of the payload + # actually get used anywhere. + $ret->{"resource"} = $reservation->current_resource; + $ret->{"type"} = $reservation->current_resource->type; + return new OpenILS::Event("SUCCESS", "payload" => $ret); } __PACKAGE__->register_method( method => "capture_reservation", @@ -960,10 +1135,18 @@ sub cancel_reservation { ]); return $e->die_event if not $bresv_list; + my @results = (); my $circ = OpenSRF::AppSession->connect("open-ils.circ") or return $e->die_event; - my @results = (); foreach my $bresv (@$bresv_list) { + $bresv->cancel_time("now"); + $e->update_booking_reservation($bresv) or do { + $circ->disconnect; + return $e->die_event; + }; + $e->xact_commit; + $e->xact_begin; + if ( $bresv->target_resource_type->catalog_item == "t" && $bresv->current_resource @@ -975,16 +1158,10 @@ sub cancel_reservation { "noop" => 1} )->gather(1)->{"textcode"}); } - $bresv->cancel_time("now"); - $e->update_booking_reservation($bresv) or do { - $circ->disconnect; - return $e->die_event; - }; - push @results, $bresv->id; } - $e->commit; + $e->disconnect; $circ->disconnect; return \@results; @@ -1094,7 +1271,7 @@ sub get_bresv_by_returnable_resource_barcode { my $e = new_editor(xact => 1, authtoken => $auth); return $e->die_event unless $e->checkauth; return $e->die_event unless $e->allowed("VIEW_USER"); - return $e->die_event unless $e->allowed("ADMIN_BOOKING_RESERVATION"); +# return $e->die_event unless $e->allowed("ADMIN_BOOKING_RESERVATION"); my $rows = $e->json_query({ "select" => {"bresv" => ["id"]}, diff --git a/Open-ILS/web/js/dojo/openils/booking/nls/capture.js b/Open-ILS/web/js/dojo/openils/booking/nls/capture.js index e59b8c1965..b868babc5f 100644 --- a/Open-ILS/web/js/dojo/openils/booking/nls/capture.js +++ b/Open-ILS/web/js/dojo/openils/booking/nls/capture.js @@ -9,10 +9,23 @@ "CAPTURE_BRESV_DATES": "Reservation time:", "CAPTURE_BRESV_BRSRC": "Resource barcode:", "CAPTURE_BRESV_PICKUP_LIB": "Pickup library:", - "CAPTURE_BRESV_PATRON_BARCODE": "Patron barcode:", "CAPTURE_CAUSES_TRANSIT": "This item is now in transit!", "CAPTURE_TRANSIT_SOURCE": "From:", "CAPTURE_TRANSIT_DEST": "To:", + "BARCODE": "Barcode", + "TITLE": "Title", + "AUTHOR": "Author", + "RESERVED": "Reserved for patron", + "REQUEST": "Request time", + "DURATION": "Reserved from", + "SLIP_DATE": "Slip date", + "PRINTED_BY": "Printed by", + "AT": "at", + "PRINT": "Print", + "PRINT_ACCESSKEY": "P", + "TRANSIT": "*** TRANSIT ***", + "RESERVATION_SHELF": "RESERVATION SHELF", + "NEEDS_ROUTED_TO": "This item need to be routed to", "AUTO_capture_heading": "Capture Reserved Resources", "AUTO_resource_barcode": "Enter barcode:", diff --git a/Open-ILS/web/js/dojo/openils/booking/nls/pickup_and_return.js b/Open-ILS/web/js/dojo/openils/booking/nls/pickup_and_return.js index 1972f02c36..f6df5dd567 100644 --- a/Open-ILS/web/js/dojo/openils/booking/nls/pickup_and_return.js +++ b/Open-ILS/web/js/dojo/openils/booking/nls/pickup_and_return.js @@ -1,9 +1,7 @@ { "NO_PATRON_BARCODE": "Please enter a patron barcode.", - "RESERVATIONS_NO_RESPONSE": - "No response from server when asking for reservations.", - "RESERVATIONS_ERROR": - "Error communicating with server (asking for reservations):", + "RESERVATIONS_NO_RESPONSE": "No response from server when asking for reservations.", + "RESERVATIONS_ERROR": "Error communicating with server (asking for reservations):", "PICKUP_NO_RESPONSE": "No response from server when attempting pickup.", "PICKUP_ERROR": "Error communicating with server (attempting pickup):", "RETURN_NO_RESPONSE": "No response from server when attempting return.", @@ -13,7 +11,7 @@ "NO_SUCH_RETURNABLE_RESOURCE": "No such returnable resource.", "RETURNABLE_RESOURCE_ERROR": "Error looking up returnable resource:", "NOTICE_CHANGE_OF_PATRON": - "Note that the resource scanned was out on reservation to different\npatron than the last resource you scanned. If this is not\nexpected, stop to examine outstanding reservations for your patron\nor on the resource.", + "Note that the resource scanned was out on reservation to a different\npatron than the last resource you scanned. If this is not\nexpected, stop to examine outstanding reservations for your patron\nor on the resource.", "AUTO_h1": "Reservations Pickup", "AUTO_return_h1": "Reservations Return", @@ -22,8 +20,7 @@ "AUTO_in_bresv": "Patron has returned these resources today:", "AUTO_ready_bresv": "Patron has these reservations ready for pickup:", "AUTO_out_bresv": "Patron currently has these reservations out:", - "AUTO_no_ready_bresv": - "Patron has no reservations ready for pickup at this time.", + "AUTO_no_ready_bresv": "Patron has no reservations ready for pickup at this time.", "AUTO_no_out_bresv": "Patron has no more reservations out at this time.", "AUTO_no_in_bresv": "Patron has not returned any resources today.", "AUTO_patron": "Patron", @@ -31,5 +28,6 @@ "AUTO_ATTR_VALUE_go": "Go", "AUTO_ATTR_VALUE_reset": "Clear / New Patron", "AUTO_ATTR_VALUE_pickup": "Pick up", - "AUTO_ATTR_VALUE_return": "Return" + "AUTO_ATTR_VALUE_return": "Return", + "ADDRESS": "${0}\n${1}\n${2}, ${3} ${4}" } diff --git a/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js b/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js index e82700116c..0d7e1d163b 100644 --- a/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js +++ b/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js @@ -4,8 +4,10 @@ "COPY_LOOKUP_NO_RESPONSE": "No response looking up copies by barcode", "COPY_LOOKUP_ERROR": "Error looking up copies by barcode: ", "COPY_MISSING": "Unexpected error: No information for copy: ", + "AT": "at", + "FOR": "for", - "AUTO_no_results": "No results", + "AUTO_no_results": "No results.", "AUTO_owning_lib_selector": "See pull list for library:", "AUTO_pull_list_title": "Booking Pull List", "AUTO_interval_in_days": "Generate list for this many days hence: ", @@ -14,7 +16,7 @@ "AUTO_th_barcode": "Barcode", "AUTO_th_call_number": "Call number", "AUTO_th_copy_location": "Copy location", - "AUTO_th_copy_number": "Copy number", + "AUTO_th_pickup_lib": "Pickup library", "AUTO_th_resv_details": "Reservation details", "AUTO_ATTR_VALUE_print": "Print" } diff --git a/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js b/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js index 5a80d16154..8c39198f5e 100644 --- a/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js +++ b/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js @@ -2,13 +2,14 @@ "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.", + "SELECT_A_BRSRC_THEN": "You have clicked 'Reserve Selected', but nothing is selected!\n\nYou must select a resource from the large box above.\n\n*** If resources that you would select are highlighted in RED, ***\nthese items are not available during the requested time; if\npossible, choose another resource or change the reservation time.", "CREATE_BRESV_LOCAL_ERROR": "Exception trying to create reservation: ", "CREATE_BRESV_SERVER_ERROR": "Server error trying to create reservation: ", "CREATE_BRESV_SERVER_NO_RESPONSE": "No response from server after trying to create reservation.", - "CREATE_BRESV_OK_MISSING_TARGET": "Created ${0} reservation(s), but ${1} of these couldn't target any resources.\n\nThis means that it won't be possible to fulfill some of these\nreservations until a suitable resource becomes available.", - "CREATE_BRESV_OK": "Created ${0} reservation.", - "CREATE_BRESV_OK_PLURAL": "Created ${0} reservations", + "CREATE_BRESV_OK_MISSING_TARGET": "Created ${0} reservation(s), but ${1} of these could not target any resources.\n\nThis means that it won't be possible to fulfill some of these\nreservations until a suitable resource becomes available.", + "CREATE_BRESV_OK_MISSING_TARGET_BLOCKED_BY_CIRC": "The desired reservation(s) are blocked by circulation(s) with the following due date(s):\n${0}", + "CREATE_BRESV_OK_MISSING_TARGET_WILL_CANCEL": "Since the requested resource could not be targeted, this\nreservation will now be canceled.", + "CREATE_BRESV_OK": "Created ${0} reservation(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: ", @@ -19,20 +20,23 @@ "HERE_ARE_EXISTING_BRESV": "Existing reservations for", "NO_EXISTING_BRESV": "This user has no existing reservations at this time.", "NO_USABLE_BRSRC": "No reservable resources. Adjust start and end time\nuntil a resource is available for reservation.", - "CXL_BRESV_SUCCESS": "Canceled ${0} reservation.", - "CXL_BRESV_SUCCESS_PLURAL": "Canceled ${0} reservations", + "CXL_BRESV_SUCCESS": "Canceled ${0} reservation(s)", "CXL_BRESV_FAILURE": "Error canceling reservations; server silent.", "CXL_BRESV_FAILURE2": "Error canceling reservations:\n", - "CXL_BRESV_SELECT_SOMETHING": "You have not selected any reservations to cancel.", + "CXL_BRESV_SELECT_SOMETHING": + "You have not selected any reservations to cancel.", "NEED_EXACTLY_ONE_BRT_PASSED_IN": "Can't book multiple resource types at once", "COULD_NOT_RETRIEVE_BRT_PASSED_IN": "Error retrieving booking resource type", "INVALID_TS_RANGE": "You must choose a valid start and end time for the reservation.", "BRSRC_NOT_FOUND": "Could not locate that resource.", "BRSRC_RETRIVE_ERROR": "Error retrieving resource: ", - "ON_FLY_NO_RESPONSE": "No response from server attempting to make item a bookable resource.", - "ON_FLY_ERROR": "Error attempting to make item a bookable resource:", + "ON_FLY_NO_RESPONSE": + "No response from server attempting to make item a bookable resource.", + "ON_FLY_ERROR": + "Error attempting to make item a bookable resource:", "ANY": "ANY", - + "ERROR_FETCHING_AOUS": + "Could not retrieve organizational unit settings.\nThis is a non-fatal error, but you may wish to\ncontact your system administrator.", "AUTO_choose_a_brt": "Choose a Bookable Resource Type", "AUTO_i_need_this_resource": "I need this resource...", "AUTO_starting_at": "Between", @@ -43,7 +47,7 @@ "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_ATTR_VALUE_button_cancel_existing": "Cancel selected", "AUTO_bresv_grid_type": "Type", "AUTO_bresv_grid_resource": "Resource", "AUTO_bresv_grid_start_time": "Start time", diff --git a/Open-ILS/web/js/ui/default/booking/capture.js b/Open-ILS/web/js/ui/default/booking/capture.js index 5502b5b3b2..5aa0ffa1db 100644 --- a/Open-ILS/web/js/ui/default/booking/capture.js +++ b/Open-ILS/web/js/ui/default/booking/capture.js @@ -8,14 +8,19 @@ const CAPTURE_UNKNOWN = 2; var localeStrings = dojo.i18n.getLocalization("openils.booking", "capture"); -function CaptureDisplay(element) { this.element = element; } +function CaptureDisplay(control_holder, data_holder) { + this.control_holder = control_holder; + this.data_holder = data_holder; +} CaptureDisplay.prototype.no_payload = function() { - this.element.appendChild(document.createTextNode(localeStrings.NO_PAYLOAD)); + this.data_holder.appendChild( + document.createTextNode(localeStrings.NO_PAYLOAD) + ); }; CaptureDisplay.prototype.dump = function(payload) { var div = document.createElement("div"); div.appendChild(document.createTextNode(localeStrings.HERES_WHAT_WE_KNOW)); - this.element.appendChild(div); + this.data_holder.appendChild(div); var ul = document.createElement("ul"); for (var k in payload) { @@ -23,90 +28,149 @@ CaptureDisplay.prototype.dump = function(payload) { li.appendChild(document.createTextNode(k + ": " + payload[k])); ul.appendChild(li); } - this.element.appendChild(ul); + this.data_holder.appendChild(ul); }; -CaptureDisplay.prototype.generate_transit_display = function(payload) { - var super_div = document.createElement("div"); - var div; - - div = document.createElement("div"); - div.appendChild(document.createTextNode( - localeStrings.CAPTURE_CAUSES_TRANSIT - )); - div.setAttribute("class", "transit_notice"); - super_div.appendChild(div); - - div = document.createElement("div"); +CaptureDisplay.prototype._generate_barcode_line = function(payload) { + var div = document.createElement("div"); div.appendChild(document.createTextNode( - localeStrings.CAPTURE_TRANSIT_SOURCE + " " + - fieldmapper.aou.findOrgUnit(payload.transit.source()).shortname() + localeStrings.BARCODE + ": " + payload.resource.barcode() )); - super_div.appendChild(div); - - div = document.createElement("div"); + return div; +}; +CaptureDisplay.prototype._generate_title_line = function(payload) { + var div = document.createElement("div"); div.appendChild(document.createTextNode( - localeStrings.CAPTURE_TRANSIT_DEST + " " + - fieldmapper.aou.findOrgUnit(payload.transit.dest()).shortname() + localeStrings.TITLE + ": " + + (payload.mvr ? payload.mvr.title() : payload.type.name()) )); - super_div.appendChild(div); - - return super_div; + return div; }; -CaptureDisplay.prototype.display_with_transit_info = function(payload) { - var div; - - div = document.createElement("div"); - div.appendChild(document.createTextNode(localeStrings.CAPTURE_INFO)); - div.setAttribute("class", "capture_info"); - this.element.appendChild(div); - - if (payload.catalog_item) { - div = document.createElement("div"); +CaptureDisplay.prototype._generate_author_line = function(payload) { + var div = document.createElement("div"); + if (payload.mvr) { div.appendChild(document.createTextNode( - localeStrings.CAPTURE_BRESV_BRSRC + " " + - payload.catalog_item.barcode() + localeStrings.AUTHOR + ": " + payload.mvr.author() )); - this.element.appendChild(div); } - - div = document.createElement("div"); - div.appendChild(document.createTextNode( - localeStrings.CAPTURE_BRESV_DATES + " " + - humanize_timestamp_string(payload.reservation.start_time()) + " - " + - humanize_timestamp_string(payload.reservation.end_time()) - )); - this.element.appendChild(div); - - div = document.createElement("div"); - div.appendChild(document.createTextNode( - localeStrings.CAPTURE_BRESV_PICKUP_LIB + " " + - fieldmapper.aou.findOrgUnit( - payload.reservation.pickup_lib() - ).shortname() + return div; +}; +CaptureDisplay.prototype._generate_transit_notice = function(payload) { + var div = document.createElement("div"); + if (payload.transit) { + div.setAttribute("class", "transit_notice"); + div.appendChild(document.createTextNode(localeStrings.TRANSIT)); + } + return div; +}; +CaptureDisplay.prototype._generate_route_line = function(payload) { + var div = document.createElement("div"); + var strong = document.createElement("strong"); + strong.appendChild(document.createTextNode( + (payload.transit ? + fieldmapper.aou.findOrgUnit(payload.transit.dest()).shortname() : + localeStrings.RESERVATION_SHELF) + ":" )); - this.element.appendChild(div); - - div = document.createElement("div"); div.appendChild(document.createTextNode( - localeStrings.CAPTURE_BRESV_PATRON_BARCODE + " " + - payload.reservation.usr().card().barcode() + localeStrings.NEEDS_ROUTED_TO + " " )); - this.element.appendChild(div); - - if (payload.transit) { - this.element.appendChild(this.generate_transit_display(payload)); - } + div.appendChild(strong); + return div; +}; +CaptureDisplay.prototype._generate_patron_info = function(payload) { + var p = document.createElement("p"); + p.innerHTML = "" + localeStrings.RESERVED + " " + + formal_name(payload.reservation.usr()) + "
" + + localeStrings.BARCODE + ": " + + payload.reservation.usr().card().barcode(); + return p; +}; +CaptureDisplay.prototype._generate_resv_info = function(payload) { + var p = document.createElement("p"); + p.innerHTML = localeStrings.REQUEST + ": " + + humanize_timestamp_string(payload.reservation.request_time()) + + "
" + + localeStrings.DURATION + ": " + + humanize_timestamp_string(payload.reservation.start_time()) + + " - " + + humanize_timestamp_string(payload.reservation.end_time()); + return p; +}; +CaptureDisplay.prototype._generate_meta_info = function(result) { + var p = document.createElement("p"); + p.innerHTML = localeStrings.SLIP_DATE + ": " + result.servertime + + "
" + localeStrings.PRINTED_BY + " " + + formal_name(openils.User.user) + " " + localeStrings.AT + " " + + fieldmapper.aou.findOrgUnit(openils.User.user.ws_ou()).shortname() + return p; +}; +CaptureDisplay.prototype.display_with_transit_info = function(result) { + var div = document.createElement("div"); + var span = document.createElement("span"); + span.appendChild(document.createTextNode(localeStrings.CAPTURE_INFO)); + span.setAttribute("class", "capture_info"); + this.control_holder.appendChild(span); + + var button = document.createElement("button"); + button.setAttribute("class", "print_slip"); + button.setAttribute("type", "button"); + button.setAttribute("accesskey", localeStrings.PRINT_ACCESSKEY); + button.innerHTML = localeStrings.PRINT; + button.onclick = function() { + try { dojo.byId("printing_iframe").contentWindow.print(); } + catch (E) { alert(E); } /* XXX */ + return false; + }; + this.control_holder.appendChild(button); + + div.appendChild(this._generate_transit_notice(result.payload)); + + var p = document.createElement("p"); + p.appendChild(this._generate_route_line(result.payload)); + p.appendChild(this._generate_barcode_line(result.payload)); + p.appendChild(this._generate_title_line(result.payload)); + p.appendChild(this._generate_author_line(result.payload)); + div.appendChild(p); + + div.appendChild(this._generate_patron_info(result.payload)); + div.appendChild(this._generate_resv_info(result.payload)); + div.appendChild(this._generate_meta_info(result)); + + this._create_iframe(div); +}; +CaptureDisplay.prototype._create_iframe = function(contents) { + var iframe = document.createElement("iframe"); + iframe.setAttribute("name", "printing_iframe"); + iframe.setAttribute("id", "printing_iframe"); + iframe.setAttribute("src", ""); + iframe.setAttribute("width", "100%"); + iframe.setAttribute("height", "400"); /* hardcode 400px? really? */ + + this.data_holder.appendChild(iframe); + + var w = dojo.byId("printing_iframe").contentWindow; + w.document.open(); + w.document.write( + "" + ); + w.document.close(); + w.document.body.appendChild(contents); + /* FIXME if (determine_autoprint_setting_somehow()) w.print(); */ +}; +CaptureDisplay.prototype.clear = function() { + this.control_holder.innerHTML = ""; + this.data_holder.innerHTML = ""; }; -CaptureDisplay.prototype.clear = function() { this.element.innerHTML = ""; }; -CaptureDisplay.prototype.load = function(payload) { +CaptureDisplay.prototype.load = function(result) { try { - this.element.appendChild(document.createElement("hr")); - if (!payload) { + this.control_holder.appendChild(document.createElement("hr")); + if (!result.payload) { this.no_payload(); - } else if (!payload.fail_cause && payload.captured) { - this.display_with_transit_info(payload); + } else if (!result.payload.fail_cause && result.payload.captured) { + this.display_with_transit_info(result); } else { - this.dump(payload); + this.dump(result.payload); } } catch (E) { alert(E); /* XXX */ @@ -136,10 +200,10 @@ function capture() { if (result && result.ilsevent !== undefined) { if (result.payload && result.payload.captured > 0) { - capture_display.load(result.payload); + capture_display.load(result); return CAPTURE_SUCCESS; } else { - capture_display.load(result.payload); + capture_display.load(result); alert(my_ils_error(localeStrings.CAPTURED_NOTHING, result)); return CAPTURE_FAILURE; } @@ -167,8 +231,9 @@ function attempt_capture() { } function my_init() { - init_auto_l10n(document.getElementById("auto_l10n_start_here")); + init_auto_l10n(dojo.byId("auto_l10n_start_here")); capture_display = new CaptureDisplay( - document.getElementById("capture_display") + dojo.byId("capture_info_top"), + dojo.byId("capture_info_bottom") ); } diff --git a/Open-ILS/web/js/ui/default/booking/common.js b/Open-ILS/web/js/ui/default/booking/common.js index cc6f302255..bc5166d577 100644 --- a/Open-ILS/web/js/ui/default/booking/common.js +++ b/Open-ILS/web/js/ui/default/booking/common.js @@ -48,6 +48,13 @@ function humanize_timestamp_string(ts) { var timeparts = parts[1].split("-")[0].split(":"); return parts[0] + " " + timeparts[0] + ":" + timeparts[1]; } +function humanize_timestamp_string2(ts) { + /* For now, this discards time zones, too. */ + var parts = ts.split(" "); + parts[1] = parts[1].replace(/[\-\+]\d+$/, ""); + var timeparts = parts[1].split("-")[0].split(":"); + return parts[0] + " " + timeparts[0] + ":" + timeparts[1]; +} function is_ils_event(e) { return (e.ilsevent != undefined); } function is_ils_actor_card_error(e) { return (e.textcode == "ACTOR_CARD_NOT_FOUND"); diff --git a/Open-ILS/web/js/ui/default/booking/populator.js b/Open-ILS/web/js/ui/default/booking/populator.js index dc0d757d16..9822144731 100644 --- a/Open-ILS/web/js/ui/default/booking/populator.js +++ b/Open-ILS/web/js/ui/default/booking/populator.js @@ -2,6 +2,8 @@ * localization (Dojo/nls) for pickup and return . */ dojo.require("dojo.data.ItemFileReadStore"); +dojo.require("dojo.date.locale"); +dojo.require("openils.PermaCrud"); function Populator(widgets, primary_input) { this.widgets = widgets; @@ -137,7 +139,7 @@ Populator.prototype.return_by_resource = function(barcode) { var r = fieldmapper.standardRequest( ["open-ils.booking", "open-ils.booking.reservations.by_returnable_resource_barcode"], - [xulG.auth.session.key, barcode] + [openils.User.authtoken, barcode] ); if (!r || r.length < 1) { alert(localeStrings.NO_SUCH_RETURNABLE_RESOURCE); @@ -159,7 +161,10 @@ Populator.prototype.return_by_resource = function(barcode) { if (!ret) { alert(localeStrings.RETURN_NO_RESPONSE); } else if (is_ils_event(ret) && ret.textcode != "SUCCESS") { - alert(my_ils_error(localeStrings.RETURN_ERROR, ret)); + if (ret.textcode == "ROUTE_ITEM") + display_transit_slip(ret); + else + alert(my_ils_error(localeStrings.RETURN_ERROR, ret)); } else { /* XXX speedbump should go, but something has to happen else * there's no indication to staff that anything happened when @@ -189,7 +194,7 @@ Populator.prototype.populate = function(barcode, which) { var result = fieldmapper.standardRequest( ["open-ils.booking", "open-ils.booking.reservations.get_captured"], - [xulG.auth.session.key, this.patron_barcode, which] + [openils.User.authtoken, this.patron_barcode, which] ); if (!result) { @@ -220,7 +225,7 @@ Populator.prototype.toggle_anyness = function(any, which) { Populator.prototype.pickup = function(reservation) { return fieldmapper.standardRequest( ["open-ils.circ", "open-ils.circ.reservation.pickup"], - [xulG.auth.session.key, { + [openils.User.authtoken, { "patron_barcode": this.patron_barcode, "reservation": reservation }] @@ -229,7 +234,7 @@ Populator.prototype.pickup = function(reservation) { Populator.prototype.return = function(reservation) { return fieldmapper.standardRequest( ["open-ils.circ", "open-ils.circ.reservation.return"], - [xulG.auth.session.key, { + [openils.User.authtoken, { "patron_barcode": this.patron_barcode, "reservation": reservation.id() /* yeah just id here ------^; lack of parallelism */ @@ -258,7 +263,10 @@ Populator.prototype.act_on_selected = function(how, which) { if (!result) { alert(no_response_msg); } else if (is_ils_event(result) && result.textcode != "SUCCESS") { - alert(my_ils_error(error_msg, result)); + if (result.textcode == "ROUTE_ITEM") + display_transit_slip(result); + else + alert(my_ils_error(error_msg, result)); } else { continue; } @@ -281,3 +289,50 @@ Populator.prototype.reset = function() { this.primary_input.focus(); } }; + +/* XXX needs to be combined with the code that shows transit slips in the + * booking capture interface. */ +function display_transit_slip(e) { + var ou = fieldmapper.aou.findOrgUnit(e.org, /* slim_ok */false); + var ma = (new openils.PermaCrud()).retrieve("aoa", ou.mailing_address()); + var mas = ma ? + dojo.string.substitute( + localeStrings.ADDRESS, + [ma.street1(),ma.street2(),ma.city(),ma.state(),ma.post_code()].map( + function(o) { return o ? o : ""; } + ) + ).replace("\n\n", "\n").replace("\n", "
") : "[Unknown address]"; + /* XXX i18n and/or template */ + try { + var win = window.open( + "","","resizeable,width=600,height=400,scrollbars=1" + ); + win.document.body.innerHTML = + "

Transit Slip

\n" + + //"\n" + + "

Destination: " + ou.name() + "

\n" + + "

" + mas + "

\n" + + "

Barcode: " + e.payload.copy.barcode() + "
\n" + + "Title:
\n" + + "Author:
\n" + + "Slip Date: " + + dojo.date.locale.format(new Date(), {"formatLength": "short"}) + + "

"; + fieldmapper.standardRequest( + ["open-ils.search", "open-ils.search.biblio.mods_from_copy"], { + "params": [e.payload.copy.id()], + "async": true, + "onresponse": function(r) { + var mvr = openils.Util.readResponse(r); + dojo.byId("title", win.document).innerHTML = mvr.title(); + dojo.byId("author", win.document).innerHTML = mvr.author(); + }, + "oncomplete": function() { + win[confirm("Print transit slip?") ? "print" : "close"](); + } + } + ); + } catch (E) { + alert("exception rendering transit slip: " + E); // XXX + } +} diff --git a/Open-ILS/web/js/ui/default/booking/pull_list.js b/Open-ILS/web/js/ui/default/booking/pull_list.js index 996cc42756..8941428c7c 100644 --- a/Open-ILS/web/js/ui/default/booking/pull_list.js +++ b/Open-ILS/web/js/ui/default/booking/pull_list.js @@ -47,18 +47,33 @@ function generate_result_row(one) { return td; } + function render_pickup_lib(pickup_lib) { + var span = document.createElement("span"); + if (pickup_lib != owning_lib_selected) + span.setAttribute("class", "pull_list_will_transit"); + span.innerHTML = localeStrings.AT + " " + + fieldmapper.aou.findOrgUnit(pickup_lib).shortname(); + return span; + } + function reservation_info_cell(one) { var td = document.createElement("td"); for (var i in one.reservations) { var one_resv = one.reservations[i]; var div = document.createElement("div"); - var s = humanize_timestamp_string(one_resv.start_time()) + " - " + - humanize_timestamp_string(one_resv.end_time()) + " " + - formal_name(one_resv.usr()); - /* FIXME: The above need patron barcode instead of name, but - * that requires a fix in the middle layer to flesh on the - * right stuff. */ - div.appendChild(document.createTextNode(s)); + div.setAttribute("class", "pull_list_resv_detail"); + var content = [ + document.createTextNode( + humanize_timestamp_string(one_resv.start_time()) + + " - " + humanize_timestamp_string(one_resv.end_time()) + ), + document.createElement("br"), + render_pickup_lib(one_resv.pickup_lib()), + document.createTextNode( + " " + localeStrings.FOR + " " + formal_name(one_resv.usr()) + ) + ]; + for (var k in content) { div.appendChild(content[k]); } td.appendChild(div); } return td; @@ -71,7 +86,6 @@ function generate_result_row(one) { cells.push(cell(undefined, one.current_resource.barcode())); cells.push(cell(baseid + "_call_number", "-")); cells.push(cell(baseid + "_copy_location", "-")); - cells.push(cell(baseid + "_copy_number", "-")); cells.push(reservation_info_cell(one)); var row = document.createElement("tr"); @@ -120,6 +134,7 @@ function get_all_relevant_acp(list) { return results; } } + return null; } function fill_in_pull_list_details(list, acp_cache) { @@ -133,10 +148,6 @@ function fill_in_pull_list_details(list, acp_cache) { var copy_location_el = document.getElementById( dom_table_rowid(one.current_resource.id()) + "_copy_location" ); - var copy_number_el = document.getElementById( - dom_table_rowid(one.current_resource.id()) + "_copy_number" - ); - var bc = one.current_resource.barcode(); if (acp_cache[bc]) { @@ -148,10 +159,6 @@ function fill_in_pull_list_details(list, acp_cache) { var value = acp_cache[bc].location().name(); if (value) copy_location_el.innerHTML = value; } - if (copy_number_el) { - var value = acp_cache[bc].copy_number(); - if (value) copy_number_el.innerHTML = value; - } } else { alert(localeStrings.COPY_MISSING + bc); } diff --git a/Open-ILS/web/js/ui/default/booking/reservation.js b/Open-ILS/web/js/ui/default/booking/reservation.js index 8958799717..7c2e242d65 100644 --- a/Open-ILS/web/js/ui/default/booking/reservation.js +++ b/Open-ILS/web/js/ui/default/booking/reservation.js @@ -3,6 +3,7 @@ */ dojo.require("fieldmapper.OrgUtils"); dojo.require("openils.PermaCrud"); +dojo.require("openils.User"); dojo.require("openils.widget.OrgUnitFilteringSelect"); dojo.require("dojo.data.ItemFileReadStore"); dojo.require("dijit.form.DateTextBox"); @@ -21,6 +22,7 @@ var brt_list = []; var brsrc_index = {}; var bresv_index = {}; var just_reserved_now = {}; +var aous_cache = {}; function AttrValueTable() { this.t = {}; } AttrValueTable.prototype.set = function(attr, value) { this.t[attr] = value; }; @@ -262,14 +264,25 @@ function sync_brsrc_index_from_ids(available_list, additional_list) { function check_bresv_targeting(results) { var missing = 0; + var due_dates = []; for (var i in results) { - if (!(results[i].targeting && results[i].targeting.current_resource)) { + var targ = results[i].targeting; + if (!(targ && targ.current_resource)) { missing++; + if (targ) { + if (targ.error == "NO_COPIES" && targ.conflicts) { + for (var k in targ.conflicts) { + /* Could potentially get more circ information from + * targ.conflicts for display in the future. */ + due_dates.push(humanize_timestamp_string2(targ.conflicts[k].due_date())); + } + } + } } else { just_reserved_now[results[i].targeting.current_resource] = true; } } - return missing; + return {"missing": missing, "due_dates": due_dates}; } function create_bresv(resource_list) { @@ -308,22 +321,44 @@ function create_bresv(resource_list) { )); } } else { - var missing; - if (missing = check_bresv_targeting(results)) { - alert(dojo.string.substitute( - localeStrings.CREATE_BRESV_OK_MISSING_TARGET, - [results.length, missing] - )); - } else { - if (results.length == 1) { - alert(dojo.string.substitute( - localeStrings.CREATE_BRESV_OK, [results.length] - )); + var targeting = check_bresv_targeting(results); + if (targeting.missing) { + if (aous_cache["booking.require_successful_targeting"]) { + alert( + dojo.string.substitute( + localeStrings.CREATE_BRESV_OK_MISSING_TARGET, + [results.length, targeting.missing] + ) + "\n\n" + + dojo.string.substitute( + localeStrings.CREATE_BRESV_OK_MISSING_TARGET_BLOCKED_BY_CIRC, + [targeting.due_dates] + ) + "\n\n" + + localeStrings.CREATE_BRESV_OK_MISSING_TARGET_WILL_CANCEL + ); + cancel_reservations( + results.map( + function(o) { return o.bresv; }, + true /* skip_update */ + ) + ); } else { - alert(dojo.string.substitute( - localeStrings.CREATE_BRESV_OK_PLURAL, [results.length] - )); + alert( + dojo.string.substitute( + localeStrings.CREATE_BRESV_OK_MISSING_TARGET, + [results.length, targeting.missing] + ) + "\n\n" + + dojo.string.substitute( + localeStrings.CREATE_BRESV_OK_MISSING_TARGET_BLOCKED_BY_CIRC, + [targeting.due_dates] + ) + ); } + } else { + alert( + dojo.string.substitute( + localeStrings.CREATE_BRESV_OK, [results.length] + ) + ); } update_brsrc_list(); update_bresv_grid(); @@ -342,7 +377,7 @@ function flatten_to_dojo_data(obj_list) { "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()), + "end_time": humanize_timestamp_string(o.end_time()) }; if (o.current_resource()) @@ -442,7 +477,7 @@ function init_bresv_grid(barcode) { } } -function cancel_reservations(bresv_id_list) { +function cancel_reservations(bresv_id_list, skip_update) { try { var result = fieldmapper.standardRequest( ["open-ils.booking", "open-ils.booking.reservations.cancel"], @@ -452,21 +487,17 @@ function cancel_reservations(bresv_id_list) { alert(localeStrings.CXL_BRESV_FAILURE2 + E); return; } - setTimeout(update_bresv_grid, 0); + if (!skip_update) setTimeout(update_bresv_grid, 0); if (!result) { alert(localeStrings.CXL_BRESV_FAILURE); } else if (is_ils_event(result)) { alert(my_ils_error(localeStrings.CXL_BRESV_FAILURE2, result)); } else { - if (result.length == 1) { - alert(dojo.string.substitute( + alert( + dojo.string.substitute( localeStrings.CXL_BRESV_SUCCESS, [result.length] - )); - } else { - alert(dojo.string.substitute( - localeStrings.CXL_BRESV_SUCCESS_PLURAL, [result.length] - )); - } + ) + ); } } @@ -705,7 +736,7 @@ function init_timestamp_widgets() { timePattern: "HH:mm", clickableIncrement: "T00:15:00", visibleIncrement: "T00:15:00", - visibleRange: "T01:30:00", + visibleRange: "T01:30:00" }, onChange: function() { reserve_timestamp_range.update_from_widget(this); @@ -736,7 +767,7 @@ function cancel_selected_bresv(bresv_dojo_items) { /* After some delay to allow the cancellations a chance to get * committed, refresh the brsrc list as it might reflect newly * available resources now. */ - setTimeout(update_brsrc_list, 2000); + if (our_brt) setTimeout(update_brsrc_list, 2000); } else { alert(localeStrings.CXL_BRESV_SELECT_SOMETHING); } @@ -770,6 +801,24 @@ function early_action_passthru() { return true; } +function init_aous_cache() { + /* The following method call could be given a longer + * list of OU settings to fetch in the future if needed. */ + var results = fieldmapper.aou.fetchOrgSettingBatch( + openils.User.user.ws_ou(), ["booking.require_successful_targeting"] + ); + if (results && !is_ils_event(results)) { + for (var k in results) { + if (results[k] != undefined) + aous_cache[k] = results[k].value; + } + } else if (results) { + alert(my_ils_error(localeStrings.ERROR_FETCHING_AOUS, results)); + } else { + alert(localeStrings.ERROR_FETCHING_AOUS); + } +} + /* * my_init */ @@ -778,6 +827,7 @@ function my_init() { reveal_dom_element(document.getElementById("brt_search_block")); hide_dom_element(document.getElementById("reserve_under")); init_auto_l10n(document.getElementById("auto_l10n_start_here")); + init_aous_cache(); init_timestamp_widgets(); if (!(opts = xulG.bresv_interface_opts)) opts = {}; diff --git a/Open-ILS/web/templates/default/booking/capture.tt2 b/Open-ILS/web/templates/default/booking/capture.tt2 index d7baf870be..2c97694c5e 100644 --- a/Open-ILS/web/templates/default/booking/capture.tt2 +++ b/Open-ILS/web/templates/default/booking/capture.tt2 @@ -1,7 +1,7 @@ [% WRAPPER "default/base.tt2" %] - +
onclick="attempt_capture();" /> -
-
+
+
[% END %] diff --git a/Open-ILS/web/templates/default/booking/pull_list.tt2 b/Open-ILS/web/templates/default/booking/pull_list.tt2 index 90de2068fa..a61ceca276 100644 --- a/Open-ILS/web/templates/default/booking/pull_list.tt2 +++ b/Open-ILS/web/templates/default/booking/pull_list.tt2 @@ -28,12 +28,11 @@ - + - + - - + 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 e5ae269401..b49a438cda 100644 --- a/Open-ILS/web/templates/default/conify/global/booking/resource.tt2 +++ b/Open-ILS/web/templates/default/conify/global/booking/resource.tt2 @@ -1,43 +1,34 @@ [% WRAPPER default/base.tt2 %] [% ctx.page_title = 'Resources' %] - - - -
-
Resources
-
- - -
-
-
-
+
Resources
+
+ + +
+ +
- - + [% END %] diff --git a/Open-ILS/web/templates/default/conify/global/booking/resource_attr.tt2 b/Open-ILS/web/templates/default/conify/global/booking/resource_attr.tt2 index f26ef6df32..86debfaf1d 100644 --- a/Open-ILS/web/templates/default/conify/global/booking/resource_attr.tt2 +++ b/Open-ILS/web/templates/default/conify/global/booking/resource_attr.tt2 @@ -1,39 +1,31 @@ [% WRAPPER default/base.tt2 %] [% ctx.page_title = 'Resource Attributes' %] - - - - -
-
Resource Attributes
-
- - -
-
-
- +
Resource Attributes
+
+ + +
+ +
-
+ [% END %] diff --git a/Open-ILS/web/templates/default/conify/global/booking/resource_attr_map.tt2 b/Open-ILS/web/templates/default/conify/global/booking/resource_attr_map.tt2 index 69679afa1b..0aad3e2618 100644 --- a/Open-ILS/web/templates/default/conify/global/booking/resource_attr_map.tt2 +++ b/Open-ILS/web/templates/default/conify/global/booking/resource_attr_map.tt2 @@ -1,39 +1,28 @@ [% WRAPPER default/base.tt2 %] [% ctx.page_title = 'Resource Attribute Maps' %] - - - - -
-
Resource Attribute Maps
-
- - -
-
-
- +
Resource Attribute Maps
+
+ + +
+ +
-
- + [% END %] diff --git a/Open-ILS/web/templates/default/conify/global/booking/resource_attr_value.tt2 b/Open-ILS/web/templates/default/conify/global/booking/resource_attr_value.tt2 index b4f92baee2..5734a783d8 100644 --- a/Open-ILS/web/templates/default/conify/global/booking/resource_attr_value.tt2 +++ b/Open-ILS/web/templates/default/conify/global/booking/resource_attr_value.tt2 @@ -1,39 +1,30 @@ [% WRAPPER default/base.tt2 %] [% ctx.page_title = 'Resource Attribute Values' %] - - - - -
-
Resource Attribute Values
-
- - -
-
-
- +
Resource Attribute Values
+
+ + +
+ +
-
- + [% END %] diff --git a/Open-ILS/web/templates/default/conify/global/booking/resource_type.tt2 b/Open-ILS/web/templates/default/conify/global/booking/resource_type.tt2 index 4e8d70466b..3ae9019a65 100644 --- a/Open-ILS/web/templates/default/conify/global/booking/resource_type.tt2 +++ b/Open-ILS/web/templates/default/conify/global/booking/resource_type.tt2 @@ -1,39 +1,32 @@ [% WRAPPER default/base.tt2 %] [% ctx.page_title = 'Resource Types' %] - - - - -
-
Resource Types
-
- - -
-
-
- +
Resource Types
+
+ + +
+ +
-
- + [% END %] -- 2.11.0