]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/offline/offline.pl
0ab17515036f1a4ea951c3458dede13e9febe72a
[Evergreen.git] / Open-ILS / src / offline / offline.pl
1 #!/usr/bin/perl
2 use strict; use warnings;
3 use CGI;
4 use 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/;
14 use OpenILS::Utils::OfflineStore;
15 use OpenSRF::Utils::SettingsClient;
16
17 use DBI;
18 $DBI::trace = 1;
19
20 my $U = "OpenILS::Application::AppUtils";
21 my $DB = "OpenILS::Utils::OfflineStore";
22 my $SES = "${DB}::Session";
23 my $SCRIPT = "OpenILS::Utils::OfflineStore::Script";
24
25 # --------------------------------------------------------------------
26 # Load the config
27 # --------------------------------------------------------------------
28 our %config;
29 do '##CONFIG##/offline-config.pl';
30
31
32 my $cgi                 = new CGI;
33 my $basedir             = $config{base_dir} || die "Offline config error: no base_dir defined\n";
34 my $bootstrap   = $config{bootstrap} || die "Offline config error: no bootstrap defined\n";
35 my $wsname              = $cgi->param('ws');
36 my $org                 = $cgi->param('org');
37 my $authtoken   = $cgi->param('ses') || "";
38 my $seskey              = $cgi->param('seskey');
39 my $action              = $cgi->param('action'); # - create, load, execute, status
40 my $requestor; 
41 my $wsobj;
42 my $orgobj;
43 my $evt;
44
45
46 &ol_init;
47 &ol_runtime_init;
48 &ol_do_action;
49
50
51 # --------------------------------------------------------------------
52 # Set it all up
53 # This function should behave as a child_init might behave in case 
54 # this is moved to mod_perl
55 # --------------------------------------------------------------------
56 sub ol_init {
57         #_ol_debug_params();
58         #$DB->DBFile($config{db});
59         $DB->DBFile($config{dsn}, $config{usr}, $config{pw});
60         OpenSRF::System->bootstrap_client(config_file => $bootstrap ); 
61         Fieldmapper->import(IDL => 
62                 OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
63 }
64
65
66 sub _ol_debug_params {
67         my $s = "";
68         my @params = $cgi->param;
69         @params = sort { $a cmp $b } @params;
70         $s .= "$_=" . $cgi->param($_) . "\n" for @params;
71         $s =~ s/\n$//o;
72         warn '-'x60 ."\n$s\n";
73 }
74
75
76 # --------------------------------------------------------------------
77 # Finds the requestor and other info specific to this request
78 # --------------------------------------------------------------------
79 sub ol_runtime_init {
80
81         # fetch the requestor object
82         ($requestor, $evt) = $U->checkses($authtoken);
83         ol_handle_result($evt) if $evt;
84
85         # try the param, the workstation, and finally the user's ws org
86         if(!$org) { 
87                 $wsobj = ol_fetch_workstation($wsname);
88                 $org = $wsobj->owning_lib if $wsobj;
89                 $org = $requestor->ws_ou unless $org;
90                 ol_handle_result(OpenILS::Event->new('OFFLINE_NO_ORG')) unless $org;
91         }
92 }
93
94
95 # --------------------------------------------------------------------
96 # Runs the requested action
97 # --------------------------------------------------------------------
98 sub ol_do_action {
99
100         my $payload;
101
102         if( $action eq 'create' ) {
103                 
104                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
105                 ol_handle_result($evt) if $evt;
106                 $payload = ol_create_session();
107
108         } elsif( $action eq 'load' ) {
109
110                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
111                 ol_handle_result($evt) if $evt;
112                 $payload = ol_load();
113
114         } elsif( $action eq 'execute' ) {
115
116                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_EXECUTE');
117                 ol_handle_result($evt) if $evt;
118                 $payload = ol_execute();
119
120         } elsif( $action eq 'status' ) {
121
122                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_VIEW');
123                 ol_handle_result($evt) if $evt;
124                 $payload = ol_status();
125         }
126
127         ol_handle_event('SUCCESS', payload => $payload );
128 }
129
130
131 # --------------------------------------------------------------------
132 # Creates a new session
133 # --------------------------------------------------------------------
134 sub ol_create_session {
135
136         my $desc = $cgi->param('desc') || "";
137         $seskey = time . "_${$}_" . int(rand() * 1000);
138
139         $logger->debug("offline: user ".$requestor->id.
140                 " creating new session with key $seskey and description $desc");
141
142         $SES->create(
143                 {       
144                         key                             => $seskey,
145                         org                             => $org,
146                         description             => $desc,
147                         creator                 => $requestor->id,
148                         create_time             => CORE::time(), 
149                         num_complete    => 0,
150                 } 
151         );
152
153         return $seskey;
154 }
155
156
157 # --------------------------------------------------------------------
158 # Holds the meta-info for a script file
159 # --------------------------------------------------------------------
160 sub ol_create_script {
161         my $count = shift;
162
163         my $session = ol_find_session($seskey);
164         my $delta = $cgi->param('delta') || 0;
165
166         my $script = $session->add_to_scripts( 
167                 {
168                         requestor       => $requestor->id,
169                         create_time     => CORE::time,
170                         workstation     => $wsname,
171                         logfile         => "$basedir/pending/$org/$seskey/$wsname.log",
172                         time_delta      => $delta,
173                         count                   => $count,
174                 }
175         );
176 }
177
178 # --------------------------------------------------------------------
179 # Finds the current session in the db
180 # --------------------------------------------------------------------
181 sub ol_find_session {
182         my $ses = $SES->retrieve($seskey);
183         ol_handle_event('OFFLINE_INVALID_SESSION', payload => $seskey) unless $ses;
184         return $ses;
185 }
186
187 # --------------------------------------------------------------------
188 # Finds a script object in the DB based on workstation and seskey
189 # --------------------------------------------------------------------
190 sub ol_find_script {
191         my $ws = shift || $wsname;
192         my $sk = shift || $seskey;
193         my ($script) = $SCRIPT->search( session => $seskey, workstation => $ws );
194         return $script;
195 }
196
197 # --------------------------------------------------------------------
198 # Creates a new script in the database and loads the new script file
199 # --------------------------------------------------------------------
200 sub ol_load {
201
202         my $session = ol_find_session;
203         my $handle      = $cgi->upload('file');
204         my $outdir      = "$basedir/pending/$org/$seskey";
205         my $outfile = "$outdir/$wsname.log";
206
207         ol_handle_event('OFFLINE_SESSION_FILE_EXISTS') if ol_find_script();
208         ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
209         ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
210
211         qx/mkdir -p $outdir/;
212         my $x = 0;
213         open(FILE, ">>$outfile") or ol_handle_event('OFFLINE_FILE_ERROR');
214         while( <$handle> ) { print FILE; $x++;}
215         close(FILE);
216
217         ol_create_script($x);
218
219         return undef;
220 }
221
222
223 # --------------------------------------------------------------------
224 sub ol_handle_result {
225         my $obj = shift;
226         my $json = JSON->perl2JSON($obj);
227
228         # Clear this so it's not remembered
229         $evt = undef;
230
231         if( $cgi->param('html')) {
232                 my $html = "<html><body onload='xulG.handle_event($json)'></body></html>";
233                 print "content-type: text/html\n\n";
234                 print "$html\n";
235
236         } else {
237
238                 print "content-type: text/plain\n\n";
239                 print "$json\n";
240         }
241
242         exit(0);
243 }
244
245 # --------------------------------------------------------------------
246 sub ol_handle_event {
247         my( $evt, @args ) = @_;
248         ol_handle_result(OpenILS::Event->new($evt, @args));
249 }
250
251
252 # --------------------------------------------------------------------
253 sub ol_flesh_session {
254         my $session = shift;
255         my %data;
256
257         map { $data{$_} = $session->$_ } $session->columns;
258         $data{scripts} = [];
259
260         for my $script ($session->scripts) {
261                 my %sdata;
262                 map { $sdata{$_} = $script->$_ } $script->columns;
263
264                 # the client doesn't need this info
265                 delete $sdata{session};
266                 delete $sdata{id};
267                 delete $sdata{logfile};
268
269                 push( @{$data{scripts}}, \%sdata );
270         }
271
272         return \%data;
273 }
274
275
276 # --------------------------------------------------------------------
277 # Returns various information on the sessions and scripts
278 # --------------------------------------------------------------------
279 sub ol_status {
280
281         my $type = $cgi->param('status_type') || "scripts";
282
283         # --------------------------------------------------------------------
284         # Returns info on every script upload attached to the current session
285         # --------------------------------------------------------------------
286         if( $type eq 'scripts' ) {
287                 my $session = ol_find_session();
288                 ol_handle_result(ol_flesh_session($session));
289
290
291         # --------------------------------------------------------------------
292         # Returns all scripts and sessions for the given org
293         # --------------------------------------------------------------------
294         } elsif( $type eq 'sessions' ) {
295                 my @sessions = $SES->search( org => $org );
296
297                 # can I do this in the DB without raw SQL?
298                 @sessions = sort { $a->create_time <=> $b->create_time } @sessions; 
299                 my @data;
300                 push( @data, ol_flesh_session($_) ) for @sessions;
301                 ol_handle_result(\@data);
302
303
304         # --------------------------------------------------------------------
305         # Returns total commands and completed commands counts
306         # --------------------------------------------------------------------
307         } elsif( $type eq 'summary' ) {
308                 my $session = ol_find_session();
309
310                 $logger->debug("offline: retrieving summary info ".
311                         "for session ".$session->key." with completed=".$session->num_complete);
312
313                 my $count = 0;
314                 $count += $_->count for ($session->scripts);
315                 ol_handle_result(
316                         { total => $count, num_complete => $session->num_complete });
317
318
319
320         # --------------------------------------------------------------------
321         # Returns the list of non-SUCCESS events that have occurred so far for 
322         # this set of commands
323         # --------------------------------------------------------------------
324         } elsif( $type eq 'exceptions' ) {
325
326                 my $session = ol_find_session();
327                 my $resfile = "$basedir/pending/$org/$seskey/results";
328                 if( $session->end_time ) {
329                         $resfile = "$basedir/archive/$org/$seskey/results";
330                 }
331                 my $data = ol_file_to_perl($resfile);
332                 $data = [ grep { $_->{event}->{ilsevent} ne '0' } @$data ];
333                 ol_handle_result($data);
334         }
335 }
336
337
338 sub ol_fetch_workstation {
339         my $name = shift;
340         $logger->debug("offline: Fetching workstation $name");
341         my $ws = $U->storagereq(
342                 'open-ils.storage.direct.actor.workstation.search.name', $name);
343         ol_handle_result(OpenILS::Event->new('ACTOR_WORKSTATION_NOT_FOUND')) unless $ws;
344         return $ws;
345 }
346
347
348
349
350 # --------------------------------------------------------------------
351 # Sorts the script commands then forks a child to executes them.
352 # --------------------------------------------------------------------
353 sub ol_execute {
354
355         my $session = ol_find_session();
356         ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
357         ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
358
359         my $commands = ol_collect_commands();
360
361         # --------------------------------------------------------------------
362         # Note that we must disconnect from opensrf before forking or the 
363         # connection will be borked...
364         # --------------------------------------------------------------------
365         OpenSRF::Transport::PeerHandle->retrieve->disconnect;
366         $DB->disconnect;
367
368
369         if( safe_fork() ) {
370
371                 # --------------------------------------------------------------------
372                 # Tell the client all is well
373                 # --------------------------------------------------------------------
374                 ol_handle_event('SUCCESS'); # - this exits
375
376         } else {
377
378
379                 # --------------------------------------------------------------------
380                 # close stdout/stderr or apache will wait on the child to finish
381                 # --------------------------------------------------------------------
382                 close(STDOUT);
383                 close(STDERR);
384
385                 $logger->debug("offline: child $$ processing data...");
386
387                 # --------------------------------------------------------------------
388                 # The child re-connects to the opensrf network and processes
389                 # the script requests 
390                 # --------------------------------------------------------------------
391                 OpenSRF::System->bootstrap_client(config_file => $bootstrap);
392         
393                 try {
394
395                         #use Class::DBI
396                         #Class::DBI->autoupdate(1);
397
398                         $DB->autoupdate(1);
399
400                         my $sesion = ol_find_session();
401                         $session->in_process(1);
402                         ol_process_commands($session, $commands);
403                         ol_archive_files($session);
404
405                 } catch Error with {
406                         my $e = shift;
407                         $logger->error("offline: child process error $e");
408                 };
409         }
410 }
411
412 sub ol_file_to_perl {
413         my $fname = shift;
414         open(F, "$fname") or ol_handle_event('OFFLINE_FILE_ERROR');
415         my @d = <F>;
416         my @p;
417         push(@p, JSON->JSON2perl($_)) for @d;
418         close(F);
419         return \@p;
420 }
421
422 # collects the commands and sorts them on timestamp+delta
423 sub ol_collect_commands {
424         my $ses = ol_find_session();
425         my @commands;
426
427         # cycle through every script loaded to this session
428         for my $script ($ses->scripts) {
429                 my $coms = ol_file_to_perl($script->logfile);
430
431                 # cycle through all of the commands for this script
432                 for my $com (@$coms) {
433                         $$com{_workstation} = $script->workstation;
434                         $$com{_realtime} = $script->time_delta + $com->{timestamp};
435                         push( @commands, $com );
436                 }
437         }
438
439         # make sure thera are no blank commands
440         @commands = grep { ($_ and $_->{type}) } @commands;
441
442         # sort on realtime
443         @commands = sort { $a->{_realtime} <=> $b->{_realtime} } @commands;
444
445         # push user registrations to the front
446         my @regs                = grep { $_->{type} eq 'register' } @commands;
447         my @others      = grep { $_->{type} ne 'register' } @commands;
448
449         return [ @regs, @others ];
450 }
451
452 sub ol_date {
453         my $time = shift || CORE::time;
454         my (undef,undef, undef, $mday,$mon,$year) = localtime($time);
455         $mon++; $year   += 1900;
456         $mday   = "0$mday" unless $mday =~ /\d{2}/o;
457         $mon    = "0$mon" unless $mon   =~ /\d{2}/o;
458         return ($year, $mon, $mday);
459 }
460
461
462 # --------------------------------------------------------------------
463 # Moves all files from the pending directory to the archive directory
464 # and removes the pending directory
465 # --------------------------------------------------------------------
466 sub ol_archive_files {
467         my $session = shift;
468         my ($y, $m, $d) = ol_date();
469
470         my $dir = "$basedir/pending/$org/$seskey";
471         my $archdir = "$basedir/archive/$org/$seskey";
472         $logger->debug("offline: archiving files to $archdir");
473
474         # Tell the db the files are moving
475         $_->logfile($archdir.'/'.$_->workstation.'.log') for ($session->scripts);
476
477         qx/mkdir -p $archdir/;
478         qx/mv $_ $archdir/ for <$dir/*>;
479         qx/rmdir $dir/;
480 }
481
482
483 # --------------------------------------------------------------------
484 # Appends results to the results file.
485 # --------------------------------------------------------------------
486 my $rhandle;
487 sub ol_append_result {
488
489         my $obj = shift;
490         my $last = shift;
491
492         $obj = JSON->perl2JSON($obj);
493
494         if(!$rhandle) {
495                 open($rhandle, ">>$basedir/pending/$org/$seskey/results") 
496                         or ol_handle_event('OFFLINE_FILE_ERROR');
497         }
498
499         print $rhandle "$obj\n";
500         close($rhandle) if $last;
501 }
502
503
504
505 # --------------------------------------------------------------------
506 # Runs the commands and returns the list of errors
507 # --------------------------------------------------------------------
508 sub ol_process_commands {
509
510         my $session      = shift;
511         my $commands = shift;
512         my $x        = 0;
513
514         $session->start_time(CORE::time);
515
516         for my $d ( @$commands ) {
517
518                 my $t           = $d->{type};
519                 my $last = ($x++ == scalar(@$commands) - 1) ? 1 : 0;
520                 my $res = { command => $d };
521
522                 try {
523                         $res->{event} = ol_handle_checkin($d)   if $t eq 'checkin';
524                         $res->{event} = ol_handle_inhouse($d)   if $t eq 'in_house_use';
525                         $res->{event} = ol_handle_checkout($d) if $t eq 'checkout';
526                         $res->{event} = ol_handle_renew($d)             if $t eq 'renew';
527                         $res->{event} = ol_handle_register($d) if $t eq 'register';
528
529                 } catch Error with {
530                         my $e = shift;
531                         $res->{event} = OpenILS::Event->new(
532                                 'INTERNAL_SERVER_ERROR', debug => "$e");
533                 };
534
535
536                 ol_append_result($res, $last);
537                 $session->num_complete( $session->num_complete + 1 );
538
539                 $logger->debug("offline: set session [".$session->key."] num_complete to ".$session->num_complete);
540         }
541
542         $session->end_time(CORE::time);
543         $session->in_process(0);
544 }
545
546
547 # --------------------------------------------------------------------
548 # Runs an in_house_use action
549 # --------------------------------------------------------------------
550 sub ol_handle_inhouse {
551
552         my $command             = shift;
553         my $realtime    = $command->{_realtime};
554         my $ws                  = $command->{_workstation};
555         my $barcode             = $command->{barcode};
556         my $count               = $command->{count} || 1;
557         my $use_time    = $command->{use_time} || "";
558
559         $logger->activity("offline: in_house_use : requestor=". $requestor->id.", realtime=$realtime, ".  
560                 "workstation=$ws, barcode=$barcode, count=$count, use_time=$use_time");
561
562         my $ids = $U->simplereq(
563                 'open-ils.circ', 
564                 'open-ils.circ.in_house_use.create', $authtoken, 
565                 { barcode => $barcode, count => $count, location => $org, use_time => $use_time } );
566         
567         return OpenILS::Event->new('SUCCESS', payload => $ids) if( ref($ids) eq 'ARRAY' );
568         return $ids;
569 }
570
571
572
573 # --------------------------------------------------------------------
574 # Pulls the relevant circ args from the command, fetches data where 
575 # necessary
576 # --------------------------------------------------------------------
577 sub ol_circ_args_from_command {
578         my $command = shift;
579
580         my $type                        = $command->{type};
581         my $realtime    = $command->{_realtime};
582         my $ws                  = $command->{_workstation};
583         my $barcode             = $command->{barcode} || "";
584         my $cotime              = $command->{checkout_time} || "";
585         my $pbc                 = $command->{patron_barcode};
586         my $due_date    = $command->{due_date} || "";
587         my $noncat              = ($command->{noncat}) ? "yes" : "no"; # for logging
588
589         $logger->activity("offline: $type : requestor=". $requestor->id.
590                 ", realtime=$realtime, workstation=$ws, checkout_time=$cotime, ".
591                 "patron=$pbc, due_date=$due_date, noncat=$noncat");
592
593         my $args = { 
594                 permit_override => 1, 
595                 barcode                         => $barcode,            
596                 checkout_time           => $cotime, 
597                 patron_barcode          => $pbc,
598                 due_date                                => $due_date };
599
600         if( $command->{noncat} ) {
601                 $args->{noncat} = 1;
602                 $args->{noncat_type} = $command->{noncat_type};
603                 $args->{noncat_count} = $command->{noncat_count};
604         }
605
606         return $args;
607 }
608
609
610
611 # --------------------------------------------------------------------
612 # Performs a checkout action
613 # --------------------------------------------------------------------
614 sub ol_handle_checkout {
615         my $command     = shift;
616         my $args = ol_circ_args_from_command($command);
617         return $U->simplereq(
618                 'open-ils.circ', 'open-ils.circ.checkout', $authtoken, $args );
619 }
620
621
622 # --------------------------------------------------------------------
623 # Performs the renewal action
624 # --------------------------------------------------------------------
625 sub ol_handle_renew {
626         my $command = shift;
627         my $args = ol_circ_args_from_command($command);
628         my $t = time;
629         return $U->simplereq(
630                 'open-ils.circ', 'open-ils.circ.renew', $authtoken, $args );
631 }
632
633
634 # --------------------------------------------------------------------
635 # Runs a checkin action
636 # --------------------------------------------------------------------
637 sub ol_handle_checkin {
638
639         my $command             = shift;
640         my $realtime    = $command->{_realtime};
641         my $ws                  = $command->{_workstation};
642         my $barcode             = $command->{barcode};
643         my $backdate    = $command->{backdate} || "";
644
645         $logger->activity("offline: checkin : requestor=". $requestor->id.
646                 ", realtime=$realtime, ".  "workstation=$ws, barcode=$barcode, backdate=$backdate");
647
648         return $U->simplereq(
649                 'open-ils.circ', 
650                 'open-ils.circ.checkin', $authtoken,
651                 { barcode => $barcode, backdate => $backdate } );
652 }
653
654
655
656 # --------------------------------------------------------------------
657 # Registers a new patron
658 # --------------------------------------------------------------------
659 sub ol_handle_register {
660         my $command = shift;
661
662         my $barcode = $command->{user}->{card}->{barcode};
663         delete $command->{user}->{card}; 
664
665         $logger->info("offline: creating new user with barcode $barcode");
666
667         # now, create the user
668         my $actor       = Fieldmapper::actor::user->new;
669         my $card                = Fieldmapper::actor::card->new;
670
671
672         # username defaults to the barcode
673         $actor->usrname( ($actor->usrname) ? $actor->usrname : $barcode );
674
675         # Set up all of the virtual IDs, isnew, etc.
676         $actor->isnew(1);
677         $actor->id(-1);
678         $actor->card(-1);
679         $actor->cards([$card]);
680
681         $card->isnew(1);
682         $card->id(-1);
683         $card->usr(-1);
684         $card->barcode($barcode);
685
686         my $billing_address;
687         my $mailing_address;
688
689         my @sresp;
690         for my $resp (@{$command->{user}->{survey_responses}}) {
691                 my $sr = Fieldmapper::action::survey_response->new;
692                 $sr->$_( $resp->{$_} ) for keys %$resp;
693                 $sr->isnew(1);
694                 $sr->usr(-1);
695                 push(@sresp, $sr);
696                 $logger->debug("offline: created new survey response for survey ".$sr->survey);
697         }
698         delete $command->{user}->{survey_responses};
699         $actor->survey_responses(\@sresp) if @sresp;
700
701         # extract the billing address
702         if( my $addr = $command->{user}->{billing_address} ) {
703                 $billing_address = Fieldmapper::actor::user_address->new;
704                 $billing_address->$_($addr->{$_}) for keys %$addr;
705                 $billing_address->isnew(1);
706                 $billing_address->id(-1);
707                 $billing_address->usr(-1);
708                 delete $command->{user}->{billing_address};
709                 $logger->debug("offline: read billing address ".$billing_address->street1);
710         }
711
712         # extract the mailing address
713         if( my $addr = $command->{user}->{mailing_address} ) {
714                 $mailing_address = Fieldmapper::actor::user_address->new;
715                 $mailing_address->$_($addr->{$_}) for keys %$addr;
716                 $mailing_address->isnew(1);
717                 $mailing_address->id(-2);
718                 $mailing_address->usr(-1);
719                 delete $command->{user}->{mailing_address};
720                 $logger->debug("offline: read mailing address ".$mailing_address->street1);
721         }
722
723         # make sure we have values for both
724         $billing_address ||= $mailing_address;
725         $mailing_address ||= $billing_address;
726
727         $actor->billing_address($billing_address->id);
728         $actor->mailing_address($mailing_address->id);
729         $actor->addresses([$mailing_address]);
730
731         push( @{$actor->addresses}, $billing_address ) 
732                 unless $billing_address->id eq $mailing_address->id;
733         
734         # pull all of the rest of the data from the command blob
735         $actor->$_( $command->{user}->{$_} ) for keys %{$command->{user}};
736
737         $logger->debug("offline: creating user object...");
738         $actor = $U->simplereq(
739                 'open-ils.actor', 
740                 'open-ils.actor.patron.update', $authtoken, $actor);
741
742         return $actor if(ref($actor) eq 'HASH'); # an event occurred
743
744         return OpenILS::Event->new('SUCCESS', payload => $actor);
745 }
746
747
748
749
750
751
752