]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Booking.pm
adding org unit settings to block or provide default elbow room for circs; logic...
[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 use DateTime;
16 use DateTime::Format::ISO8601;
17
18 sub prepare_new_brt {
19     my ($record_id, $owning_lib, $mvr) = @_;
20
21     my $brt = new Fieldmapper::booking::resource_type;
22     $brt->isnew(1);
23     $brt->name($mvr->title);
24     $brt->record($record_id);
25     $brt->catalog_item('t');
26     $brt->owner($owning_lib);
27
28     return $brt;
29 }
30
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}
35     );
36
37     return $results->[0] if (length @$results > 0);
38     return undef;
39 }
40
41 sub get_mvr {
42     return $U->simplereq(
43         'open-ils.search',
44         'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
45         shift # record id
46     );
47 }
48
49 sub get_unique_owning_libs {
50     my %hash = ();
51     $hash{$_->call_number->owning_lib} = 1 foreach (@_);    # @_ are copies
52     return keys %hash;
53 }
54
55 sub fetch_copies_by_ids {
56     my ($e, $copy_ids) = @_;
57     my $results = $e->search_asset_copy([
58         {id => $copy_ids},
59         {flesh => 1, flesh_fields => {acp => ['call_number']}}
60     ]);
61     return $results if ref($results) eq 'ARRAY';
62     return [];
63 }
64
65 sub get_single_record_id {
66     my $record_id = undef;
67     foreach (@_) {  # @_ are copies
68         return undef if
69             (defined $record_id && $record_id != $_->call_number->record);
70         $record_id = $_->call_number->record;
71     }
72     return $record_id;
73 }
74
75 __PACKAGE__->register_method(
76     method   => "create_brt_and_brsrc",
77     api_name => "open-ils.booking.create_brt_and_brsrc_from_copies",
78     signature => {
79         params => [
80             {type => 'string', desc => 'Authentication token'},
81             {type => 'array', desc => 'Copy IDs'},
82         ],
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 " .
87             "pairs"}
88     }
89 );
90
91 sub create_brt_and_brsrc {
92     my ($self, $conn, $authtoken, $copy_ids) = @_;
93     my (@created_brt, @created_brsrc);
94     my %brt_table = ();
95
96     my $e = new_editor(xact => 1, authtoken => $authtoken);
97     return $e->die_event unless $e->checkauth;
98
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;
102
103     foreach (get_unique_owning_libs(@copies)) {
104         $brt_table{$_} = get_existing_brt($e, $record_id, $_, $mvr) ||
105             prepare_new_brt($record_id, $_, $mvr);
106     }
107
108     while (my ($owning_lib, $brt) = each %brt_table) {
109         if ($brt->isnew) {
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)
118                 );
119             }
120             push @created_brt, [$brt->id, $brt->record];
121         }
122     }
123
124     foreach (@copies) {
125         if (
126             $e->allowed('CREATE_BOOKING_RESOURCE', $_->call_number->owning_lib)
127         ) {
128             my $brsrc = new Fieldmapper::booking::resource;
129             $brsrc->isnew(1);
130             $brsrc->type($brt_table{$_->call_number->owning_lib}->id);
131             $brsrc->owner($_->call_number->owning_lib);
132             $brsrc->barcode($_->barcode);
133
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.
142         }
143     }
144
145     $e->commit and
146         return {brt => \@created_brt, brsrc => \@created_brsrc} or
147         return $e->die_event;
148 }
149
150 sub resource_list_by_attrs {
151     my $self = shift;
152     my $client = shift;
153     my $auth = shift;
154     my $filters = shift;
155
156     return undef unless ($filters->{type} || $filters->{attribute_values});
157
158     my $e = new_editor(authtoken=>$auth);
159     return $e->event unless $e->checkauth;
160
161     my $query = {
162         'select'   => { brsrc => [ 'id' ] },
163         'from'     => { brsrc => {} },
164         'where'    => {},
165         'distinct' => 1
166     };
167
168     if ($filters->{type}) {
169         $query->{where}->{type} = $filters->{type};
170     }
171
172     if ($filters->{attribute_values}) {
173
174         $query->{from}->{brsrc}->{bram} = { field => 'resource' };
175
176         $filters->{attribute_values} = [$filters->{attribute_values}]
177             if (!ref($filters->{attribute_values}));
178
179         $query->{having}->{'+bram'}->{value}->{'@>'} = {
180             transform => 'array_accum',
181             value => '{'.join(',', @{ $filters->{attribute_values} } ).'}'
182         };
183     }
184
185     if ($filters->{available}) {
186         $query->{from}->{brsrc}->{bresv} = { field => 'current_resource' };
187
188         if (!ref($filters->{available})) { # just one time, start perhaps
189             $query->{where}->{'+bresv'} = {
190                 '-or' => {
191                     'overbook' => 't',
192                     '-or' => {
193                         start_time => { '>=' => $filters->{available} },
194                         end_time   => { '<=' => $filters->{available} },
195                     }
196                 }
197             };
198         } else { # start and end times
199             $query->{where}->{'+bresv'} = {
200                 '-or' => {
201                     'overbook' => 't',
202                     '-and' => {
203                         '-or' => {
204                             start_time => { '>=' => $filters->{available}->[0] },
205                             end_time   => { '<=' => $filters->{available}->[0] },
206                         },
207                         '-or' => {
208                             start_time => { '>=' => $filters->{available}->[1] },
209                             end_time   => { '<=' => $filters->{available}->[1] },
210                         }
211                     }
212                 }
213             };
214         }
215     }
216
217     if ($filters->{booked}) {
218         $query->{from}->{brsrc}->{bresv} = { field => 'current_resource' };
219
220         if (!ref($filters->{booked})) { # just one time, start perhaps
221             $query->{where}->{'+bresv'} = {
222                 start_time => { '<=' => $filters->{booked} },
223                 end_time   => { '>=' => $filters->{booked} },
224             };
225         } else { # start and end times
226             $query->{where}->{'+bresv'} = {
227                 '-or' => {
228                     '-and' => {
229                         start_time => { '<=' => $filters->{booked}->[0] },
230                         end_time   => { '>=' => $filters->{booked}->[0] },
231                     },
232                     '-and' => {
233                         start_time => { '<=' => $filters->{booked}->[1] },
234                         end_time   => { '>=' => $filters->{booked}->[1] },
235                     }
236                 }
237             };
238         }
239     }
240
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 ];
244     $cstore->disconnect;
245
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 }
250     )->gather(1);
251     $pcrud->disconnect;
252
253     return $allowed_ids;
254 }
255 __PACKAGE__->register_method(
256     method   => "resource_list_by_attrs",
257     api_name => "open-ils.booking.resources.filtered_id_list",
258     argc     => 2,
259     signature=> {
260         params => [
261             {type => 'string', desc => 'Authentication token'},
262             {type => 'object', desc => 'Filter object -- see notes for details'}
263         ],
264         return => { desc => "An array of brsrc ids matching the requested filters." },
265     },
266     notes    => <<'NOTES'
267
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.
274  * booked           => Either:
275                         A timestamp during which the resources are reserved.
276                         A range of two timestamps which overlap a reservation of the resources.
277
278 Note that at least one of 'type' or 'attribute_values' is required.
279
280 NOTES
281
282 );
283
284 sub reservation_list_by_filters {
285     my $self = shift;
286     my $client = shift;
287     my $auth = shift;
288     my $filters = shift;
289
290     return undef unless ($filters->{user} || $filters->{resource} || $filters->{type} || $filters->{attribute_values});
291
292     my $e = new_editor(authtoken=>$auth);
293     return $e->event unless $e->checkauth;
294     return $e->event unless $e->allowed('VIEW_TRANSACTION');
295
296     my $query = {
297         'select'   => { bresv => [ 'id' ] },
298         'from'     => { bresv => {} },
299         'where'    => {},
300         'order_by' => [{ class => bresv => field => start_time => direction => 'asc' }],
301         'distinct' => 1
302     };
303
304     if ($filters->{fields}) {
305         $query->{where} = $filters->{fields};
306     }
307
308
309     if ($filters->{user}) {
310         $query->{where}->{usr} = $filters->{user};
311     }
312
313     if ($filters->{type}) {
314         $query->{where}->{target_resource_type} = $filters->{type};
315     }
316
317     if ($filters->{resource}) {
318         $query->{where}->{target_resource} = $filters->{resource};
319     }
320
321     if ($filters->{attribute_values}) {
322
323         $query->{from}->{bresv}->{bravm} = { field => 'reservation' };
324
325         $filters->{attribute_values} = [$filters->{attribute_values}]
326             if (!ref($filters->{attribute_values}));
327
328         $query->{having}->{'+bravm'}->{attr_value}->{'@>'} = {
329             transform => 'array_accum',
330             value => '{'.join(',', @{ $filters->{attribute_values} } ).'}'
331         };
332     }
333
334     if ($filters->{search_start} || $filters->{search_end}) {
335         
336         $query->{where}->{'-or'} = {};
337
338         $query->{where}->{'-or'}->{start_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] }
339                 if ($filters->{search_start});
340
341         $query->{where}->{'-or'}->{end_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] }
342                 if ($filters->{search_end});
343     }
344
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 ];
348     $cstore->disconnect;
349
350     return $ids;
351 }
352 __PACKAGE__->register_method(
353     method   => "reservation_list_by_filters",
354     api_name => "open-ils.booking.reservations.filtered_id_list",
355     argc     => 2,
356     signature=> {
357         params => [
358             {type => 'string', desc => 'Authentication token'},
359             {type => 'object', desc => 'Filter object -- see notes for details'}
360         ],
361         return => { desc => "An array of brsrc ids matching the requested filters." },
362     },
363     notes    => <<'NOTES'
364
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.
373
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').
377
378 NOTES
379
380 );
381
382
383 1;