]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/offline/offline.pl
Offline checkin & renewal checks, & ou settings
[working/Evergreen.git] / Open-ILS / src / offline / offline.pl
1 #!/usr/bin/perl
2 use strict; use warnings;
3 use CGI;
4 use OpenSRF::Utils::JSON;
5 use OpenSRF::System;
6 use OpenSRF::Utils::Logger qw/$logger/;
7 use OpenILS::Application::AppUtils;
8 use OpenILS::Event;
9 use OpenSRF::EX qw/:try/;
10 use Data::Dumper;
11 use OpenILS::Utils::Fieldmapper;
12 use Digest::MD5 qw/md5_hex/;
13 use OpenSRF::Utils qw/:daemon cleanse_ISO8601/;
14 use OpenILS::Utils::OfflineStore;
15 use OpenSRF::Utils::SettingsClient;
16 use OpenSRF::Utils;
17 use DateTime;
18 use DateTime::Format::ISO8601;
19
20 use DBI;
21 $DBI::trace = 1;
22
23 my $U = "OpenILS::Application::AppUtils";
24 my $DB = "OpenILS::Utils::OfflineStore";
25 my $SES = "${DB}::Session";
26 my $SCRIPT = "OpenILS::Utils::OfflineStore::Script";
27 my $user_groups;
28
29 # --------------------------------------------------------------------
30 # Load the config
31 # --------------------------------------------------------------------
32 our %config;
33 do '##CONFIG##/offline-config.pl';
34
35
36 my $cgi                 = new CGI;
37 my $basedir             = $config{base_dir} || die "Offline config error: no base_dir defined\n";
38 my $bootstrap   = $config{bootstrap} || die "Offline config error: no bootstrap defined\n";
39 my $wsname              = $cgi->param('ws');
40 my $org                 = $cgi->param('org');
41 my $authtoken   = $cgi->param('ses') || "";
42 my $seskey              = $cgi->param('seskey');
43 my $action              = $cgi->param('action'); # - create, load, execute, status
44 my $requestor; 
45 my $wsobj;
46 my $orgobj;
47 my $evt;
48
49
50 &ol_init;
51 &ol_runtime_init;
52 &ol_do_action;
53
54
55 # --------------------------------------------------------------------
56 # Set it all up
57 # This function should behave as a child_init might behave in case 
58 # this is moved to mod_perl
59 # --------------------------------------------------------------------
60 sub ol_init {
61         $DB->DBFile($config{dsn}, $config{usr}, $config{pw});
62         ol_connect();
63 }
64
65 sub ol_connect {
66         OpenSRF::System->bootstrap_client(config_file => $bootstrap ); 
67         Fieldmapper->import(IDL => 
68                 OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
69
70 }
71
72
73 sub _ol_debug_params {
74         my $s = "";
75         my @params = $cgi->param;
76         @params = sort { $a cmp $b } @params;
77         $s .= "$_=" . $cgi->param($_) . "\n" for @params;
78         $s =~ s/\n$//o;
79         warn '-'x60 ."\n$s\n";
80 }
81
82
83 # --------------------------------------------------------------------
84 # Finds the requestor and other info specific to this request
85 # --------------------------------------------------------------------
86 sub ol_runtime_init {
87
88         # fetch the requestor object
89         ($requestor, $evt) = $U->checkses($authtoken);
90         ol_handle_result($evt) if $evt;
91
92         # try the param, the workstation, and finally the user's ws org
93         if(!$org) { 
94                 $wsobj = ol_fetch_workstation($wsname);
95                 $org = $wsobj->owning_lib if $wsobj;
96                 $org = $requestor->ws_ou unless $org;
97                 ol_handle_result(OpenILS::Event->new('OFFLINE_NO_ORG')) unless $org;
98         }
99
100     $user_groups = $U->simplereq(
101         'open-ils.actor', 'open-ils.actor.groups.retrieve');
102 }
103
104
105 # --------------------------------------------------------------------
106 # Runs the requested action
107 # --------------------------------------------------------------------
108 sub ol_do_action {
109
110         my $payload;
111
112         if( $action eq 'create' ) {
113                 
114                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
115                 ol_handle_result($evt) if $evt;
116                 $payload = ol_create_session();
117
118         } elsif( $action eq 'load' ) {
119
120                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
121                 ol_handle_result($evt) if $evt;
122                 $payload = ol_load();
123
124         } elsif( $action eq 'execute' ) {
125
126                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_EXECUTE');
127                 ol_handle_result($evt) if $evt;
128                 $payload = ol_execute();
129
130         } elsif( $action eq 'status' ) {
131
132                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_VIEW');
133                 ol_handle_result($evt) if $evt;
134                 $payload = ol_status();
135         }
136
137         ol_handle_event('SUCCESS', payload => $payload );
138 }
139
140
141 # --------------------------------------------------------------------
142 # Creates a new session
143 # --------------------------------------------------------------------
144 sub ol_create_session {
145
146         my $desc = $cgi->param('desc') || "";
147         $seskey = time . "_${$}_" . int(rand() * 1000);
148
149         $logger->debug("offline: user ".$requestor->id.
150                 " creating new session with key $seskey and description $desc");
151
152         $SES->create(
153                 {       
154                         key                             => $seskey,
155                         org                             => $org,
156                         description             => $desc,
157                         creator                 => $requestor->id,
158                         create_time             => CORE::time(), 
159                         num_complete    => 0,
160                 } 
161         );
162
163         return $seskey;
164 }
165
166
167 # --------------------------------------------------------------------
168 # Holds the meta-info for a script file
169 # --------------------------------------------------------------------
170 sub ol_create_script {
171         my $count = shift;
172
173         my $session = ol_find_session($seskey);
174         my $delta = $cgi->param('delta') || 0;
175
176         my $script = $session->add_to_scripts( 
177                 {
178                         requestor       => $requestor->id,
179                         create_time     => CORE::time,
180                         workstation     => $wsname,
181                         logfile         => "$basedir/pending/$org/$seskey/$wsname.log",
182                         time_delta      => $delta,
183                         count                   => $count,
184                 }
185         );
186 }
187
188 # --------------------------------------------------------------------
189 # Finds the current session in the db
190 # --------------------------------------------------------------------
191 sub ol_find_session {
192         my $ses = $SES->retrieve($seskey);
193         ol_handle_event('OFFLINE_INVALID_SESSION', payload => $seskey) unless $ses;
194         return $ses;
195 }
196
197 # --------------------------------------------------------------------
198 # Finds a script object in the DB based on workstation and seskey
199 # --------------------------------------------------------------------
200 sub ol_find_script {
201         my $ws = shift || $wsname;
202         my $sk = shift || $seskey;
203         my ($script) = $SCRIPT->search( session => $seskey, workstation => $ws );
204         return $script;
205 }
206
207 # --------------------------------------------------------------------
208 # Creates a new script in the database and loads the new script file
209 # --------------------------------------------------------------------
210 sub ol_load {
211
212         my $session = ol_find_session;
213         my $handle      = $cgi->upload('file');
214         my $outdir      = "$basedir/pending/$org/$seskey";
215         my $outfile = "$outdir/$wsname.log";
216
217         ol_handle_event('OFFLINE_SESSION_FILE_EXISTS') if ol_find_script();
218         ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
219         ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
220
221         qx/mkdir -p $outdir/;
222         my $x = 0;
223         open(FILE, ">>$outfile") or ol_handle_event('OFFLINE_FILE_ERROR');
224         while( <$handle> ) { print FILE; $x++;}
225         close(FILE);
226
227         ol_create_script($x);
228
229         return undef;
230 }
231
232
233 # --------------------------------------------------------------------
234 sub ol_handle_result {
235         my $obj = shift;
236         my $json = OpenSRF::Utils::JSON->perl2JSON($obj);
237
238         # Clear this so it's not remembered
239         $evt = undef;
240
241         if( $cgi->param('html')) {
242                 my $html = "<html><body onload='xulG.handle_event($json)'></body></html>";
243                 print "content-type: text/html\n\n";
244                 print "$html\n";
245
246         } else {
247
248                 print "content-type: text/plain\n\n";
249                 print "$json\n";
250         }
251
252         exit(0);
253 }
254
255 # --------------------------------------------------------------------
256 sub ol_handle_event {
257         my( $evt, @args ) = @_;
258         ol_handle_result(OpenILS::Event->new($evt, @args));
259 }
260
261
262 # --------------------------------------------------------------------
263 sub ol_flesh_session {
264         my $session = shift;
265         my %data;
266
267         map { $data{$_} = $session->$_ } $session->columns;
268         $data{scripts} = [];
269
270         for my $script ($session->scripts) {
271                 my %sdata;
272                 map { $sdata{$_} = $script->$_ } $script->columns;
273
274                 # the client doesn't need this info
275                 delete $sdata{session};
276                 delete $sdata{id};
277                 delete $sdata{logfile};
278
279                 push( @{$data{scripts}}, \%sdata );
280         }
281
282         return \%data;
283 }
284
285
286 # --------------------------------------------------------------------
287 # Returns various information on the sessions and scripts
288 # --------------------------------------------------------------------
289 sub ol_status {
290
291         my $type = $cgi->param('status_type') || "scripts";
292
293         # --------------------------------------------------------------------
294         # Returns info on every script upload attached to the current session
295         # --------------------------------------------------------------------
296         if( $type eq 'scripts' ) {
297                 my $session = ol_find_session();
298                 ol_handle_result(ol_flesh_session($session));
299
300
301         # --------------------------------------------------------------------
302         # Returns all scripts and sessions for the given org
303         # --------------------------------------------------------------------
304         } elsif( $type eq 'sessions' ) {
305                 my @sessions = $SES->search( org => $org );
306
307                 # can I do this in the DB without raw SQL?
308                 @sessions = sort { $a->create_time <=> $b->create_time } @sessions; 
309                 my @data;
310                 push( @data, ol_flesh_session($_) ) for @sessions;
311                 ol_handle_result(\@data);
312
313
314         # --------------------------------------------------------------------
315         # Returns total commands and completed commands counts
316         # --------------------------------------------------------------------
317         } elsif( $type eq 'summary' ) {
318                 my $session = ol_find_session();
319
320                 $logger->debug("offline: retrieving summary info ".
321                         "for session ".$session->key." with completed=".$session->num_complete);
322
323                 my $count = 0;
324                 $count += $_->count for ($session->scripts);
325                 ol_handle_result(
326                         { total => $count, num_complete => $session->num_complete });
327
328
329
330         # --------------------------------------------------------------------
331         # Returns the list of non-SUCCESS events that have occurred so far for 
332         # this set of commands
333         # --------------------------------------------------------------------
334         } elsif( $type eq 'exceptions' ) {
335
336                 my $session = ol_find_session();
337                 my $resfile = "$basedir/pending/$org/$seskey/results";
338                 if( $session->end_time ) {
339                         $resfile = "$basedir/archive/$org/$seskey/results";
340                 }
341                 my $data = ol_file_to_perl($resfile);
342         my $data2 = [];
343         for my $d (@$data) {
344             my $evt = $d->{event};
345             $evt = $evt->[0] if ref $evt eq 'ARRAY';
346             push(@$data2, $d) if $evt->{ilsevent} ne '0';
347         }
348                 #$data = [ grep { $_->{event}->{ilsevent} ne '0' } @$data ];
349                 ol_handle_result($data2);
350         }
351 }
352
353
354 sub ol_fetch_workstation {
355         my $name = shift;
356         $logger->debug("offline: Fetching workstation $name");
357         my $ws = $U->storagereq(
358                 'open-ils.storage.direct.actor.workstation.search.name', $name);
359         ol_handle_result(OpenILS::Event->new('ACTOR_WORKSTATION_NOT_FOUND')) unless $ws;
360         return $ws;
361 }
362
363
364
365
366 # --------------------------------------------------------------------
367 # Sorts the script commands then forks a child to executes them.
368 # --------------------------------------------------------------------
369 sub ol_execute {
370
371         my $session = ol_find_session();
372         ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
373         ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
374
375         my $commands = ol_collect_commands();
376
377         # --------------------------------------------------------------------
378         # Note that we must disconnect from opensrf before forking or the 
379         # connection will be borked...
380         # --------------------------------------------------------------------
381         OpenSRF::Transport::PeerHandle->retrieve->disconnect;
382         $DB->disconnect;
383
384
385         if( safe_fork() ) {
386
387                 # --------------------------------------------------------------------
388                 # Tell the client all is well
389                 # --------------------------------------------------------------------
390                 ol_handle_event('SUCCESS'); # - this exits
391
392         } else {
393
394
395                 # --------------------------------------------------------------------
396                 # close stdout/stderr or apache will wait on the child to finish
397                 # --------------------------------------------------------------------
398                 close(STDOUT);
399                 close(STDERR);
400
401                 $logger->debug("offline: child $$ processing data...");
402
403                 # --------------------------------------------------------------------
404                 # The child re-connects to the opensrf network and processes
405                 # the script requests 
406                 # --------------------------------------------------------------------
407                 OpenSRF::System->bootstrap_client(config_file => $bootstrap);
408         
409                 try {
410
411                         #use Class::DBI
412                         #Class::DBI->autoupdate(1);
413
414                         $DB->autoupdate(1);
415
416                         my $sesion = ol_find_session();
417                         $session->in_process(1);
418                         ol_process_commands($session, $commands);
419                         ol_archive_files($session);
420
421                 } catch Error with {
422                         my $e = shift;
423                         $logger->error("offline: child process error $e");
424                 };
425         }
426 }
427
428 sub ol_file_to_perl {
429         my $fname = shift;
430         open(F, "$fname") or ol_handle_event('OFFLINE_FILE_ERROR');
431         my @d = <F>;
432         my @p;
433         push(@p, OpenSRF::Utils::JSON->JSON2perl($_)) for @d;
434         close(F);
435         return \@p;
436 }
437
438 # collects the commands and sorts them on timestamp+delta
439 sub ol_collect_commands {
440         my $ses = ol_find_session();
441         my @commands;
442
443         # cycle through every script loaded to this session
444         for my $script ($ses->scripts) {
445                 my $coms = ol_file_to_perl($script->logfile);
446
447                 # cycle through all of the commands for this script
448                 for my $com (@$coms) {
449                         $$com{_workstation} = $script->workstation;
450                         $$com{_realtime} = $script->time_delta + $com->{timestamp};
451                         push( @commands, $com );
452                 }
453         }
454
455         # make sure thera are no blank commands
456         @commands = grep { ($_ and $_->{type}) } @commands;
457
458         # sort on realtime
459         @commands = sort { $a->{_realtime} <=> $b->{_realtime} } @commands;
460
461         # push user registrations to the front
462         my @regs                = grep { $_->{type} eq 'register' } @commands;
463         my @others      = grep { $_->{type} ne 'register' } @commands;
464
465         return [ @regs, @others ];
466 }
467
468 sub ol_date {
469         my $time = shift || CORE::time;
470         my (undef,undef, undef, $mday,$mon,$year) = localtime($time);
471         $mon++; $year   += 1900;
472         $mday   = "0$mday" unless $mday =~ /\d{2}/o;
473         $mon    = "0$mon" unless $mon   =~ /\d{2}/o;
474         return ($year, $mon, $mday);
475 }
476
477
478 # --------------------------------------------------------------------
479 # Moves all files from the pending directory to the archive directory
480 # and removes the pending directory
481 # --------------------------------------------------------------------
482 sub ol_archive_files {
483         my $session = shift;
484         my ($y, $m, $d) = ol_date();
485
486         my $dir = "$basedir/pending/$org/$seskey";
487         my $archdir = "$basedir/archive/$org/$seskey";
488         $logger->debug("offline: archiving files to $archdir");
489
490         # Tell the db the files are moving
491         $_->logfile($archdir.'/'.$_->workstation.'.log') for ($session->scripts);
492
493         qx/mkdir -p $archdir/;
494         qx/mv $_ $archdir/ for <$dir/*>;
495         qx/rmdir $dir/;
496 }
497
498
499 # --------------------------------------------------------------------
500 # Appends results to the results file.
501 # --------------------------------------------------------------------
502 my $rhandle;
503 sub ol_append_result {
504
505         my $obj = shift;
506         my $last = shift;
507
508         $obj = OpenSRF::Utils::JSON->perl2JSON($obj);
509
510         if(!$rhandle) {
511                 open($rhandle, ">>$basedir/pending/$org/$seskey/results") 
512                         or ol_handle_event('OFFLINE_FILE_ERROR');
513         }
514
515         print $rhandle "$obj\n";
516         close($rhandle) if $last;
517 }
518
519
520
521 # --------------------------------------------------------------------
522 # Runs the commands and returns the list of errors
523 # --------------------------------------------------------------------
524 sub ol_process_commands {
525
526         my $session      = shift;
527         my $commands = shift;
528         my $x        = 0;
529
530         $session->start_time(CORE::time);
531
532         for my $d ( @$commands ) {
533
534                 my $t           = $d->{type};
535                 my $last = ($x++ == scalar(@$commands) - 1) ? 1 : 0;
536                 my $res = { command => $d };
537                 my $err;
538
539                 while( 1 ) {
540
541                         $err = undef;
542                         $logger->debug("offline: top of execute loop : $t");
543
544                         try {
545                                 $res->{event} = ol_handle_checkin($d)   if $t eq 'checkin';
546                                 $res->{event} = ol_handle_inhouse($d)   if $t eq 'in_house_use';
547                                 $res->{event} = ol_handle_checkout($d) if $t eq 'checkout';
548                                 $res->{event} = ol_handle_renew($d)             if $t eq 'renew';
549                                 $res->{event} = ol_handle_register($d) if $t eq 'register';
550         
551                         } catch Error with { $err = shift; };
552
553                         if( $err ) {
554
555                                 if( ref($err) eq 'OpenSRF::EX::JabberDisconnected' ) {
556                                         $logger->error("offline: we lost jabber .. trying to reconnect");
557                                         ol_connect();
558
559                                 } else {
560                                         $res->{event} = OpenILS::Event->new('INTERNAL_SERVER_ERROR', debug => "$err");
561                                         last;
562                                 }
563
564                         } else { last; }
565
566                         sleep(1);
567                 }
568
569                 ol_append_result($res, $last);
570                 $session->num_complete( $session->num_complete + 1 );
571
572                 $logger->debug("offline: set session [".$session->key."] num_complete to ".$session->num_complete);
573         }
574
575         $session->end_time(CORE::time);
576         $session->in_process(0);
577 }
578
579
580 # --------------------------------------------------------------------
581 # Runs an in_house_use action
582 # --------------------------------------------------------------------
583 sub ol_handle_inhouse {
584
585         my $command             = shift;
586         my $realtime    = $command->{_realtime};
587         my $ws                  = $command->{_workstation};
588         my $barcode             = $command->{barcode};
589         my $count               = $command->{count} || 1;
590         my $use_time    = $command->{use_time} || "";
591
592         $logger->activity("offline: in_house_use : requestor=". $requestor->id.", realtime=$realtime, ".  
593                 "workstation=$ws, barcode=$barcode, count=$count, use_time=$use_time");
594
595         if( $count > 99 ) {
596                 return OpenILS::Event->new(
597                         'INTERNAL_SERVER_ERROR', payload => 'TOO MANY IN HOUSE USE');
598         }
599
600         my $ids = $U->simplereq(
601                 'open-ils.circ', 
602                 'open-ils.circ.in_house_use.create', $authtoken, 
603                 { barcode => $barcode, count => $count, location => $org, use_time => $use_time } );
604         
605         return OpenILS::Event->new('SUCCESS', payload => $ids) if( ref($ids) eq 'ARRAY' );
606         return $ids;
607 }
608
609
610
611 # --------------------------------------------------------------------
612 # Pulls the relevant circ args from the command, fetches data where 
613 # necessary
614 # --------------------------------------------------------------------
615 my %user_id_cache;
616 sub ol_circ_args_from_command {
617         my $command = shift;
618
619         my $type                        = $command->{type};
620         my $realtime    = $command->{_realtime};
621         my $ws                  = $command->{_workstation};
622         my $barcode             = $command->{barcode} || "";
623         my $cotime              = $command->{checkout_time} || "";
624         my $pbc                 = $command->{patron_barcode};
625         my $due_date    = $command->{due_date} || "";
626         my $noncat              = ($command->{noncat}) ? "yes" : "no"; # for logging
627
628         $logger->activity("offline: $type : requestor=". $requestor->id.
629                 ", realtime=$realtime, workstation=$ws, checkout_time=$cotime, ".
630                 "patron=$pbc, due_date=$due_date, noncat=$noncat");
631
632
633         my $args = { 
634                 permit_override => 1, 
635                 barcode         => $barcode,            
636                 checkout_time   => $cotime, 
637                 patron_barcode  => $pbc,
638                 due_date        => $due_date 
639     };
640
641     if(ol_get_org_setting('circ.offline.username_allowed')) {
642
643         my $format = ol_get_org_setting('opac.barcode_regex');
644         if($format) {
645
646             $format =~ s/^\/|\/$//g; # remove any couching //'s
647             if($pbc !~ qr/$format/) {
648
649                 # the patron barcode does not match the configured barcode format
650                 # assume they passed a username instead
651
652                 my $user_id = $user_id_cache{$pbc} ||
653                     $U->simplereq(
654                         'open-ils.actor', 
655                         'open-ils.actor.username.exists', 
656                         $authtoken, $pbc);
657
658                 
659                 if($user_id) {
660                     # a valid username was provided, update the args and cache
661                     $user_id_cache{$pbc} = $user_id;
662                     $args->{patron_id} = $user_id;
663                     delete $args->{patron_barcode};
664                 }
665             }
666         }
667     }
668
669
670         if( $command->{noncat} ) {
671                 $args->{noncat} = 1;
672                 $args->{noncat_type} = $command->{noncat_type};
673                 $args->{noncat_count} = $command->{noncat_count};
674         }
675
676         return $args;
677 }
678
679 sub ol_get_org_setting {
680     my $name = shift;
681     return $U->simplereq(
682         'open-ils.actor',
683         'open-ils.actor.ou_setting.ancestor_default',
684         $org, $name, $authtoken);
685 }
686
687
688
689 # --------------------------------------------------------------------
690 # Performs a checkout action
691 # --------------------------------------------------------------------
692 sub ol_handle_checkout {
693         my $command     = shift;
694         my $args = ol_circ_args_from_command($command);
695
696         if( $args->{noncat} and $args->{noncat_count} > 99 ) {
697                 return OpenILS::Event->new(
698                         'INTERNAL_SERVER_ERROR', payload => 'TOO MANY NON CATS');
699         }
700
701     if( $args->{barcode} ) {
702
703         # $c becomes the Copy
704         # $e possibily becomes the Exception
705         my( $c, $e ) = $U->fetch_copy_by_barcode($args->{barcode});
706         return $e if $e;
707
708         my $barcode = $args->{barcode};
709         # Have to have this config option (or org setting) and a
710         # status_changed_time for skippage
711         if ((
712                 ol_get_org_setting(
713                     'circ.offline.skip_checkout_if_newer_status_changed_time'
714                 )
715                 || $config{skip_late}
716             )
717             && length($c->status_changed_time())
718         ) {
719             my $cts = DateTime::Format::ISO8601->parse_datetime( cleanse_ISO8601($c->status_changed_time()) )->epoch();
720             my $xts = $command->{timestamp}; # Transaction Time Stamp
721             $logger->activity("offline: ol_handle_checkout: barcode=$barcode, cts=$cts, xts=$xts");
722
723             # Asset has changed after this transaction, ignore
724             if ($cts >= $xts) {
725                 return OpenILS::Event->new(
726                     'SKIP_ASSET_CHANGED'
727                 );
728             }
729     #       $logger->activity("offline: fetch_copy_by_barcode: " . Dumper($c->real_fields()));
730         }
731     }
732
733     my $evt = $U->simplereq(
734                 'open-ils.circ', 'open-ils.circ.checkout', $authtoken, $args );
735
736     # if the item is already checked out to this user and we are past 
737     # the configured auto-renewal interval, try to renew the circ.
738     if( ref $evt ne 'ARRAY' and
739         $evt->{textcode} == 'OPEN_CIRCULATION_EXISTS' and 
740         $evt->{payload}->{auto_renew}) {
741
742             return ol_handle_renew($command);
743     }
744
745     return $evt;
746 }
747
748
749 # --------------------------------------------------------------------
750 # Performs the renewal action
751 # --------------------------------------------------------------------
752 sub ol_handle_renew {
753         my $command = shift;
754         my $args = ol_circ_args_from_command($command);
755         my $t = time;
756
757     if( $args->{barcode} ) {
758
759         # $c becomes the Copy
760         # $e possibily becomes the Exception
761         my( $c, $e ) = $U->fetch_copy_by_barcode($args->{barcode});
762         return $e if $e;
763
764         my $barcode = $args->{barcode};
765         # Have to have this config option (or org setting) and a
766         # status_changed_time for skippage
767         if ((
768                 ol_get_org_setting(
769                     'circ.offline.skip_renew_if_newer_status_changed_time'
770                 )
771                 || $config{skip_late}
772             )
773             && length($c->status_changed_time())
774         ) {
775             my $cts = DateTime::Format::ISO8601->parse_datetime( cleanse_ISO8601($c->status_changed_time()) )->epoch();
776             my $xts = $command->{timestamp}; # Transaction Time Stamp
777             $logger->activity("offline: ol_handle_renew: barcode=$barcode, cts=$cts, xts=$xts");
778
779             # Asset has changed after this transaction, ignore
780             if ($cts >= $xts) {
781                 return OpenILS::Event->new(
782                     'SKIP_ASSET_CHANGED'
783                 );
784             }
785         }
786     }
787
788         return $U->simplereq(
789                 'open-ils.circ', 'open-ils.circ.renew.override', $authtoken, $args );
790 }
791
792
793 # --------------------------------------------------------------------
794 # Runs a checkin action
795 # --------------------------------------------------------------------
796 sub ol_handle_checkin {
797
798         my $command             = shift;
799         my $realtime    = $command->{_realtime};
800         my $ws                  = $command->{_workstation};
801         my $barcode             = $command->{barcode};
802         my $backdate    = $command->{backdate} || "";
803
804         $logger->activity("offline: checkin : requestor=". $requestor->id.
805                 ", realtime=$realtime, ".  "workstation=$ws, barcode=$barcode, backdate=$backdate");
806
807     if( $barcode ) {
808
809         # $c becomes the Copy
810         # $e possibily becomes the Exception
811         my( $c, $e ) = $U->fetch_copy_by_barcode($barcode);
812         return $e if $e;
813
814         # Have to have this config option (or org setting) and a
815         # status_changed_time for skippage
816         if ((
817                 ol_get_org_setting(
818                     'circ.offline.skip_checkin_if_newer_status_changed_time'
819                 )
820                 || $config{skip_late}
821             )
822             && length($c->status_changed_time())
823         ) {
824             my $cts = DateTime::Format::ISO8601->parse_datetime( cleanse_ISO8601($c->status_changed_time()) )->epoch();
825             my $xts = $command->{timestamp}; # Transaction Time Stamp
826             $logger->activity("offline: ol_handle_checkin: barcode=$barcode, cts=$cts, xts=$xts");
827
828             # Asset has changed after this transaction, ignore
829             if ($cts >= $xts) {
830                 return OpenILS::Event->new(
831                     'SKIP_ASSET_CHANGED'
832                 );
833             }
834         }
835     }
836
837         return $U->simplereq(
838                 'open-ils.circ', 
839                 'open-ils.circ.checkin', $authtoken,
840                 { barcode => $barcode, backdate => $backdate } );
841 }
842
843
844
845 # --------------------------------------------------------------------
846 # Registers a new patron
847 # --------------------------------------------------------------------
848 sub ol_handle_register {
849         my $command = shift;
850
851         my $barcode = $command->{user}->{card}->{barcode};
852         delete $command->{user}->{card}; 
853
854         $logger->info("offline: creating new user with barcode $barcode");
855
856         # now, create the user
857         my $actor       = Fieldmapper::actor::user->new;
858         my $card                = Fieldmapper::actor::card->new;
859
860
861         # username defaults to the barcode
862         $actor->usrname( ($actor->usrname) ? $actor->usrname : $barcode );
863
864         # Set up all of the virtual IDs, isnew, etc.
865         $actor->isnew(1);
866         $actor->id(-1);
867         $actor->card(-1);
868         $actor->cards([$card]);
869
870         $card->isnew(1);
871         $card->id(-1);
872         $card->usr(-1);
873         $card->barcode($barcode);
874
875         my $billing_address;
876         my $mailing_address;
877
878         my @sresp;
879         for my $resp (@{$command->{user}->{survey_responses}}) {
880                 my $sr = Fieldmapper::action::survey_response->new;
881                 $sr->$_( $resp->{$_} ) for keys %$resp;
882                 $sr->isnew(1);
883                 $sr->usr(-1);
884                 push(@sresp, $sr);
885                 $logger->debug("offline: created new survey response for survey ".$sr->survey);
886         }
887         delete $command->{user}->{survey_responses};
888         $actor->survey_responses(\@sresp) if @sresp;
889
890         # extract the billing address
891         if( my $addr = $command->{user}->{billing_address} ) {
892                 $billing_address = Fieldmapper::actor::user_address->new;
893                 $billing_address->$_($addr->{$_}) for keys %$addr;
894                 $billing_address->isnew(1);
895                 $billing_address->id(-1);
896                 $billing_address->usr(-1);
897                 delete $command->{user}->{billing_address};
898                 $logger->debug("offline: read billing address ".$billing_address->street1);
899         }
900
901         # extract the mailing address
902         if( my $addr = $command->{user}->{mailing_address} ) {
903                 $mailing_address = Fieldmapper::actor::user_address->new;
904                 $mailing_address->$_($addr->{$_}) for keys %$addr;
905                 $mailing_address->isnew(1);
906                 $mailing_address->id(-2);
907                 $mailing_address->usr(-1);
908                 delete $command->{user}->{mailing_address};
909                 $logger->debug("offline: read mailing address ".$mailing_address->street1);
910         }
911
912         # make sure we have values for both
913         $billing_address ||= $mailing_address;
914         $mailing_address ||= $billing_address;
915
916         $actor->billing_address($billing_address->id);
917         $actor->mailing_address($mailing_address->id);
918         $actor->addresses([$mailing_address]);
919
920         push( @{$actor->addresses}, $billing_address ) 
921                 unless $billing_address->id eq $mailing_address->id;
922         
923         # pull all of the rest of the data from the command blob
924         $actor->$_( $command->{user}->{$_} ) for keys %{$command->{user}};
925
926     # calculate the expire date for the patron based on the profile group
927     my ($grp) = grep {$_->id == $actor->profile} @$user_groups;
928     if($grp) {
929         my $seconds = OpenSRF::Utils->interval_to_seconds($grp->perm_interval);
930         my $expire_date = DateTime->from_epoch(epoch => DateTime->now->epoch + $seconds)->epoch;
931                 $logger->debug("offline: setting expire date to $expire_date");
932         $actor->expire_date($U->epoch2ISO8601($expire_date));
933     }
934
935         $logger->debug("offline: creating user object...");
936         $actor = $U->simplereq(
937                 'open-ils.actor', 
938                 'open-ils.actor.patron.update', $authtoken, $actor);
939
940         return $actor if(ref($actor) eq 'HASH'); # an event occurred
941
942         return OpenILS::Event->new('SUCCESS', payload => $actor);
943 }
944
945
946
947
948
949
950