]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Booking.pm
add method to return a list of reservation ids by filter; make sure that there is...
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Booking.pm
1 package OpenILS::Application::Booking;
2
3 use strict;
4 use warnings;
5
6 use OpenILS::Application;
7 use base qw/OpenILS::Application/;
8
9 use OpenILS::Utils::CStoreEditor qw/:funcs/;
10 use OpenILS::Utils::Fieldmapper;
11 use OpenILS::Application::AppUtils;
12 my $U = "OpenILS::Application::AppUtils";
13
14 use OpenSRF::Utils::Logger qw/$logger/;
15
16 sub prepare_new_brt {
17     my ($record_id, $owning_lib, $mvr) = @_;
18
19     my $brt = new Fieldmapper::booking::resource_type;
20     $brt->isnew(1);
21     $brt->name($mvr->title);
22     $brt->record($record_id);
23     $brt->catalog_item('t');
24     $brt->owner($owning_lib);
25
26     return $brt;
27 }
28
29 sub get_existing_brt {
30     my ($e, $record_id, $owning_lib, $mvr) = @_;
31     my $results = $e->search_booking_resource_type(
32         {name => $mvr->title, owner => $owning_lib, record => $record_id}
33     );
34
35     return $results->[0] if (length @$results > 0);
36     return undef;
37 }
38
39 sub get_mvr {
40     return $U->simplereq(
41         'open-ils.search',
42         'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
43         shift # record id
44     );
45 }
46
47 sub get_unique_owning_libs {
48     my %hash = ();
49     $hash{$_->call_number->owning_lib} = 1 foreach (@_);    # @_ are copies
50     return keys %hash;
51 }
52
53 sub fetch_copies_by_ids {
54     my ($e, $copy_ids) = @_;
55     my $results = $e->search_asset_copy([
56         {id => $copy_ids},
57         {flesh => 1, flesh_fields => {acp => ['call_number']}}
58     ]);
59     return $results if ref($results) eq 'ARRAY';
60     return [];
61 }
62
63 sub get_single_record_id {
64     my $record_id = undef;
65     foreach (@_) {  # @_ are copies
66         return undef if
67             (defined $record_id && $record_id != $_->call_number->record);
68         $record_id = $_->call_number->record;
69     }
70     return $record_id;
71 }
72
73 __PACKAGE__->register_method(
74     method   => "create_brt_and_brsrc",
75     api_name => "open-ils.booking.create_brt_and_brsrc_from_copies",
76     signature => {
77         params => [
78             {type => 'string', desc => 'Authentication token'},
79             {type => 'array', desc => 'Copy IDs'},
80         ],
81         return => { desc => "A two-element hash. The 'brt' element " .
82             "is a list of created booking resource types described by " .
83             "id/copyid pairs.  The 'brsrc' element is a similar " .
84             "list of created booking resources described by copy/recordid " .
85             "pairs"}
86     }
87 );
88
89 sub create_brt_and_brsrc {
90     my ($self, $conn, $authtoken, $copy_ids) = @_;
91     my (@created_brt, @created_brsrc);
92     my %brt_table = ();
93
94     my $e = new_editor(xact => 1, authtoken => $authtoken);
95     return $e->die_event unless $e->checkauth;
96
97     my @copies = @{fetch_copies_by_ids($e, $copy_ids)};
98     my $record_id = get_single_record_id(@copies) or return $e->die_event;
99     my $mvr = get_mvr($record_id) or return $e->die_event;
100
101     foreach (get_unique_owning_libs(@copies)) {
102         $brt_table{$_} = get_existing_brt($e, $record_id, $_, $mvr) ||
103             prepare_new_brt($record_id, $_, $mvr);
104     }
105
106     while (my ($owning_lib, $brt) = each %brt_table) {
107         if ($brt->isnew) {
108             if ($e->allowed('CREATE_BOOKING_RESOURCE_TYPE', $owning_lib)) {
109                 # We can/should abort if this creation fails, because the
110                 # logic isn't going to be trying to create any redundnat
111                 # brt's, therefore any error will be more serious than
112                 # that.  See the different take on creating brsrc's below.
113                 return $e->die_event unless (
114                     #    v-- Important: assignment modifies original hash
115                     $brt = $e->create_booking_resource_type($brt)
116                 );
117             }
118             push @created_brt, [$brt->id, $brt->record];
119         }
120     }
121
122     foreach (@copies) {
123         if (
124             $e->allowed('CREATE_BOOKING_RESOURCE', $_->call_number->owning_lib)
125         ) {
126             my $brsrc = new Fieldmapper::booking::resource;
127             $brsrc->isnew(1);
128             $brsrc->type($brt_table{$_->call_number->owning_lib}->id);
129             $brsrc->owner($_->call_number->owning_lib);
130             $brsrc->barcode($_->barcode);
131
132             # We don't want to abort the transaction or do anything dramatic if
133             # this fails, because quite possibly a user selected many copies on
134             # which to perform this "create booking resource" operation, and
135             # among those copies there may be some that we still need to
136             # create, and some that we don't.  So we just do what we can.
137             push @created_brsrc, [$brsrc->id, $_->id] if
138                 ($brsrc = $e->create_booking_resource($brsrc));
139             #           ^--- Important: assignment.
140         }
141     }
142
143     $e->commit and
144         return {brt => \@created_brt, brsrc => \@created_brsrc} or
145         return $e->die_event;
146 }
147
148 sub resource_list_by_attrs {
149     my $self = shift;
150     my $client = shift;
151     my $auth = shift;
152     my $filters = shift;
153
154     return undef unless ($filters->{type} || $filters->{attribute_values});
155
156     my $query = {
157         'select'   => { brsrc => [ 'id' ] },
158         'from'     => { brsrc => {} },
159         'where'    => {},
160         'distinct' => 1
161     };
162
163     if ($filters->{type}) {
164         $query->{where}->{type} = $filters->{type};
165     }
166
167     if ($filters->{attribute_values}) {
168
169         $query->{from}->{brsrc}->{bram} = { field => 'resource' };
170
171         $filters->{attribute_values} = [$filters->{attribute_values}]
172             if (!ref($filters->{attribute_values}));
173
174         $query->{having}->{'+bram'}->{value}->{'@>'} = {
175             transform => 'array_accum',
176             value => '{'.join(',', @{ $filters->{attribute_values} } ).'}'
177         };
178     }
179
180     if ($filters->{available}) {
181         $query->{from}->{brsrc}->{bresv} = { field => 'current_resource' };
182
183         if (!ref($filters->{available})) { # just one time, start perhaps
184             $query->{where}->{'+bresv'} = {
185                 '-or' => {
186                     'overbook' => 't',
187                     '-or' => {
188                         start_time => { '>=' => $filters->{available} },
189                         end_time   => { '<=' => $filters->{available} },
190                     }
191                 }
192             };
193         } else { # start and end times
194             $query->{where}->{'+bresv'} = {
195                 '-or' => {
196                     'overbook' => 't',
197                     '-and' => {
198                         '-or' => {
199                             start_time => { '>=' => $filters->{available}->[0] },
200                             end_time   => { '<=' => $filters->{available}->[0] },
201                         },
202                         '-or' => {
203                             start_time => { '>=' => $filters->{available}->[1] },
204                             end_time   => { '<=' => $filters->{available}->[1] },
205                         }
206                     }
207                 }
208             };
209         }
210     }
211
212     if ($filters->{booked}) {
213         $query->{from}->{brsrc}->{bresv} = { field => 'current_resource' };
214
215         if (!ref($filters->{booked})) { # just one time, start perhaps
216             $query->{where}->{'+bresv'} = {
217                 start_time => { '<=' => $filters->{booked} },
218                 end_time   => { '>=' => $filters->{booked} },
219             };
220         } else { # start and end times
221             $query->{where}->{'+bresv'} = {
222                 '-or' => {
223                     '-and' => {
224                         start_time => { '<=' => $filters->{booked}->[0] },
225                         end_time   => { '>=' => $filters->{booked}->[0] },
226                     },
227                     '-and' => {
228                         start_time => { '<=' => $filters->{booked}->[1] },
229                         end_time   => { '>=' => $filters->{booked}->[1] },
230                     }
231                 }
232             };
233         }
234     }
235
236     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
237     my $ids = $cstore->request( 'open-ils.cstore.json_query.atomic', $query )->gather(1);
238     $ids = [ map { $_->{id} } @$ids ];
239     $cstore->disconnect;
240
241     my $pcrud = OpenSRF::AppSession->connect('open-ils.pcrud');
242     my $allowed_ids = $pcrud->request(
243         'open-ils.pcrud.id_list.brsrc.atomic',
244         $auth => { id => $ids }
245     )->gather(1);
246     $pcrud->disconnect;
247
248     return $allowed_ids;
249 }
250 __PACKAGE__->register_method(
251     method   => "resource_list_by_attrs",
252     api_name => "open-ils.booking.resources.filtered_id_list",
253     argc     => 2,
254     signature=> {
255         params => [
256             {type => 'string', desc => 'Authentication token'},
257             {type => 'object', desc => 'Filter object -- see notes for details'}
258         ],
259         return => { desc => "An array of brsrc ids matching the requested filters." },
260     },
261     notes    => <<'NOTES'
262
263 The filter object parameter can contain the following keys:
264  * type             => The id of a booking resource type (brt)
265  * attribute_values => The ids of booking resource type attribute values that the resource must have assigned to it (brav)
266  * available        => Either:
267                         A timestamp during which the resources are not reserved.  If the resource is overbookable, this is ignored.
268                         A range of two timestamps which do not overlap any reservations for the resources.  If the resource is overbookable, this is ignored.
269  * booked           => Either:
270                         A timestamp during which the resources are reserved.
271                         A range of two timestamps which overlap a reservation of the resources.
272
273 Note that at least one of 'type' or 'attribute_values' is required.
274
275 NOTES
276
277 );
278
279 sub reservation_list_by_filters {
280     my $self = shift;
281     my $client = shift;
282     my $auth = shift;
283     my $filters = shift;
284
285     return undef unless ($filters->{user} || $filters->{resource} || $filters->{type} || $filters->{attribute_values});
286
287     my $query = {
288         'select'   => { bresv => [ 'id' ] },
289         'from'     => { bresv => {} },
290         'where'    => {},
291         'distinct' => 1
292     };
293
294     if ($filters->{fields}) {
295         $query->{where} = $filters->{fields};
296     }
297
298
299     if ($filters->{user}) {
300         $query->{where}->{usr} = $filters->{user};
301     }
302
303     if ($filters->{type}) {
304         $query->{where}->{target_resource_type} = $filters->{type};
305     }
306
307     if ($filters->{resource}) {
308         $query->{where}->{target_resource} = $filters->{resource};
309     }
310
311     if ($filters->{attribute_values}) {
312
313         $query->{from}->{bresv}->{bravm} = { field => 'reservation' };
314
315         $filters->{attribute_values} = [$filters->{attribute_values}]
316             if (!ref($filters->{attribute_values}));
317
318         $query->{having}->{'+bravm'}->{attr_value}->{'@>'} = {
319             transform => 'array_accum',
320             value => '{'.join(',', @{ $filters->{attribute_values} } ).'}'
321         };
322     }
323
324     if ($filters->{search_start} || $filters->{search_end}) {
325         
326         $query->{where}->{'-or'} = {};
327
328         $query->{where}->{'-or'}->{start_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] }
329                 if ($filters->{search_start});
330
331         $query->{where}->{'-or'}->{end_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] }
332                 if ($filters->{search_end});
333     }
334
335     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
336     my $ids = $cstore->request( 'open-ils.cstore.json_query.atomic', $query )->gather(1);
337     $ids = [ map { $_->{id} } @$ids ];
338     $cstore->disconnect;
339
340     return $ids;
341 }
342 __PACKAGE__->register_method(
343     method   => "reservation_list_by_filters",
344     api_name => "open-ils.booking.reservations.filtered_id_list",
345     argc     => 2,
346     signature=> {
347         params => [
348             {type => 'string', desc => 'Authentication token'},
349             {type => 'object', desc => 'Filter object -- see notes for details'}
350         ],
351         return => { desc => "An array of brsrc ids matching the requested filters." },
352     },
353     notes    => <<'NOTES'
354
355 The filter object parameter can contain the following keys:
356  * user             => The id of a user that has requested a bookable item -- filters on bresv.usr
357  * type             => The id of a booking resource type (brt) -- filters on bresv.target_resource_type
358  * resource         => The id of a booking resource (brsrc) -- filters on bresv.target_resource
359  * attribute_values => The ids of booking resource type attribute values that the resource must have assigned to it (brav)
360  * search_start     => If search_end is not specified, booking interval (start_time to end_time) must contain this timestamp.
361  * search_end       => If search_start is not specified, booking interval (start_time to end_time) must contain this timestamp.
362  * fields           => An object containing any combination of bresv search filters in standard cstore/pcrud search format.
363
364 Note that at least one of 'user', 'type', 'resource' or 'attribute_values' is required.  If both search_start and search_end are specified,
365 then the result includes any reservations that overlap with that time range.  Any filter fields supplied in 'fields' are overridden
366 by the top-level filters ('user', 'type', 'resource').
367
368 NOTES
369
370 );
371
372
373 1;