1 package OpenILS::Application::Booking;
6 use OpenILS::Application;
7 use base qw/OpenILS::Application/;
9 use OpenILS::Utils::CStoreEditor qw/:funcs/;
10 use OpenILS::Utils::Fieldmapper;
11 use OpenILS::Application::AppUtils;
12 my $U = "OpenILS::Application::AppUtils";
14 use OpenSRF::Utils::Logger qw/$logger/;
16 use DateTime::Format::ISO8601;
19 my ($record_id, $owning_lib, $mvr) = @_;
21 my $brt = new Fieldmapper::booking::resource_type;
23 $brt->name($mvr->title);
24 $brt->record($record_id);
25 $brt->catalog_item('t');
26 $brt->owner($owning_lib);
31 sub get_existing_brt {
32 my ($e, $record_id, $owning_lib, $mvr) = @_;
33 my $results = $e->search_booking_resource_type(
34 {name => $mvr->title, owner => $owning_lib, record => $record_id}
37 return $results->[0] if (length @$results > 0);
44 'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
49 sub get_unique_owning_libs {
51 $hash{$_->call_number->owning_lib} = 1 foreach (@_); # @_ are copies
55 sub fetch_copies_by_ids {
56 my ($e, $copy_ids) = @_;
57 my $results = $e->search_asset_copy([
59 {flesh => 1, flesh_fields => {acp => ['call_number']}}
61 return $results if ref($results) eq 'ARRAY';
65 sub get_single_record_id {
66 my $record_id = undef;
67 foreach (@_) { # @_ are copies
69 (defined $record_id && $record_id != $_->call_number->record);
70 $record_id = $_->call_number->record;
75 __PACKAGE__->register_method(
76 method => "create_brt_and_brsrc",
77 api_name => "open-ils.booking.create_brt_and_brsrc_from_copies",
80 {type => 'string', desc => 'Authentication token'},
81 {type => 'array', desc => 'Copy IDs'},
83 return => { desc => "A two-element hash. The 'brt' element " .
84 "is a list of created booking resource types described by " .
85 "id/copyid pairs. The 'brsrc' element is a similar " .
86 "list of created booking resources described by copy/recordid " .
91 sub create_brt_and_brsrc {
92 my ($self, $conn, $authtoken, $copy_ids) = @_;
93 my (@created_brt, @created_brsrc);
96 my $e = new_editor(xact => 1, authtoken => $authtoken);
97 return $e->die_event unless $e->checkauth;
99 my @copies = @{fetch_copies_by_ids($e, $copy_ids)};
100 my $record_id = get_single_record_id(@copies) or return $e->die_event;
101 my $mvr = get_mvr($record_id) or return $e->die_event;
103 foreach (get_unique_owning_libs(@copies)) {
104 $brt_table{$_} = get_existing_brt($e, $record_id, $_, $mvr) ||
105 prepare_new_brt($record_id, $_, $mvr);
108 while (my ($owning_lib, $brt) = each %brt_table) {
110 if ($e->allowed('CREATE_BOOKING_RESOURCE_TYPE', $owning_lib)) {
111 # We can/should abort if this creation fails, because the
112 # logic isn't going to be trying to create any redundnat
113 # brt's, therefore any error will be more serious than
114 # that. See the different take on creating brsrc's below.
115 return $e->die_event unless (
116 # v-- Important: assignment modifies original hash
117 $brt = $e->create_booking_resource_type($brt)
120 push @created_brt, [$brt->id, $brt->record];
126 $e->allowed('CREATE_BOOKING_RESOURCE', $_->call_number->owning_lib)
128 my $brsrc = new Fieldmapper::booking::resource;
130 $brsrc->type($brt_table{$_->call_number->owning_lib}->id);
131 $brsrc->owner($_->call_number->owning_lib);
132 $brsrc->barcode($_->barcode);
134 # We don't want to abort the transaction or do anything dramatic if
135 # this fails, because quite possibly a user selected many copies on
136 # which to perform this "create booking resource" operation, and
137 # among those copies there may be some that we still need to
138 # create, and some that we don't. So we just do what we can.
139 push @created_brsrc, [$brsrc->id, $_->id] if
140 ($brsrc = $e->create_booking_resource($brsrc));
141 # ^--- Important: assignment.
146 return {brt => \@created_brt, brsrc => \@created_brsrc} or
147 return $e->die_event;
150 sub resource_list_by_attrs {
156 return undef unless ($filters->{type} || $filters->{attribute_values});
158 my $e = new_editor(authtoken=>$auth);
159 return $e->event unless $e->checkauth;
162 'select' => { brsrc => [ 'id' ] },
163 'from' => { brsrc => {} },
168 if ($filters->{type}) {
169 $query->{where}->{type} = $filters->{type};
172 if ($filters->{attribute_values}) {
174 $query->{from}->{brsrc}->{bram} = { field => 'resource' };
176 $filters->{attribute_values} = [$filters->{attribute_values}]
177 if (!ref($filters->{attribute_values}));
179 $query->{having}->{'+bram'}->{value}->{'@>'} = {
180 transform => 'array_accum',
181 value => '{'.join(',', @{ $filters->{attribute_values} } ).'}'
185 if ($filters->{available}) {
186 $query->{from}->{brsrc}->{bresv} = { field => 'current_resource' };
188 if (!ref($filters->{available})) { # just one time, start perhaps
189 $query->{where}->{'+bresv'} = {
193 start_time => { '>=' => $filters->{available} },
194 end_time => { '<=' => $filters->{available} },
198 } else { # start and end times
199 $query->{where}->{'+bresv'} = {
204 start_time => { '>=' => $filters->{available}->[0] },
205 end_time => { '<=' => $filters->{available}->[0] },
208 start_time => { '>=' => $filters->{available}->[1] },
209 end_time => { '<=' => $filters->{available}->[1] },
217 if ($filters->{booked}) {
218 $query->{from}->{brsrc}->{bresv} = { field => 'current_resource' };
220 if (!ref($filters->{booked})) { # just one time, start perhaps
221 $query->{where}->{'+bresv'} = {
222 start_time => { '<=' => $filters->{booked} },
223 end_time => { '>=' => $filters->{booked} },
225 } else { # start and end times
226 $query->{where}->{'+bresv'} = {
229 start_time => { '<=' => $filters->{booked}->[0] },
230 end_time => { '>=' => $filters->{booked}->[0] },
233 start_time => { '<=' => $filters->{booked}->[1] },
234 end_time => { '>=' => $filters->{booked}->[1] },
241 my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
242 my $ids = $cstore->request( 'open-ils.cstore.json_query.atomic', $query )->gather(1);
243 $ids = [ map { $_->{id} } @$ids ];
246 my $pcrud = OpenSRF::AppSession->connect('open-ils.pcrud');
247 my $allowed_ids = $pcrud->request(
248 'open-ils.pcrud.id_list.brsrc.atomic',
249 $auth => { id => $ids }
255 __PACKAGE__->register_method(
256 method => "resource_list_by_attrs",
257 api_name => "open-ils.booking.resources.filtered_id_list",
261 {type => 'string', desc => 'Authentication token'},
262 {type => 'object', desc => 'Filter object -- see notes for details'}
264 return => { desc => "An array of brsrc ids matching the requested filters." },
268 The filter object parameter can contain the following keys:
269 * type => The id of a booking resource type (brt)
270 * attribute_values => The ids of booking resource type attribute values that the resource must have assigned to it (brav)
271 * available => Either:
272 A timestamp during which the resources are not reserved. If the resource is overbookable, this is ignored.
273 A range of two timestamps which do not overlap any reservations for the resources. If the resource is overbookable, this is ignored.
275 A timestamp during which the resources are reserved.
276 A range of two timestamps which overlap a reservation of the resources.
278 Note that at least one of 'type' or 'attribute_values' is required.
284 sub reservation_list_by_filters {
290 return undef unless ($filters->{user} || $filters->{resource} || $filters->{type} || $filters->{attribute_values});
292 my $e = new_editor(authtoken=>$auth);
293 return $e->event unless $e->checkauth;
294 return $e->event unless $e->allowed('VIEW_TRANSACTION');
297 'select' => { bresv => [ 'id' ] },
298 'from' => { bresv => {} },
300 'order_by' => [{ class => bresv => field => start_time => direction => 'asc' }],
304 if ($filters->{fields}) {
305 $query->{where} = $filters->{fields};
309 if ($filters->{user}) {
310 $query->{where}->{usr} = $filters->{user};
313 if ($filters->{type}) {
314 $query->{where}->{target_resource_type} = $filters->{type};
317 if ($filters->{resource}) {
318 $query->{where}->{target_resource} = $filters->{resource};
321 if ($filters->{attribute_values}) {
323 $query->{from}->{bresv}->{bravm} = { field => 'reservation' };
325 $filters->{attribute_values} = [$filters->{attribute_values}]
326 if (!ref($filters->{attribute_values}));
328 $query->{having}->{'+bravm'}->{attr_value}->{'@>'} = {
329 transform => 'array_accum',
330 value => '{'.join(',', @{ $filters->{attribute_values} } ).'}'
334 if ($filters->{search_start} || $filters->{search_end}) {
336 $query->{where}->{'-or'} = {};
338 $query->{where}->{'-or'}->{start_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] }
339 if ($filters->{search_start});
341 $query->{where}->{'-or'}->{end_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] }
342 if ($filters->{search_end});
345 my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
346 my $ids = $cstore->request( 'open-ils.cstore.json_query.atomic', $query )->gather(1);
347 $ids = [ map { $_->{id} } @$ids ];
352 __PACKAGE__->register_method(
353 method => "reservation_list_by_filters",
354 api_name => "open-ils.booking.reservations.filtered_id_list",
358 {type => 'string', desc => 'Authentication token'},
359 {type => 'object', desc => 'Filter object -- see notes for details'}
361 return => { desc => "An array of brsrc ids matching the requested filters." },
365 The filter object parameter can contain the following keys:
366 * user => The id of a user that has requested a bookable item -- filters on bresv.usr
367 * type => The id of a booking resource type (brt) -- filters on bresv.target_resource_type
368 * resource => The id of a booking resource (brsrc) -- filters on bresv.target_resource
369 * attribute_values => The ids of booking resource type attribute values that the resource must have assigned to it (brav)
370 * search_start => If search_end is not specified, booking interval (start_time to end_time) must contain this timestamp.
371 * search_end => If search_start is not specified, booking interval (start_time to end_time) must contain this timestamp.
372 * fields => An object containing any combination of bresv search filters in standard cstore/pcrud search format.
374 Note that at least one of 'user', 'type', 'resource' or 'attribute_values' is required. If both search_start and search_end are specified,
375 then the result includes any reservations that overlap with that time range. Any filter fields supplied in 'fields' are overridden
376 by the top-level filters ('user', 'type', 'resource').