2 use strict; use warnings;
4 use OpenSRF::Utils::JSON;
6 use OpenSRF::Utils::Logger qw/$logger/;
7 use OpenILS::Application::AppUtils;
9 use OpenSRF::EX qw/:try/;
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;
18 use DateTime::Format::ISO8601;
23 my $U = "OpenILS::Application::AppUtils";
24 my $DB = "OpenILS::Utils::OfflineStore";
25 my $SES = "${DB}::Session";
26 my $SCRIPT = "OpenILS::Utils::OfflineStore::Script";
29 # Used by the functionality that produces SKIP_ASSET_CHANGED events
30 my %seen_barcode = ();
31 my %skip_barcode_for_status_changed = ();
33 # --------------------------------------------------------------------
35 # --------------------------------------------------------------------
37 do '##CONFIG##/offline-config.pl';
41 my $basedir = $config{base_dir} || die "Offline config error: no base_dir defined\n";
42 my $bootstrap = $config{bootstrap} || die "Offline config error: no bootstrap defined\n";
43 my $wsname = $cgi->param('ws');
44 my $org = $cgi->param('org');
45 my $authtoken = $cgi->param('ses') || "";
46 my $seskey = $cgi->param('seskey');
47 my $action = $cgi->param('action'); # - create, load, execute, status
59 # --------------------------------------------------------------------
61 # This function should behave as a child_init might behave in case
62 # this is moved to mod_perl
63 # --------------------------------------------------------------------
65 $DB->DBFile($config{dsn}, $config{usr}, $config{pw});
70 OpenSRF::System->bootstrap_client(config_file => $bootstrap );
71 Fieldmapper->import(IDL =>
72 OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
77 sub _ol_debug_params {
79 my @params = $cgi->param;
80 @params = sort { $a cmp $b } @params;
81 $s .= "$_=" . $cgi->param($_) . "\n" for @params;
83 warn '-'x60 ."\n$s\n";
87 # --------------------------------------------------------------------
88 # Finds the requestor and other info specific to this request
89 # --------------------------------------------------------------------
92 # fetch the requestor object
93 ($requestor, $evt) = $U->checkses($authtoken);
94 ol_handle_result($evt) if $evt;
96 # try the param, the workstation, and finally the user's ws org
98 $wsobj = ol_fetch_workstation($wsname);
99 $org = $wsobj->owning_lib if $wsobj;
100 $org = $requestor->ws_ou unless $org;
101 ol_handle_result(OpenILS::Event->new('OFFLINE_NO_ORG')) unless $org;
104 $user_groups = $U->simplereq(
105 'open-ils.actor', 'open-ils.actor.groups.retrieve');
109 # --------------------------------------------------------------------
110 # Runs the requested action
111 # --------------------------------------------------------------------
116 if( $action eq 'create' ) {
118 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
119 ol_handle_result($evt) if $evt;
120 $payload = ol_create_session();
122 } elsif( $action eq 'load' ) {
124 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
125 ol_handle_result($evt) if $evt;
126 $payload = ol_load();
128 } elsif( $action eq 'execute' ) {
130 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_EXECUTE');
131 ol_handle_result($evt) if $evt;
132 $payload = ol_execute();
134 } elsif( $action eq 'status' ) {
136 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_VIEW');
137 ol_handle_result($evt) if $evt;
138 $payload = ol_status();
141 ol_handle_event('SUCCESS', payload => $payload );
145 # --------------------------------------------------------------------
146 # Creates a new session
147 # --------------------------------------------------------------------
148 sub ol_create_session {
150 my $desc = $cgi->param('desc') || "";
151 $seskey = time . "_${$}_" . int(rand() * 1000);
153 $logger->debug("offline: user ".$requestor->id.
154 " creating new session with key $seskey and description $desc");
160 description => $desc,
161 creator => $requestor->id,
162 create_time => CORE::time(),
171 # --------------------------------------------------------------------
172 # Holds the meta-info for a script file
173 # --------------------------------------------------------------------
174 sub ol_create_script {
177 my $session = ol_find_session($seskey);
178 my $delta = $cgi->param('delta') || 0;
180 my $script = $session->add_to_scripts(
182 requestor => $requestor->id,
183 create_time => CORE::time,
184 workstation => $wsname,
185 logfile => "$basedir/pending/$org/$seskey/$wsname.log",
186 time_delta => $delta,
192 # --------------------------------------------------------------------
193 # Finds the current session in the db
194 # --------------------------------------------------------------------
195 sub ol_find_session {
196 my $ses = $SES->retrieve($seskey);
197 ol_handle_event('OFFLINE_INVALID_SESSION', payload => $seskey) unless $ses;
201 # --------------------------------------------------------------------
202 # Finds a script object in the DB based on workstation and seskey
203 # --------------------------------------------------------------------
205 my $ws = shift || $wsname;
206 my $sk = shift || $seskey;
207 my ($script) = $SCRIPT->search( session => $seskey, workstation => $ws );
211 # --------------------------------------------------------------------
212 # Creates a new script in the database and loads the new script file
213 # --------------------------------------------------------------------
216 my $session = ol_find_session;
217 my $handle = $cgi->upload('file');
218 my $outdir = "$basedir/pending/$org/$seskey";
219 my $outfile = "$outdir/$wsname.log";
221 ol_handle_event('OFFLINE_SESSION_FILE_EXISTS') if ol_find_script();
222 ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
223 ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
225 qx/mkdir -p $outdir/;
227 open(FILE, ">>$outfile") or ol_handle_event('OFFLINE_FILE_ERROR');
228 while( <$handle> ) { print FILE; $x++;}
231 ol_create_script($x);
237 # --------------------------------------------------------------------
238 sub ol_handle_result {
240 my $json = OpenSRF::Utils::JSON->perl2JSON($obj);
242 # Clear this so it's not remembered
245 if( $cgi->param('html')) {
246 my $html = "<html><body onload='xulG.handle_event($json)'></body></html>";
247 print "content-type: text/html\n\n";
252 print "content-type: text/plain\n\n";
259 # --------------------------------------------------------------------
260 sub ol_handle_event {
261 my( $evt, @args ) = @_;
262 ol_handle_result(OpenILS::Event->new($evt, @args));
266 # --------------------------------------------------------------------
267 sub ol_flesh_session {
271 map { $data{$_} = $session->$_ } $session->columns;
274 for my $script ($session->scripts) {
276 map { $sdata{$_} = $script->$_ } $script->columns;
278 # the client doesn't need this info
279 delete $sdata{session};
281 delete $sdata{logfile};
283 push( @{$data{scripts}}, \%sdata );
290 # --------------------------------------------------------------------
291 # Returns various information on the sessions and scripts
292 # --------------------------------------------------------------------
295 my $type = $cgi->param('status_type') || "scripts";
297 # --------------------------------------------------------------------
298 # Returns info on every script upload attached to the current session
299 # --------------------------------------------------------------------
300 if( $type eq 'scripts' ) {
301 my $session = ol_find_session();
302 ol_handle_result(ol_flesh_session($session));
305 # --------------------------------------------------------------------
306 # Returns all scripts and sessions for the given org
307 # --------------------------------------------------------------------
308 } elsif( $type eq 'sessions' ) {
309 my @sessions = $SES->search( org => $org );
311 # can I do this in the DB without raw SQL?
312 @sessions = sort { $a->create_time <=> $b->create_time } @sessions;
314 push( @data, ol_flesh_session($_) ) for @sessions;
315 ol_handle_result(\@data);
318 # --------------------------------------------------------------------
319 # Returns total commands and completed commands counts
320 # --------------------------------------------------------------------
321 } elsif( $type eq 'summary' ) {
322 my $session = ol_find_session();
324 $logger->debug("offline: retrieving summary info ".
325 "for session ".$session->key." with completed=".$session->num_complete);
328 $count += $_->count for ($session->scripts);
330 { total => $count, num_complete => $session->num_complete });
334 # --------------------------------------------------------------------
335 # Returns the list of non-SUCCESS events that have occurred so far for
336 # this set of commands
337 # --------------------------------------------------------------------
338 } elsif( $type eq 'exceptions' ) {
340 my $session = ol_find_session();
341 my $resfile = "$basedir/pending/$org/$seskey/results";
342 if( $session->end_time ) {
343 $resfile = "$basedir/archive/$org/$seskey/results";
345 my $data = ol_file_to_perl($resfile);
348 my $evt = $d->{event};
349 $evt = $evt->[0] if ref $evt eq 'ARRAY';
350 push(@$data2, $d) if $evt->{ilsevent} ne '0';
352 #$data = [ grep { $_->{event}->{ilsevent} ne '0' } @$data ];
353 ol_handle_result($data2);
358 sub ol_fetch_workstation {
360 $logger->debug("offline: Fetching workstation $name");
361 my $ws = $U->storagereq(
362 'open-ils.storage.direct.actor.workstation.search.name', $name);
363 ol_handle_result(OpenILS::Event->new('ACTOR_WORKSTATION_NOT_FOUND')) unless $ws;
370 # --------------------------------------------------------------------
371 # Sorts the script commands then forks a child to executes them.
372 # --------------------------------------------------------------------
375 my $session = ol_find_session();
376 ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
377 ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
379 my $commands = ol_collect_commands();
381 # --------------------------------------------------------------------
382 # Note that we must disconnect from opensrf before forking or the
383 # connection will be borked...
384 # --------------------------------------------------------------------
385 OpenSRF::Transport::PeerHandle->retrieve->disconnect;
391 # --------------------------------------------------------------------
392 # Tell the client all is well
393 # --------------------------------------------------------------------
394 ol_handle_event('SUCCESS'); # - this exits
399 # --------------------------------------------------------------------
400 # close stdout/stderr or apache will wait on the child to finish
401 # --------------------------------------------------------------------
405 $logger->debug("offline: child $$ processing data...");
407 # --------------------------------------------------------------------
408 # The child re-connects to the opensrf network and processes
409 # the script requests
410 # --------------------------------------------------------------------
411 OpenSRF::System->bootstrap_client(config_file => $bootstrap);
416 #Class::DBI->autoupdate(1);
420 my $sesion = ol_find_session();
421 $session->in_process(1);
422 ol_process_commands($session, $commands);
423 ol_archive_files($session);
427 $logger->error("offline: child process error $e");
432 sub ol_file_to_perl {
434 open(F, "$fname") or ol_handle_event('OFFLINE_FILE_ERROR');
437 push(@p, OpenSRF::Utils::JSON->JSON2perl($_)) for @d;
442 # collects the commands and sorts them on timestamp+delta
443 sub ol_collect_commands {
444 my $ses = ol_find_session();
447 # cycle through every script loaded to this session
448 for my $script ($ses->scripts) {
449 my $coms = ol_file_to_perl($script->logfile);
451 # cycle through all of the commands for this script
452 for my $com (@$coms) {
453 $$com{_workstation} = $script->workstation;
454 $$com{_realtime} = $script->time_delta + $com->{timestamp};
455 push( @commands, $com );
459 # make sure thera are no blank commands
460 @commands = grep { ($_ and $_->{type}) } @commands;
463 @commands = sort { $a->{_realtime} <=> $b->{_realtime} } @commands;
465 # push user registrations to the front
466 my @regs = grep { $_->{type} eq 'register' } @commands;
467 my @others = grep { $_->{type} ne 'register' } @commands;
469 return [ @regs, @others ];
473 my $time = shift || CORE::time;
474 my (undef,undef, undef, $mday,$mon,$year) = localtime($time);
475 $mon++; $year += 1900;
476 $mday = "0$mday" unless $mday =~ /\d{2}/o;
477 $mon = "0$mon" unless $mon =~ /\d{2}/o;
478 return ($year, $mon, $mday);
482 # --------------------------------------------------------------------
483 # Moves all files from the pending directory to the archive directory
484 # and removes the pending directory
485 # --------------------------------------------------------------------
486 sub ol_archive_files {
488 my ($y, $m, $d) = ol_date();
490 my $dir = "$basedir/pending/$org/$seskey";
491 my $archdir = "$basedir/archive/$org/$seskey";
492 $logger->debug("offline: archiving files to $archdir");
494 # Tell the db the files are moving
495 $_->logfile($archdir.'/'.$_->workstation.'.log') for ($session->scripts);
497 qx/mkdir -p $archdir/;
498 qx/mv $_ $archdir/ for <$dir/*>;
503 # --------------------------------------------------------------------
504 # Appends results to the results file.
505 # --------------------------------------------------------------------
507 sub ol_append_result {
512 $obj = OpenSRF::Utils::JSON->perl2JSON($obj);
515 open($rhandle, ">>$basedir/pending/$org/$seskey/results")
516 or ol_handle_event('OFFLINE_FILE_ERROR');
519 print $rhandle "$obj\n";
520 close($rhandle) if $last;
525 # --------------------------------------------------------------------
526 # Runs the commands and returns the list of errors
527 # --------------------------------------------------------------------
528 sub ol_process_commands {
531 my $commands = shift;
534 $session->start_time(CORE::time);
536 for my $d ( @$commands ) {
539 my $last = ($x++ == scalar(@$commands) - 1) ? 1 : 0;
540 my $res = { command => $d };
546 $logger->debug("offline: top of execute loop : $t");
549 $res->{event} = ol_handle_checkin($d) if $t eq 'checkin';
550 $res->{event} = ol_handle_inhouse($d) if $t eq 'in_house_use';
551 $res->{event} = ol_handle_checkout($d) if $t eq 'checkout';
552 $res->{event} = ol_handle_renew($d) if $t eq 'renew';
553 $res->{event} = ol_handle_register($d) if $t eq 'register';
555 } catch Error with { $err = shift; };
559 if( ref($err) eq 'OpenSRF::EX::JabberDisconnected' ) {
560 $logger->error("offline: we lost jabber .. trying to reconnect");
564 $res->{event} = OpenILS::Event->new('INTERNAL_SERVER_ERROR', debug => "$err");
573 ol_append_result($res, $last);
574 $session->num_complete( $session->num_complete + 1 );
576 $logger->debug("offline: set session [".$session->key."] num_complete to ".$session->num_complete);
579 $session->end_time(CORE::time);
580 $session->in_process(0);
584 # --------------------------------------------------------------------
585 # Runs an in_house_use action
586 # --------------------------------------------------------------------
587 sub ol_handle_inhouse {
590 my $realtime = $command->{_realtime};
591 my $ws = $command->{_workstation};
592 my $barcode = $command->{barcode};
593 my $count = $command->{count} || 1;
594 my $use_time = $command->{use_time} || "";
596 $logger->activity("offline: in_house_use : requestor=". $requestor->id.", realtime=$realtime, ".
597 "workstation=$ws, barcode=$barcode, count=$count, use_time=$use_time");
600 return OpenILS::Event->new(
601 'INTERNAL_SERVER_ERROR', payload => 'TOO MANY IN HOUSE USE');
604 my $ids = $U->simplereq(
606 'open-ils.circ.in_house_use.create', $authtoken,
607 { barcode => $barcode, count => $count, location => $org, use_time => $use_time } );
609 return OpenILS::Event->new('SUCCESS', payload => $ids) if( ref($ids) eq 'ARRAY' );
615 # --------------------------------------------------------------------
616 # Pulls the relevant circ args from the command, fetches data where
618 # --------------------------------------------------------------------
620 sub ol_circ_args_from_command {
623 my $type = $command->{type};
624 my $realtime = $command->{_realtime};
625 my $ws = $command->{_workstation};
626 my $barcode = $command->{barcode} || "";
627 my $cotime = $command->{checkout_time} || "";
628 my $pbc = $command->{patron_barcode};
629 my $due_date = $command->{due_date} || "";
630 my $noncat = ($command->{noncat}) ? "yes" : "no"; # for logging
632 $logger->activity("offline: $type : requestor=". $requestor->id.
633 ", realtime=$realtime, workstation=$ws, checkout_time=$cotime, ".
634 "patron=$pbc, due_date=$due_date, noncat=$noncat");
638 permit_override => 1,
640 checkout_time => $cotime,
641 patron_barcode => $pbc,
642 due_date => $due_date
645 if(ol_get_org_setting('circ.offline.username_allowed')) {
647 my $format = ol_get_org_setting('opac.barcode_regex');
650 $format =~ s/^\/|\/$//g; # remove any couching //'s
651 if($pbc !~ qr/$format/) {
653 # the patron barcode does not match the configured barcode format
654 # assume they passed a username instead
656 my $user_id = $user_id_cache{$pbc} ||
659 'open-ils.actor.username.exists',
664 # a valid username was provided, update the args and cache
665 $user_id_cache{$pbc} = $user_id;
666 $args->{patron_id} = $user_id;
667 delete $args->{patron_barcode};
674 if( $command->{noncat} ) {
676 $args->{noncat_type} = $command->{noncat_type};
677 $args->{noncat_count} = $command->{noncat_count};
683 sub ol_get_org_setting {
685 return $U->simplereq(
687 'open-ils.actor.ou_setting.ancestor_default',
688 $org, $name, $authtoken);
693 # --------------------------------------------------------------------
694 # Performs a checkout action
695 # --------------------------------------------------------------------
696 sub ol_handle_checkout {
698 my $args = ol_circ_args_from_command($command);
700 if( $args->{noncat} and $args->{noncat_count} > 99 ) {
701 return OpenILS::Event->new(
702 'INTERNAL_SERVER_ERROR', payload => 'TOO MANY NON CATS');
705 if( $args->{barcode} ) {
707 # $c becomes the Copy
708 # $e possibily becomes the Exception
709 my( $c, $e ) = $U->fetch_copy_by_barcode($args->{barcode});
712 my $barcode = $args->{barcode};
713 # Have to have this config option (or org setting) and a
714 # status_changed_time for skippage, and barcode not seen before
717 'circ.offline.skip_checkout_if_newer_status_changed_time'
719 || $config{skip_late}
721 && length($c->status_changed_time())
722 && ! $seen_barcode{$barcode}
724 $seen_barcode{$barcode} = 1;
725 my $cts = DateTime::Format::ISO8601->parse_datetime( cleanse_ISO8601($c->status_changed_time()) )->epoch();
726 my $xts = $command->{timestamp}; # Transaction Time Stamp
727 $logger->activity("offline: ol_handle_checkout: considering status_changed_time for barcode=$barcode, cts=$cts, xts=$xts");
729 # Asset has changed after this transaction, ignore
731 $skip_barcode_for_status_changed{$barcode} = 1;
734 $logger->activity("No skip check: barcode=$barcode seen_barcode=".$seen_barcode{$1}." status_changed_time=".$c->status_changed_time." ou_setting=".ol_get_org_setting('circ.offline.skip_checkout_if_newer_status_changed_time'));
736 if ($skip_barcode_for_status_changed{$barcode}) {
737 $logger->activity("offline: ol_handle_checkout: barcode=$barcode has SKIP_ASSET_CHANGED");
738 return OpenILS::Event->new(
744 my $evt = $U->simplereq(
745 'open-ils.circ', 'open-ils.circ.checkout', $authtoken, $args );
747 # if the item is already checked out to this user and we are past
748 # the configured auto-renewal interval, try to renew the circ.
749 if( ref $evt ne 'ARRAY' and
750 $evt->{textcode} == 'OPEN_CIRCULATION_EXISTS' and
751 $evt->{payload}->{auto_renew}) {
753 return ol_handle_renew($command);
760 # --------------------------------------------------------------------
761 # Performs the renewal action
762 # --------------------------------------------------------------------
763 sub ol_handle_renew {
765 my $args = ol_circ_args_from_command($command);
768 if( $args->{barcode} ) {
770 # $c becomes the Copy
771 # $e possibily becomes the Exception
772 my( $c, $e ) = $U->fetch_copy_by_barcode($args->{barcode});
775 my $barcode = $args->{barcode};
776 # Have to have this config option (or org setting) and a
777 # status_changed_time for skippage, and barcode not seen before
780 'circ.offline.skip_renew_if_newer_status_changed_time'
782 || $config{skip_late}
784 && length($c->status_changed_time())
785 && ! $seen_barcode{$barcode}
787 $seen_barcode{$barcode} = 1;
788 my $cts = DateTime::Format::ISO8601->parse_datetime( cleanse_ISO8601($c->status_changed_time()) )->epoch();
789 my $xts = $command->{timestamp}; # Transaction Time Stamp
790 $logger->activity("offline: ol_handle_renew: considering status_changed_time for barcode=$barcode, cts=$cts, xts=$xts");
792 # Asset has changed after this transaction, ignore
794 $skip_barcode_for_status_changed{$barcode} = 1;
797 $logger->activity("No skip check: barcode=$barcode seen_barcode=".$seen_barcode{$1}." status_changed_time=".$c->status_changed_time." ou_setting=".ol_get_org_setting('circ.offline.skip_renew_if_newer_status_changed_time'));
799 if ($skip_barcode_for_status_changed{$barcode}) {
800 $logger->activity("offline: ol_handle_renew: barcode=$barcode has SKIP_ASSET_CHANGED");
801 return OpenILS::Event->new(
807 return $U->simplereq(
808 'open-ils.circ', 'open-ils.circ.renew.override', $authtoken, $args );
812 # --------------------------------------------------------------------
813 # Runs a checkin action
814 # --------------------------------------------------------------------
815 sub ol_handle_checkin {
818 my $realtime = $command->{_realtime};
819 my $ws = $command->{_workstation};
820 my $barcode = $command->{barcode};
821 my $backdate = $command->{backdate} || "";
823 $logger->activity("offline: checkin : requestor=". $requestor->id.
824 ", realtime=$realtime, ". "workstation=$ws, barcode=$barcode, backdate=$backdate");
828 # $c becomes the Copy
829 # $e possibily becomes the Exception
830 my( $c, $e ) = $U->fetch_copy_by_barcode($barcode);
833 # Have to have this config option (or org setting) and a
834 # status_changed_time for skippage, and barcode not seen before
837 'circ.offline.skip_checkin_if_newer_status_changed_time'
839 || $config{skip_late}
841 && length($c->status_changed_time())
842 && ! $seen_barcode{$barcode}
844 $seen_barcode{$barcode} = 1;
845 my $cts = DateTime::Format::ISO8601->parse_datetime( cleanse_ISO8601($c->status_changed_time()) )->epoch();
846 my $xts = $command->{timestamp}; # Transaction Time Stamp
847 $logger->activity("offline: ol_handle_checkin: considering status_changed_time for barcode=$barcode, cts=$cts, xts=$xts");
849 # Asset has changed after this transaction, ignore
851 $skip_barcode_for_status_changed{$barcode} = 1;
854 $logger->activity("No skip check: barcode=$barcode seen_barcode=".$seen_barcode{$1}." status_changed_time=".$c->status_changed_time." ou_setting=".ol_get_org_setting('circ.offline.skip_checkin_if_newer_status_changed_time'));
856 if ($skip_barcode_for_status_changed{$barcode}) {
857 $logger->activity("offline: ol_handle_checkin: barcode=$barcode has SKIP_ASSET_CHANGED");
858 return OpenILS::Event->new(
864 return $U->simplereq(
866 'open-ils.circ.checkin', $authtoken,
867 { barcode => $barcode, backdate => $backdate } );
872 # --------------------------------------------------------------------
873 # Registers a new patron
874 # --------------------------------------------------------------------
875 sub ol_handle_register {
878 my $barcode = $command->{user}->{card}->{barcode};
879 delete $command->{user}->{card};
881 $logger->info("offline: creating new user with barcode $barcode");
883 # now, create the user
884 my $actor = Fieldmapper::actor::user->new;
885 my $card = Fieldmapper::actor::card->new;
888 # username defaults to the barcode
889 $actor->usrname( ($actor->usrname) ? $actor->usrname : $barcode );
891 # Set up all of the virtual IDs, isnew, etc.
895 $actor->cards([$card]);
900 $card->barcode($barcode);
906 for my $resp (@{$command->{user}->{survey_responses}}) {
907 my $sr = Fieldmapper::action::survey_response->new;
908 $sr->$_( $resp->{$_} ) for keys %$resp;
912 $logger->debug("offline: created new survey response for survey ".$sr->survey);
914 delete $command->{user}->{survey_responses};
915 $actor->survey_responses(\@sresp) if @sresp;
917 # extract the billing address
918 if( my $addr = $command->{user}->{billing_address} ) {
919 $billing_address = Fieldmapper::actor::user_address->new;
920 $billing_address->$_($addr->{$_}) for keys %$addr;
921 $billing_address->isnew(1);
922 $billing_address->id(-1);
923 $billing_address->usr(-1);
924 delete $command->{user}->{billing_address};
925 $logger->debug("offline: read billing address ".$billing_address->street1);
928 # extract the mailing address
929 if( my $addr = $command->{user}->{mailing_address} ) {
930 $mailing_address = Fieldmapper::actor::user_address->new;
931 $mailing_address->$_($addr->{$_}) for keys %$addr;
932 $mailing_address->isnew(1);
933 $mailing_address->id(-2);
934 $mailing_address->usr(-1);
935 delete $command->{user}->{mailing_address};
936 $logger->debug("offline: read mailing address ".$mailing_address->street1);
939 # make sure we have values for both
940 $billing_address ||= $mailing_address;
941 $mailing_address ||= $billing_address;
943 $actor->billing_address($billing_address->id);
944 $actor->mailing_address($mailing_address->id);
945 $actor->addresses([$mailing_address]);
947 push( @{$actor->addresses}, $billing_address )
948 unless $billing_address->id eq $mailing_address->id;
950 # pull all of the rest of the data from the command blob
951 $actor->$_( $command->{user}->{$_} ) for keys %{$command->{user}};
953 # calculate the expire date for the patron based on the profile group
954 my ($grp) = grep {$_->id == $actor->profile} @$user_groups;
956 my $seconds = OpenSRF::Utils->interval_to_seconds($grp->perm_interval);
957 my $expire_date = DateTime->from_epoch(epoch => DateTime->now->epoch + $seconds)->epoch;
958 $logger->debug("offline: setting expire date to $expire_date");
959 $actor->expire_date($U->epoch2ISO8601($expire_date));
962 $logger->debug("offline: creating user object...");
963 $actor = $U->simplereq(
965 'open-ils.actor.patron.update', $authtoken, $actor);
967 return $actor if(ref($actor) eq 'HASH'); # an event occurred
969 return OpenILS::Event->new('SUCCESS', payload => $actor);