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