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 $webclient = $cgi->param('wc');
44 my $wsname = $cgi->param('ws');
45 my $org = $cgi->param('org');
46 my $authtoken = $cgi->param('ses') || "";
47 my $seskey = $cgi->param('seskey');
48 my $action = $cgi->param('action'); # - create, load, execute, status
60 # --------------------------------------------------------------------
62 # This function should behave as a child_init might behave in case
63 # this is moved to mod_perl
64 # --------------------------------------------------------------------
66 $DB->DBFile($config{dsn}, $config{usr}, $config{pw});
71 OpenSRF::System->bootstrap_client(config_file => $bootstrap );
72 Fieldmapper->import(IDL =>
73 OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
78 sub _ol_debug_params {
80 my @params = $cgi->param;
81 @params = sort { $a cmp $b } @params;
82 $s .= "$_=" . $cgi->param($_) . "\n" for @params;
84 warn '-'x60 ."\n$s\n";
88 # --------------------------------------------------------------------
89 # Finds the requestor and other info specific to this request
90 # --------------------------------------------------------------------
93 # fetch the requestor object
94 ($requestor, $evt) = $U->checkses($authtoken);
95 ol_handle_result($evt) if $evt;
97 # try the param, the workstation, and finally the user's ws org
99 $wsobj = ol_fetch_workstation($wsname);
100 $org = $wsobj->owning_lib if $wsobj;
101 $org = $requestor->ws_ou unless $org;
102 ol_handle_result(OpenILS::Event->new('OFFLINE_NO_ORG')) unless $org;
105 $user_groups = $U->simplereq(
106 'open-ils.actor', 'open-ils.actor.groups.retrieve');
110 # --------------------------------------------------------------------
111 # Runs the requested action
112 # --------------------------------------------------------------------
117 if( $action eq 'create' ) {
119 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
120 ol_handle_result($evt) if $evt;
121 $payload = ol_create_session();
123 } elsif( $action eq 'load' ) {
125 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
126 ol_handle_result($evt) if $evt;
127 $payload = ol_load();
129 } elsif( $action eq 'execute' ) {
131 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_EXECUTE');
132 ol_handle_result($evt) if $evt;
133 $payload = ol_execute();
135 } elsif( $action eq 'status' ) {
137 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_VIEW');
138 ol_handle_result($evt) if $evt;
139 $payload = ol_status();
142 ol_handle_event('SUCCESS', payload => $payload );
146 # --------------------------------------------------------------------
147 # Creates a new session
148 # --------------------------------------------------------------------
149 sub ol_create_session {
151 my $desc = $cgi->param('desc') || "";
152 $seskey = time . "_${$}_" . int(rand() * 1000);
154 $logger->debug("offline: user ".$requestor->id.
155 " creating new session with key $seskey and description $desc");
161 description => $desc,
162 creator => $requestor->id,
163 create_time => CORE::time(),
172 # --------------------------------------------------------------------
173 # Holds the meta-info for a script file
174 # --------------------------------------------------------------------
175 sub ol_create_script {
178 my $session = ol_find_session($seskey);
179 my $delta = $cgi->param('delta') || 0;
181 my $script = $session->add_to_scripts(
183 requestor => $requestor->id,
184 create_time => CORE::time,
185 workstation => $wsname,
186 logfile => "$basedir/pending/$org/$seskey/$wsname.log",
187 time_delta => $delta,
193 # --------------------------------------------------------------------
194 # Finds the current session in the db
195 # --------------------------------------------------------------------
196 sub ol_find_session {
197 my $ses = $SES->retrieve($seskey);
198 ol_handle_event('OFFLINE_INVALID_SESSION', payload => $seskey) unless $ses;
202 # --------------------------------------------------------------------
203 # Finds a script object in the DB based on workstation and seskey
204 # --------------------------------------------------------------------
206 my $ws = shift || $wsname;
207 my $sk = shift || $seskey;
208 my ($script) = $SCRIPT->search( session => $seskey, workstation => $ws );
212 # --------------------------------------------------------------------
213 # Creates a new script in the database and loads the new script file
214 # --------------------------------------------------------------------
217 my $session = ol_find_session;
218 my $handle = $cgi->upload('file');
219 my $outdir = "$basedir/pending/$org/$seskey";
220 my $outfile = "$outdir/$wsname.log";
222 ol_handle_event('OFFLINE_SESSION_FILE_EXISTS') if ol_find_script();
223 ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
224 ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
226 qx/mkdir -p $outdir/;
228 open(FILE, ">>$outfile") or ol_handle_event('OFFLINE_FILE_ERROR');
229 while( <$handle> ) { print FILE; $x++;}
232 ol_create_script($x);
238 # --------------------------------------------------------------------
239 sub ol_handle_result {
241 my $json = OpenSRF::Utils::JSON->perl2JSON($obj);
243 # Clear this so it's not remembered
246 if( $cgi->param('html')) {
247 my $html = "<html><body onload='xulG.handle_event($json)'></body></html>";
248 print "content-type: text/html\n\n";
253 print "content-type: text/plain\n\n";
260 # --------------------------------------------------------------------
261 sub ol_handle_event {
262 my( $evt, @args ) = @_;
263 ol_handle_result(OpenILS::Event->new($evt, @args));
267 # --------------------------------------------------------------------
268 sub ol_flesh_session {
272 map { $data{$_} = $session->$_ } $session->columns;
275 for my $script ($session->scripts) {
277 map { $sdata{$_} = $script->$_ } $script->columns;
279 # the client doesn't need this info
280 delete $sdata{session};
282 delete $sdata{logfile};
284 push( @{$data{scripts}}, \%sdata );
291 # --------------------------------------------------------------------
292 # Returns various information on the sessions and scripts
293 # --------------------------------------------------------------------
296 my $type = $cgi->param('status_type') || "scripts";
298 # --------------------------------------------------------------------
299 # Returns info on every script upload attached to the current session
300 # --------------------------------------------------------------------
301 if( $type eq 'scripts' ) {
302 my $session = ol_find_session();
303 ol_handle_result(ol_flesh_session($session));
306 # --------------------------------------------------------------------
307 # Returns all scripts and sessions for the given org
308 # --------------------------------------------------------------------
309 } elsif( $type eq 'sessions' ) {
310 my @sessions = $SES->search( org => $org );
312 # can I do this in the DB without raw SQL?
313 @sessions = sort { $a->create_time <=> $b->create_time } @sessions;
315 push( @data, ol_flesh_session($_) ) for @sessions;
316 ol_handle_result(\@data);
319 # --------------------------------------------------------------------
320 # Returns total commands and completed commands counts
321 # --------------------------------------------------------------------
322 } elsif( $type eq 'summary' ) {
323 my $session = ol_find_session();
325 $logger->debug("offline: retrieving summary info ".
326 "for session ".$session->key." with completed=".$session->num_complete);
329 $count += $_->count for ($session->scripts);
331 { total => $count, num_complete => $session->num_complete });
335 # --------------------------------------------------------------------
336 # Returns the list of non-SUCCESS events that have occurred so far for
337 # this set of commands
338 # --------------------------------------------------------------------
339 } elsif( $type eq 'exceptions' ) {
341 my $session = ol_find_session();
342 my $resfile = "$basedir/pending/$org/$seskey/results";
343 if( $session->end_time ) {
344 $resfile = "$basedir/archive/$org/$seskey/results";
346 my $data = ol_file_to_perl($resfile);
349 my $evt = $d->{event};
350 $evt = $evt->[0] if ref $evt eq 'ARRAY';
351 push(@$data2, $d) if $evt->{ilsevent} ne '0';
353 #$data = [ grep { $_->{event}->{ilsevent} ne '0' } @$data ];
354 ol_handle_result($data2);
359 sub ol_fetch_workstation {
361 $logger->debug("offline: Fetching workstation $name");
362 my $ws = $U->storagereq(
363 'open-ils.storage.direct.actor.workstation.search.name', $name);
364 ol_handle_result(OpenILS::Event->new('ACTOR_WORKSTATION_NOT_FOUND')) unless $ws;
371 # --------------------------------------------------------------------
372 # Sorts the script commands then forks a child to executes them.
373 # --------------------------------------------------------------------
376 my $session = ol_find_session();
377 ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
378 ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
380 my $commands = ol_collect_commands();
382 # --------------------------------------------------------------------
383 # Note that we must disconnect from opensrf before forking or the
384 # connection will be borked...
385 # --------------------------------------------------------------------
386 OpenSRF::Transport::PeerHandle->retrieve->disconnect;
392 # --------------------------------------------------------------------
393 # Tell the client all is well
394 # --------------------------------------------------------------------
395 ol_handle_event('SUCCESS'); # - this exits
400 # --------------------------------------------------------------------
401 # close stdout/stderr or apache will wait on the child to finish
402 # --------------------------------------------------------------------
406 $logger->debug("offline: child $$ processing data...");
408 # --------------------------------------------------------------------
409 # The child re-connects to the opensrf network and processes
410 # the script requests
411 # --------------------------------------------------------------------
412 OpenSRF::System->bootstrap_client(config_file => $bootstrap);
417 #Class::DBI->autoupdate(1);
421 my $sesion = ol_find_session();
422 $session->in_process(1);
423 ol_process_commands($session, $commands);
424 ol_archive_files($session);
428 $logger->error("offline: child process error $e");
433 sub ol_file_to_perl {
435 open(F, "$fname") or ol_handle_event('OFFLINE_FILE_ERROR');
438 push(@p, OpenSRF::Utils::JSON->JSON2perl($_)) for @d;
443 # collects the commands and sorts them on timestamp+delta
444 sub ol_collect_commands {
445 my $ses = ol_find_session();
448 # cycle through every script loaded to this session
449 for my $script ($ses->scripts) {
450 my $coms = ol_file_to_perl($script->logfile);
452 # cycle through all of the commands for this script
453 for my $com (@$coms) {
454 $$com{_workstation} = $script->workstation;
455 $$com{_realtime} = $script->time_delta + $com->{timestamp};
456 push( @commands, $com );
460 # make sure thera are no blank commands
461 @commands = grep { ($_ and $_->{type}) } @commands;
464 @commands = sort { $a->{_realtime} <=> $b->{_realtime} } @commands;
466 # push user registrations to the front
467 my @regs = grep { $_->{type} eq 'register' } @commands;
468 my @others = grep { $_->{type} ne 'register' } @commands;
470 return [ @regs, @others ];
474 my $time = shift || CORE::time;
475 my (undef,undef, undef, $mday,$mon,$year) = localtime($time);
476 $mon++; $year += 1900;
477 $mday = "0$mday" unless $mday =~ /\d{2}/o;
478 $mon = "0$mon" unless $mon =~ /\d{2}/o;
479 return ($year, $mon, $mday);
483 # --------------------------------------------------------------------
484 # Moves all files from the pending directory to the archive directory
485 # and removes the pending directory
486 # --------------------------------------------------------------------
487 sub ol_archive_files {
489 my ($y, $m, $d) = ol_date();
491 my $dir = "$basedir/pending/$org/$seskey";
492 my $archdir = "$basedir/archive/$org/$seskey";
493 $logger->debug("offline: archiving files to $archdir");
495 # Tell the db the files are moving
496 $_->logfile($archdir.'/'.$_->workstation.'.log') for ($session->scripts);
498 qx/mkdir -p $archdir/;
499 qx/mv $_ $archdir/ for <$dir/*>;
504 # --------------------------------------------------------------------
505 # Appends results to the results file.
506 # --------------------------------------------------------------------
508 sub ol_append_result {
513 $obj = OpenSRF::Utils::JSON->perl2JSON($obj);
516 open($rhandle, ">>$basedir/pending/$org/$seskey/results")
517 or ol_handle_event('OFFLINE_FILE_ERROR');
520 print $rhandle "$obj\n";
521 close($rhandle) if $last;
526 # --------------------------------------------------------------------
527 # Runs the commands and returns the list of errors
528 # --------------------------------------------------------------------
529 sub ol_process_commands {
532 my $commands = shift;
535 $session->start_time(CORE::time);
537 for my $d ( @$commands ) {
540 my $last = ($x++ == scalar(@$commands) - 1) ? 1 : 0;
541 my $res = { command => $d };
547 $logger->debug("offline: top of execute loop : $t");
550 $res->{event} = ol_handle_checkin($d) if $t eq 'checkin';
551 $res->{event} = ol_handle_inhouse($d) if $t eq 'in_house_use';
552 $res->{event} = ol_handle_checkout($d) if $t eq 'checkout';
553 $res->{event} = ol_handle_renew($d) if $t eq 'renew';
554 $res->{event} = ol_handle_register($d) if $t eq 'register';
556 } catch Error with { $err = shift; };
560 if( ref($err) eq 'OpenSRF::EX::JabberDisconnected' ) {
561 $logger->error("offline: we lost jabber .. trying to reconnect");
565 $res->{event} = OpenILS::Event->new('INTERNAL_SERVER_ERROR', debug => "$err");
574 ol_append_result($res, $last);
575 $session->num_complete( $session->num_complete + 1 );
577 $logger->debug("offline: set session [".$session->key."] num_complete to ".$session->num_complete);
580 $session->end_time(CORE::time);
581 $session->in_process(0);
585 # --------------------------------------------------------------------
586 # Runs an in_house_use action
587 # --------------------------------------------------------------------
588 sub ol_handle_inhouse {
591 my $realtime = $command->{_realtime};
592 my $ws = $command->{_workstation};
593 my $barcode = $command->{barcode};
594 my $count = $command->{count} || 1;
595 my $use_time = $command->{use_time} || "";
597 $logger->activity("offline: in_house_use : requestor=". $requestor->id.", realtime=$realtime, ".
598 "workstation=$ws, barcode=$barcode, count=$count, use_time=$use_time");
601 return OpenILS::Event->new(
602 'INTERNAL_SERVER_ERROR', payload => 'TOO MANY IN HOUSE USE');
605 my $ids = $U->simplereq(
607 'open-ils.circ.in_house_use.create', $authtoken,
608 { barcode => $barcode, count => $count, location => $org, use_time => $use_time } );
610 return OpenILS::Event->new('SUCCESS', payload => $ids) if( ref($ids) eq 'ARRAY' );
616 # --------------------------------------------------------------------
617 # Pulls the relevant circ args from the command, fetches data where
619 # --------------------------------------------------------------------
621 sub ol_circ_args_from_command {
624 my $type = $command->{type};
625 my $realtime = $command->{_realtime};
626 my $ws = $command->{_workstation};
627 my $barcode = $command->{barcode} || "";
628 my $cotime = $command->{checkout_time} || "";
629 my $pbc = $command->{patron_barcode};
630 my $due_date = $command->{due_date} || "";
631 my $noncat = ($command->{noncat}) ? "yes" : "no"; # for logging
633 $logger->activity("offline: $type : requestor=". $requestor->id.
634 ", realtime=$realtime, workstation=$ws, checkout_time=$cotime, ".
635 "patron=$pbc, due_date=$due_date, noncat=$noncat");
639 permit_override => 1,
641 checkout_time => $cotime,
642 patron_barcode => $pbc,
643 due_date => $due_date
646 if(ol_get_org_setting('circ.offline.username_allowed')) {
648 my $format = ol_get_org_setting('opac.barcode_regex');
651 $format =~ s/^\/|\/$//g; # remove any couching //'s
652 if($pbc !~ qr/$format/) {
654 # the patron barcode does not match the configured barcode format
655 # assume they passed a username instead
657 my $user_id = $user_id_cache{$pbc} ||
660 'open-ils.actor.username.exists',
665 # a valid username was provided, update the args and cache
666 $user_id_cache{$pbc} = $user_id;
667 $args->{patron_id} = $user_id;
668 delete $args->{patron_barcode};
675 if( $command->{noncat} ) {
677 $args->{noncat_type} = $command->{noncat_type};
678 $args->{noncat_count} = $command->{noncat_count};
684 sub ol_get_org_setting {
686 return $U->simplereq(
688 'open-ils.actor.ou_setting.ancestor_default',
689 $org, $name, $authtoken);
694 # --------------------------------------------------------------------
695 # Performs a checkout action
696 # --------------------------------------------------------------------
697 sub ol_handle_checkout {
699 my $args = ol_circ_args_from_command($command);
701 if( $args->{noncat} and $args->{noncat_count} > 99 ) {
702 return OpenILS::Event->new(
703 'INTERNAL_SERVER_ERROR', payload => 'TOO MANY NON CATS');
706 if( $args->{barcode} ) {
708 # $c becomes the Copy
709 # $e possibily becomes the Exception
710 my( $c, $e ) = $U->fetch_copy_by_barcode($args->{barcode});
713 my $barcode = $args->{barcode};
714 # Have to have this config option (or org setting) and a
715 # status_changed_time for skippage, and barcode not seen before
718 'circ.offline.skip_checkout_if_newer_status_changed_time'
720 || $config{skip_late}
722 && length($c->status_changed_time())
723 && ! $seen_barcode{$barcode}
725 $seen_barcode{$barcode} = 1;
726 my $cts = DateTime::Format::ISO8601->parse_datetime( cleanse_ISO8601($c->status_changed_time()) )->epoch();
727 my $xts = $command->{timestamp}; # Transaction Time Stamp
728 $logger->activity("offline: ol_handle_checkout: considering status_changed_time for barcode=$barcode, cts=$cts, xts=$xts");
730 # Asset has changed after this transaction, ignore
732 $skip_barcode_for_status_changed{$barcode} = 1;
735 $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'));
737 if ($skip_barcode_for_status_changed{$barcode}) {
738 $logger->activity("offline: ol_handle_checkout: barcode=$barcode has SKIP_ASSET_CHANGED");
739 return OpenILS::Event->new(
745 my $evt = $U->simplereq(
746 'open-ils.circ', 'open-ils.circ.checkout', $authtoken, $args );
748 # if the item is already checked out to this user and we are past
749 # the configured auto-renewal interval, try to renew the circ.
750 if( ref $evt ne 'ARRAY' and
751 $evt->{textcode} == 'OPEN_CIRCULATION_EXISTS' and
752 $evt->{payload}->{auto_renew}) {
754 return ol_handle_renew($command);
761 # --------------------------------------------------------------------
762 # Performs the renewal action
763 # --------------------------------------------------------------------
764 sub ol_handle_renew {
766 my $args = ol_circ_args_from_command($command);
769 if( $args->{barcode} ) {
771 # $c becomes the Copy
772 # $e possibily becomes the Exception
773 my( $c, $e ) = $U->fetch_copy_by_barcode($args->{barcode});
776 my $barcode = $args->{barcode};
777 # Have to have this config option (or org setting) and a
778 # status_changed_time for skippage, and barcode not seen before
781 'circ.offline.skip_renew_if_newer_status_changed_time'
783 || $config{skip_late}
785 && length($c->status_changed_time())
786 && ! $seen_barcode{$barcode}
788 $seen_barcode{$barcode} = 1;
789 my $cts = DateTime::Format::ISO8601->parse_datetime( cleanse_ISO8601($c->status_changed_time()) )->epoch();
790 my $xts = $command->{timestamp}; # Transaction Time Stamp
791 $logger->activity("offline: ol_handle_renew: considering status_changed_time for barcode=$barcode, cts=$cts, xts=$xts");
793 # Asset has changed after this transaction, ignore
795 $skip_barcode_for_status_changed{$barcode} = 1;
798 $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'));
800 if ($skip_barcode_for_status_changed{$barcode}) {
801 $logger->activity("offline: ol_handle_renew: barcode=$barcode has SKIP_ASSET_CHANGED");
802 return OpenILS::Event->new(
808 return $U->simplereq(
809 'open-ils.circ', 'open-ils.circ.renew.override', $authtoken, $args );
813 # --------------------------------------------------------------------
814 # Runs a checkin action
815 # --------------------------------------------------------------------
816 sub ol_handle_checkin {
819 my $realtime = $command->{_realtime};
820 my $ws = $command->{_workstation};
821 my $barcode = $command->{barcode};
822 my $backdate = $command->{backdate} || "";
824 $logger->activity("offline: checkin : requestor=". $requestor->id.
825 ", realtime=$realtime, ". "workstation=$ws, barcode=$barcode, backdate=$backdate");
829 # $c becomes the Copy
830 # $e possibily becomes the Exception
831 my( $c, $e ) = $U->fetch_copy_by_barcode($barcode);
834 # Have to have this config option (or org setting) and a
835 # status_changed_time for skippage, and barcode not seen before
838 'circ.offline.skip_checkin_if_newer_status_changed_time'
840 || $config{skip_late}
842 && length($c->status_changed_time())
843 && ! $seen_barcode{$barcode}
845 $seen_barcode{$barcode} = 1;
846 my $cts = DateTime::Format::ISO8601->parse_datetime( cleanse_ISO8601($c->status_changed_time()) )->epoch();
847 my $xts = $command->{timestamp}; # Transaction Time Stamp
848 $logger->activity("offline: ol_handle_checkin: considering status_changed_time for barcode=$barcode, cts=$cts, xts=$xts");
850 # Asset has changed after this transaction, ignore
852 $skip_barcode_for_status_changed{$barcode} = 1;
855 $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'));
857 if ($skip_barcode_for_status_changed{$barcode}) {
858 $logger->activity("offline: ol_handle_checkin: barcode=$barcode has SKIP_ASSET_CHANGED");
859 return OpenILS::Event->new(
865 return $U->simplereq(
867 'open-ils.circ.checkin', $authtoken,
868 { barcode => $barcode, backdate => $backdate } );
873 # --------------------------------------------------------------------
874 # Registers a new patron
875 # --------------------------------------------------------------------
876 sub ol_handle_register {
879 my $barcode = $command->{user}->{card}->{barcode};
880 delete $command->{user}->{card};
881 delete $command->{user}->{cards} if $command->{user}->{cards};
883 $logger->info("offline: creating new user with barcode $barcode");
885 # now, create the user
886 my $actor = Fieldmapper::actor::user->new;
887 my $card = Fieldmapper::actor::card->new;
890 # username defaults to the barcode
891 $actor->usrname( ($actor->usrname) ? $actor->usrname : $barcode );
893 # Set up all of the virtual IDs, isnew, etc.
897 $actor->cards([$card]);
902 $card->barcode($barcode);
908 for my $resp (@{$command->{user}->{survey_responses}}) {
909 my $sr = Fieldmapper::action::survey_response->new;
910 $sr->$_( $resp->{$_} ) for keys %$resp;
914 $logger->debug("offline: created new survey response for survey ".$sr->survey);
916 delete $command->{user}->{survey_responses};
917 $actor->survey_responses(\@sresp) if @sresp;
920 # extract the billing address
921 if( my $addr = $command->{user}->{billing_address} ) {
922 $bid = $command->{user}->{billing_address}->{id};
923 $billing_address = Fieldmapper::actor::user_address->new;
924 $billing_address->$_($addr->{$_}) for keys %$addr;
925 $billing_address->isnew(1);
926 $billing_address->id(-1);
927 $billing_address->usr(-1);
928 delete $command->{user}->{billing_address};
929 $logger->debug("offline: read billing address ".$billing_address->street1);
933 # extract the mailing address
934 if( my $addr = $command->{user}->{mailing_address} ) {
935 $mid = $command->{user}->{mailing_address}->{id};
936 if ($webclient && $mid != $bid) {
937 $mailing_address = Fieldmapper::actor::user_address->new;
938 $mailing_address->$_($addr->{$_}) for keys %$addr;
939 $mailing_address->isnew(1);
940 $mailing_address->id(-2);
941 $mailing_address->usr(-1);
942 $logger->debug("offline: read mailing address ".$mailing_address->street1);
943 } elsif (!$webclient) {
944 $mailing_address = Fieldmapper::actor::user_address->new;
945 $mailing_address->$_($addr->{$_}) for keys %$addr;
946 $mailing_address->isnew(1);
947 $mailing_address->id(-2);
948 $mailing_address->usr(-1);
949 $logger->debug("offline: read mailing address ".$mailing_address->street1);
951 delete $command->{user}->{mailing_address};
954 # make sure we have values for both
955 $billing_address ||= $mailing_address;
956 $mailing_address ||= $billing_address;
958 $actor->billing_address($billing_address->id);
959 $actor->mailing_address($mailing_address->id);
960 $actor->addresses([$mailing_address]);
962 push( @{$actor->addresses}, $billing_address )
963 unless $billing_address->id eq $mailing_address->id;
966 for my $a ( @{$command->{user}->{addresses}} ) {
967 next if ($a->{id} == $bid || $a->{id} == $mid);
968 # extract all other addresses
969 my $addr = Fieldmapper::actor::user_address->new;
970 $addr->$_($a->{$_}) for keys %$a;
974 $logger->debug("offline: read other address ".$addr->street1);
976 push( @{$actor->addresses}, $addr );
979 # pull all of the rest of the data from the command blob
980 $actor->$_( $command->{user}->{$_} ) for grep { $_ ne 'addresses' } keys %{$command->{user}};
982 # calculate the expire date for the patron based on the profile group
983 my ($grp) = grep {$_->id == $actor->profile} @$user_groups;
985 my $seconds = OpenSRF::Utils->interval_to_seconds($grp->perm_interval);
986 my $expire_date = DateTime->from_epoch(epoch => DateTime->now->epoch + $seconds)->epoch;
987 $logger->debug("offline: setting expire date to $expire_date");
988 $actor->expire_date($U->epoch2ISO8601($expire_date));
991 $logger->debug("offline: creating user object...");
992 $actor = $U->simplereq(
994 'open-ils.actor.patron.update', $authtoken, $actor);
996 return $actor if(ref($actor) eq 'HASH'); # an event occurred
998 return OpenILS::Event->new('SUCCESS', payload => $actor);