LP#1552778: copy some date/time utils from OpenSRF
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Actor / ClosedDates.pm
1 package OpenILS::Application::Actor::ClosedDates;
2 use base 'OpenILS::Application';
3 use strict; use warnings;
4 use OpenSRF::EX qw(:try);
5 use OpenILS::Utils::DateTime qw(:datetime);
6 use DateTime;
7 use DateTime::Format::ISO8601;
8 use OpenILS::Utils::CStoreEditor q/:funcs/;
9 use OpenILS::Application::AppUtils;
10 my $U = "OpenILS::Application::AppUtils";
11
12 sub initialize { return 1; }
13
14 sub process_emergency {
15     my( $self, $conn, $auth, $date ) = @_;
16
17     my $e = new_editor(authtoken=>$auth);
18     return $e->event unless $e->checkauth;
19
20     return $e->die_event unless $e->allowed(
21         'EMERGENCY_CLOSING', $date->org_unit);
22
23     my $id = ref($date->emergency_closing) ? $date->emergency_closing->id : $date->emergency_closing;
24
25     # Stage 1
26     $e->xact_begin;
27     my $rows = $e->json_query({
28         from => ['action.emergency_closing_stage_1', $id]
29     });
30     $e->xact_commit;
31     return unless ($rows && @$rows);
32
33     $conn->respond({stage => 'start', stats => $$rows[0]});
34
35     my $ses = OpenSRF::AppSession->create('open-ils.trigger');
36
37     # Stage 2 - circs
38     my $circs = $e->search_action_emergency_closing_circulation(
39         { emergency_closing => $id }
40     );
41     my $circ_total = scalar(@$circs);
42
43     my $mod = 1;
44     $mod = int($circ_total / 10) if ($circ_total >= 100);
45     $mod = int($circ_total / 100) if ($circ_total >= 1000);
46
47     my $count = 0;
48     for my $circ (@$circs) {
49         $e->xact_begin;
50         my $rows = $e->json_query({ from => ['action.emergency_closing_stage_2_circ', $circ->id] });
51         $e->xact_commit;
52         $count++;
53         $ses->request('open-ils.trigger.event.autocreate', 'checkout.due.emergency_closing', $circ, $e->requestor->ws_ou)
54             if (ref($rows) && @$rows && $U->is_true($$rows[0]{'action.emergency_closing_stage_2_circ'}));
55         $conn->respond({stage => 'circulations', circulations => [$count,$circ_total]})
56             if ($mod == 1 or !($circ_total % $mod));
57     }
58
59     # Stage 3 - holds
60     my $holds = $e->search_action_emergency_closing_hold(
61         { emergency_closing => $id }
62     );
63     my $hold_total = scalar(@$holds);
64
65     $mod = 1;
66     $mod = int($hold_total / 10) if ($hold_total >= 100);
67     $mod = int($hold_total / 100) if ($hold_total >= 1000);
68
69     $count = 0;
70     for my $hold (@$holds) {
71         $e->xact_begin;
72         my $rows = $e->json_query({ from => ['action.emergency_closing_stage_2_hold', $hold->id] });
73         $e->xact_commit;
74         $count++;
75         $ses->request('open-ils.trigger.event.autocreate', 'hold.shelf_expire.emergency_closing', $hold, $e->requestor->ws_ou)
76             if (ref($rows) && @$rows && $U->is_true($$rows[0]{'action.emergency_closing_stage_2_hold'}));
77         $conn->respond({stage => 'holds', holds => [$count,$hold_total]})
78             if ($mod == 1 or !($hold_total % $mod));
79     }
80
81     # Stage 2 - reservations
82     my $ress = $e->search_action_emergency_closing_reservation(
83         { emergency_closing => $id }
84     );
85     my $res_total = scalar(@$ress);
86
87     $mod = 1;
88     $mod = int($res_total / 10) if ($res_total >= 100);
89     $mod = int($res_total / 100) if ($res_total >= 1000);
90
91     $count = 0;
92     for my $res (@$ress) {
93         $e->xact_begin;
94         my $rows = $e->json_query({ from => ['action.emergency_closing_stage_2_reservation', $res->id] });
95         $e->xact_commit;
96         $count++;
97         $ses->request('open-ils.trigger.event.autocreate', 'booking.due.emergency_closing', $res, $e->requestor->ws_ou)
98             if (ref($rows) && @$rows && $U->is_true($$rows[0]{'action.emergency_closing_stage_2_reservation'}));
99         $conn->respond({stage => 'ress', ress => [$count,$res_total]})
100             if ($mod == 1 or !($res_total % $mod));
101     }
102
103     # Stage 3
104     my $eclosing = $e->retrieve_action_emergency_closing($id);
105     $eclosing->process_end_time('now');
106     $e->xact_begin;
107     $e->update_action_emergency_closing($eclosing);
108     $e->xact_commit;
109
110     return {stage => 'complete', complete => 1};
111 }
112 __PACKAGE__->register_method( 
113     method      => 'process_emergency',
114     api_name    => 'open-ils.actor.org_unit.closed.process_emergency',
115     stream      => 1,
116     max_bundle_count => 1,
117     signature   => q/Processes an emergency closing/
118 );
119
120 __PACKAGE__->register_method( 
121     method => 'fetch_dates',
122     api_name    => 'open-ils.actor.org_unit.closed.retrieve.all',
123     signature   => q/
124         Retrieves a list of closed date object IDs
125     /
126 );
127
128 sub fetch_dates {
129     my( $self, $conn, $auth, $args ) = @_;
130
131     my $e = new_editor(authtoken=>$auth);
132     return $e->event unless $e->checkauth;
133
134     my $org = $$args{orgid} || $e->requestor->ws_ou;
135     my @date = localtime;
136     my $start = $$args{start_date} ||  #default to today 
137         ($date[5] + 1900) .'-'. ($date[4] + 1) .'-'. $date[3];
138     my $end = $$args{end_date} || '3000-01-01'; # Y3K, here I come..
139
140     my $dates = $e->search_actor_org_unit_closed_date( 
141         [{ 
142             '-or' => [
143                 { close_start => { ">=" => $start }, close_end => { "<=" => $end } },
144                 { emergency_closing => { "!=" => undef }, "+aec" => { process_end_time => { "=" => undef } } }
145             ],
146             org_unit => $org,
147         }, {flesh        => 2,
148             flesh_fields => { aoucd => ['emergency_closing'], aec => ['status'] },
149             join         => { "aec" => { type => "left" } },
150             limit        => $$args{limit},
151             offset       => $$args{offset}
152         }], { idlist => $$args{idlist} } ) or return $e->event;
153
154     if(!$$args{idlist} and @$dates) {
155         $dates = [ sort { $a->close_start cmp $b->close_start } @$dates ];
156     }
157
158     return $dates;
159 }
160
161 __PACKAGE__->register_method( 
162     method => 'fetch_date',
163     api_name    => 'open-ils.actor.org_unit.closed.retrieve',
164     signature   => q/
165         Retrieves a single date object
166     /
167 );
168
169 sub fetch_date {
170     my( $self, $conn, $auth, $id ) = @_;
171     my $e = new_editor(authtoken=>$auth);
172     return $e->event unless $e->checkauth;
173     my $date = $e->retrieve_actor_org_unit_closed_date($id) or return $e->event;
174     $date->emergency_closing(
175         $e->retrieve_action_emergency_closing($date->emergency_closing)
176     ) if $date->emergency_closing;
177     return $date;
178 }
179
180
181 __PACKAGE__->register_method( 
182     method => 'delete_date',
183     api_name    => 'open-ils.actor.org_unit.closed.delete',
184     signature   => q/
185         Removes a single date object
186     /
187 );
188
189 sub delete_date {
190     my( $self, $conn, $auth, $id ) = @_;
191     my $e = new_editor(authtoken=>$auth, xact => 1);
192     return $e->die_event unless $e->checkauth;
193     my $date = $e->retrieve_actor_org_unit_closed_date($id) or return $e->die_event;
194     if ($date->emergency_closing) {
195         return $e->die_event unless $e->allowed(
196             'EMERGENCY_CLOSING', $date->org_unit);
197     }
198     return $e->die_event unless $e->allowed(
199         'actor.org_unit.closed_date.delete', $date->org_unit);
200     $e->delete_actor_org_unit_closed_date($date) or return $e->die_event;
201     $e->commit;
202     return 1;
203 }
204
205
206
207
208 __PACKAGE__->register_method( 
209     method => 'create_date',
210     api_name    => 'open-ils.actor.org_unit.closed.create',
211     signature   => q/
212         Creates a new org closed data
213     /
214 );
215
216 sub create_date {
217     my( $self, $conn, $auth, $date, $emergency ) = @_;
218
219     my $e = new_editor(authtoken=>$auth, xact =>1);
220     return $e->die_event unless $e->checkauth;
221     
222     return $e->die_event unless $e->allowed(
223         'actor.org_unit.closed_date.create', $date->org_unit);
224
225     if ($emergency) {
226         return $e->die_event
227             unless $e->allowed('EMERGENCY_CLOSING', $date->org_unit);
228         $e->create_action_emergency_closing($emergency)
229             or return $e->die_event;
230         $date->emergency_closing($emergency->id);
231     }
232
233     $e->create_actor_org_unit_closed_date($date) or return $e->die_event;
234
235     my $newobj = $e->retrieve_actor_org_unit_closed_date($date->id)
236         or return $e->die_event;
237
238     $newobj->emergency_closing(
239         $e->retrieve_action_emergency_closing($newobj->emergency_closing)
240     ) if $emergency;
241
242     $e->commit;
243     return $newobj;
244 }
245
246
247 __PACKAGE__->register_method(
248     method => 'edit_date',
249     api_name    => 'open-ils.actor.org_unit.closed.update',
250     signature   => q/
251         Updates a closed date object
252     /
253 );
254
255 sub edit_date {
256     my( $self, $conn, $auth, $date ) = @_;
257     my $e = new_editor(authtoken=>$auth, xact =>1);
258     return $e->die_event unless $e->checkauth;
259     
260     # First make sure they have the right to update the selected date object
261     my $odate = $e->retrieve_actor_org_unit_closed_date($date->id) 
262         or return $e->die_event;
263
264     if ($odate->emergency_closing) {
265         return $e->die_event unless $e->allowed(
266             'EMERGENCY_CLOSING', $odate->org_unit);
267     }
268
269     return $e->die_event unless $e->allowed(
270         'actor.org_unit.closed_date.update', $odate->org_unit);
271
272     $e->update_actor_org_unit_closed_date($date) or return $e->die_event;
273
274     my $newobj = $e->retrieve_actor_org_unit_closed_date($date->id)
275         or return $e->die_event;
276
277     $newobj->emergency_closing(
278         $e->retrieve_action_emergency_closing($newobj->emergency_closing)
279     ) if $odate->emergency_closing;
280
281     $e->commit;
282
283     return $newobj;
284 }
285
286
287 __PACKAGE__->register_method(
288     method  => 'is_probably_emergency_closing',
289     api_name    => 'open-ils.actor.org_unit.closed_date.emergency_test',
290     signature   => q/
291         Returns a truthy value if the closing start date is either in
292         the past or is nearer in the future than the longest configured
293         circulation duration.
294         @param auth An auth token
295         @param date A closed date object
296     /
297 );
298 sub is_probably_emergency_closing {
299     my( $self, $conn, $auth, $date ) = @_;
300     my $e = new_editor(authtoken=>$auth);
301     return $e->event unless $e->checkauth;
302
303     # First, when is it?
304     my $start_seconds = DateTime::Format::ISO8601->parse_datetime(
305         clean_ISO8601($date->close_start)
306     )->epoch;
307
308     # Is it in the past?
309     return 1 if ($start_seconds < time); # It is!
310
311     # No? Let's see if it's coming up sooner than
312     # the currently-furthest normal due date...
313     my $rules = $e->search_config_rules_circ_duration({
314         extended => {
315             '>'     => {
316                 transform => 'interval_pl_timestamptz',
317                 params    => ['now'],
318                 value     => $date->close_start
319             }
320         }
321     }); # That is basically: WHERE 'now'::timestamptz + extended > $date->close_start
322         # which translates to "the closed start happens earlier than the theoretically
323         # latest due date we could currently have, so it might need emergency
324         # treatment.
325
326     return scalar(@$rules); # No rows means "not emergency".
327 }
328
329
330 __PACKAGE__->register_method(
331     method  => 'closed_dates_overlap',
332     api_name    => 'open-ils.actor.org_unit.closed_date.overlap',
333     signature   => q/
334         Returns an object with 'start' and 'end' fields 
335         start is the first day the org is open going backwards from 
336         'date'.  end is the next day the org is open going
337         forward from 'date'.
338         @param auth An auth token
339         @param orgid The org unit in question
340         @param date The date to search
341     /
342 );
343 sub closed_dates_overlap {
344     my( $self, $conn, $auth, $orgid, $date ) = @_;
345     my $e = new_editor(authtoken=>$auth);
346     return $e->event unless $e->checkauth;
347     return $e->request(
348         'open-ils.storage.actor.org_unit.closed_date.overlap', $orgid, $date );
349 }
350
351
352
353
354 1;