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/;
14 use OpenILS::Utils::DateTime qw/clean_ISO8601/;
15 use OpenILS::Utils::OfflineStore;
16 use OpenSRF::Utils::SettingsClient;
19 use DateTime::Format::ISO8601;
24 my $U = "OpenILS::Application::AppUtils";
25 my $DB = "OpenILS::Utils::OfflineStore";
26 my $SES = "${DB}::Session";
27 my $SCRIPT = "OpenILS::Utils::OfflineStore::Script";
30 # Used by the functionality that produces SKIP_ASSET_CHANGED events
31 my %seen_barcode = ();
32 my %skip_barcode_for_status_changed = ();
34 # --------------------------------------------------------------------
36 # --------------------------------------------------------------------
38 do '##CONFIG##/offline-config.pl';
42 my $basedir = $config{base_dir} || die "Offline config error: no base_dir defined\n";
43 my $bootstrap = $config{bootstrap} || die "Offline config error: no bootstrap defined\n";
44 my $webclient = $cgi->param('wc');
45 my $wsname = $cgi->param('ws');
46 my $org = $cgi->param('org');
47 my $authtoken = $cgi->param('ses') || "";
48 my $seskey = $cgi->param('seskey');
49 my $action = $cgi->param('action'); # - create, load, execute, status
61 # --------------------------------------------------------------------
63 # This function should behave as a child_init might behave in case
64 # this is moved to mod_perl
65 # --------------------------------------------------------------------
67 $DB->DBFile($config{dsn}, $config{usr}, $config{pw});
72 OpenSRF::System->bootstrap_client(config_file => $bootstrap );
73 Fieldmapper->import(IDL =>
74 OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
79 sub _ol_debug_params {
81 my @params = $cgi->param;
82 @params = sort { $a cmp $b } @params;
83 $s .= "$_=" . $cgi->param($_) . "\n" for @params;
85 warn '-'x60 ."\n$s\n";
89 # --------------------------------------------------------------------
90 # Finds the requestor and other info specific to this request
91 # --------------------------------------------------------------------
94 # fetch the requestor object
95 ($requestor, $evt) = $U->checkses($authtoken);
96 ol_handle_result($evt) if $evt;
98 # try the param, the workstation, and finally the user's ws org
100 $wsobj = ol_fetch_workstation($wsname);
101 $org = $wsobj->owning_lib if $wsobj;
102 $org = $requestor->ws_ou unless $org;
103 ol_handle_result(OpenILS::Event->new('OFFLINE_NO_ORG')) unless $org;
106 $user_groups = $U->simplereq(
107 'open-ils.actor', 'open-ils.actor.groups.retrieve');
111 # --------------------------------------------------------------------
112 # Runs the requested action
113 # --------------------------------------------------------------------
118 if( $action eq 'create' ) {
120 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
121 ol_handle_result($evt) if $evt;
122 $payload = ol_create_session();
124 } elsif( $action eq 'load' ) {
126 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
127 ol_handle_result($evt) if $evt;
128 $payload = ol_load();
130 } elsif( $action eq 'execute' ) {
132 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_EXECUTE');
133 ol_handle_result($evt) if $evt;
134 $payload = ol_execute();
136 } elsif( $action eq 'status' ) {
138 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_VIEW');
139 ol_handle_result($evt) if $evt;
140 $payload = ol_status();
143 ol_handle_event('SUCCESS', payload => $payload );
147 # --------------------------------------------------------------------
148 # Creates a new session
149 # --------------------------------------------------------------------
150 sub ol_create_session {
152 my $desc = $cgi->param('desc') || "";
153 $seskey = time . "_${$}_" . int(rand() * 1000);
155 $logger->debug("offline: user ".$requestor->id.
156 " creating new session with key $seskey and description $desc");
162 description => $desc,
163 creator => $requestor->id,
164 create_time => CORE::time(),
173 # --------------------------------------------------------------------
174 # Holds the meta-info for a script file
175 # --------------------------------------------------------------------
176 sub ol_create_script {
179 my $session = ol_find_session($seskey);
180 my $delta = $cgi->param('delta') || 0;
182 my $script = $session->add_to_scripts(
184 requestor => $requestor->id,
185 create_time => CORE::time,
186 workstation => $wsname,
187 logfile => "$basedir/pending/$org/$seskey/$wsname.log",
188 time_delta => $delta,
194 # --------------------------------------------------------------------
195 # Finds the current session in the db
196 # --------------------------------------------------------------------
197 sub ol_find_session {
198 my $ses = $SES->retrieve($seskey);
199 ol_handle_event('OFFLINE_INVALID_SESSION', payload => $seskey) unless $ses;
203 # --------------------------------------------------------------------
204 # Finds a script object in the DB based on workstation and seskey
205 # --------------------------------------------------------------------
207 my $ws = shift || $wsname;
208 my $sk = shift || $seskey;
209 my ($script) = $SCRIPT->search( session => $seskey, workstation => $ws );
213 # --------------------------------------------------------------------
214 # Creates a new script in the database and loads the new script file
215 # --------------------------------------------------------------------
218 my $session = ol_find_session;
219 my $handle = $cgi->upload('file');
220 my $outdir = "$basedir/pending/$org/$seskey";
221 my $outfile = "$outdir/$wsname.log";
223 ol_handle_event('OFFLINE_SESSION_FILE_EXISTS') if ol_find_script();
224 ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
225 ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
227 qx/mkdir -p $outdir/;
229 open(FILE, ">>$outfile") or ol_handle_event('OFFLINE_FILE_ERROR');
230 while( <$handle> ) { print FILE; $x++;}
233 ol_create_script($x);
239 # --------------------------------------------------------------------
240 sub ol_handle_result {
242 my $json = OpenSRF::Utils::JSON->perl2JSON($obj);
244 # Clear this so it's not remembered
247 if( $cgi->param('html')) {
248 my $html = "<html><body onload='xulG.handle_event($json)'></body></html>";
249 print "content-type: text/html\n\n";
254 print "content-type: text/plain\n\n";
261 # --------------------------------------------------------------------
262 sub ol_handle_event {
263 my( $evt, @args ) = @_;
264 ol_handle_result(OpenILS::Event->new($evt, @args));
268 # --------------------------------------------------------------------
269 sub ol_flesh_session {
273 map { $data{$_} = $session->$_ } $session->columns;
276 for my $script ($session->scripts) {
278 map { $sdata{$_} = $script->$_ } $script->columns;
280 # the client doesn't need this info
281 delete $sdata{session};
283 delete $sdata{logfile};
285 push( @{$data{scripts}}, \%sdata );
292 # --------------------------------------------------------------------
293 # Returns various information on the sessions and scripts
294 # --------------------------------------------------------------------
297 my $type = $cgi->param('status_type') || "scripts";
299 # --------------------------------------------------------------------
300 # Returns info on every script upload attached to the current session
301 # --------------------------------------------------------------------
302 if( $type eq 'scripts' ) {
303 my $session = ol_find_session();
304 ol_handle_result(ol_flesh_session($session));
307 # --------------------------------------------------------------------
308 # Returns all scripts and sessions for the given org
309 # --------------------------------------------------------------------
310 } elsif( $type eq 'sessions' ) {
311 my @sessions = $SES->search( org => $org );
313 # can I do this in the DB without raw SQL?
314 @sessions = sort { $a->create_time <=> $b->create_time } @sessions;
316 push( @data, ol_flesh_session($_) ) for @sessions;
317 ol_handle_result(\@data);
320 # --------------------------------------------------------------------
321 # Returns total commands and completed commands counts
322 # --------------------------------------------------------------------
323 } elsif( $type eq 'summary' ) {
324 my $session = ol_find_session();
326 $logger->debug("offline: retrieving summary info ".
327 "for session ".$session->key." with completed=".$session->num_complete);
330 $count += $_->count for ($session->scripts);
332 { total => $count, num_complete => $session->num_complete });
336 # --------------------------------------------------------------------
337 # Returns the list of non-SUCCESS events that have occurred so far for
338 # this set of commands
339 # --------------------------------------------------------------------
340 } elsif( $type eq 'exceptions' ) {
342 my $session = ol_find_session();
343 my $resfile = "$basedir/pending/$org/$seskey/results";
344 if( $session->end_time ) {
345 $resfile = "$basedir/archive/$org/$seskey/results";
347 my $data = ol_file_to_perl($resfile);
350 my $evt = $d->{event};
351 $evt = $evt->[0] if ref $evt eq 'ARRAY';
352 push(@$data2, $d) if $evt->{ilsevent} ne '0';
354 #$data = [ grep { $_->{event}->{ilsevent} ne '0' } @$data ];
355 ol_handle_result($data2);
360 sub ol_fetch_workstation {
362 $logger->debug("offline: Fetching workstation $name");
363 my $ws = $U->storagereq(
364 'open-ils.storage.direct.actor.workstation.search.name', $name);
365 ol_handle_result(OpenILS::Event->new('ACTOR_WORKSTATION_NOT_FOUND')) unless $ws;
372 # --------------------------------------------------------------------
373 # Sorts the script commands then forks a child to executes them.
374 # --------------------------------------------------------------------
377 my $session = ol_find_session();
378 ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
379 ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
381 my $commands = ol_collect_commands();
383 # --------------------------------------------------------------------
384 # Note that we must disconnect from opensrf before forking or the
385 # connection will be borked...
386 # --------------------------------------------------------------------
387 OpenSRF::Transport::PeerHandle->retrieve->disconnect;
393 # --------------------------------------------------------------------
394 # Tell the client all is well
395 # --------------------------------------------------------------------
396 ol_handle_event('SUCCESS'); # - this exits
401 # --------------------------------------------------------------------
402 # close stdout/stderr or apache will wait on the child to finish
403 # --------------------------------------------------------------------
407 $logger->debug("offline: child $$ processing data...");
409 # --------------------------------------------------------------------
410 # The child re-connects to the opensrf network and processes
411 # the script requests
412 # --------------------------------------------------------------------
413 OpenSRF::System->bootstrap_client(config_file => $bootstrap);
418 #Class::DBI->autoupdate(1);
422 my $sesion = ol_find_session();
423 $session->in_process(1);
424 ol_process_commands($session, $commands);
425 ol_archive_files($session);
429 $logger->error("offline: child process error $e");
434 sub ol_file_to_perl {
436 open(F, "$fname") or ol_handle_event('OFFLINE_FILE_ERROR');
439 push(@p, OpenSRF::Utils::JSON->JSON2perl($_)) for @d;
444 # collects the commands and sorts them on timestamp+delta
445 sub ol_collect_commands {
446 my $ses = ol_find_session();
449 # cycle through every script loaded to this session
450 for my $script ($ses->scripts) {
451 my $coms = ol_file_to_perl($script->logfile);
453 # cycle through all of the commands for this script
454 for my $com (@$coms) {
455 $$com{_workstation} = $script->workstation;
456 $$com{_realtime} = $script->time_delta + $com->{timestamp};
457 push( @commands, $com );
461 # make sure thera are no blank commands
462 @commands = grep { ($_ and $_->{type}) } @commands;
465 @commands = sort { $a->{_realtime} <=> $b->{_realtime} } @commands;
467 # push user registrations to the front
468 my @regs = grep { $_->{type} eq 'register' } @commands;
469 my @others = grep { $_->{type} ne 'register' } @commands;
471 return [ @regs, @others ];
475 my $time = shift || CORE::time;
476 my (undef,undef, undef, $mday,$mon,$year) = localtime($time);
477 $mon++; $year += 1900;
478 $mday = "0$mday" unless $mday =~ /\d{2}/o;
479 $mon = "0$mon" unless $mon =~ /\d{2}/o;
480 return ($year, $mon, $mday);
484 # --------------------------------------------------------------------
485 # Moves all files from the pending directory to the archive directory
486 # and removes the pending directory
487 # --------------------------------------------------------------------
488 sub ol_archive_files {
490 my ($y, $m, $d) = ol_date();
492 my $dir = "$basedir/pending/$org/$seskey";
493 my $archdir = "$basedir/archive/$org/$seskey";
494 $logger->debug("offline: archiving files to $archdir");
496 # Tell the db the files are moving
497 $_->logfile($archdir.'/'.$_->workstation.'.log') for ($session->scripts);
499 qx/mkdir -p $archdir/;
500 qx/mv $_ $archdir/ for <$dir/*>;
505 # --------------------------------------------------------------------
506 # Appends results to the results file.
507 # --------------------------------------------------------------------
509 sub ol_append_result {
514 $obj = OpenSRF::Utils::JSON->perl2JSON($obj);
517 open($rhandle, ">>$basedir/pending/$org/$seskey/results")
518 or ol_handle_event('OFFLINE_FILE_ERROR');
521 print $rhandle "$obj\n";
522 close($rhandle) if $last;
527 # --------------------------------------------------------------------
528 # Runs the commands and returns the list of errors
529 # --------------------------------------------------------------------
530 sub ol_process_commands {
533 my $commands = shift;
536 $session->start_time(CORE::time);
538 for my $d ( @$commands ) {
541 my $last = ($x++ == scalar(@$commands) - 1) ? 1 : 0;
542 my $res = { command => $d };
548 $logger->debug("offline: top of execute loop : $t");
551 $res->{event} = ol_handle_checkin($d) if $t eq 'checkin';
552 $res->{event} = ol_handle_inhouse($d) if $t eq 'in_house_use';
553 $res->{event} = ol_handle_checkout($d) if $t eq 'checkout';
554 $res->{event} = ol_handle_renew($d) if $t eq 'renew';
555 $res->{event} = ol_handle_register($d) if $t eq 'register';
557 } catch Error with { $err = shift; };
561 if( ref($err) eq 'OpenSRF::EX::JabberDisconnected' ) {
562 $logger->error("offline: we lost jabber .. trying to reconnect");
566 $res->{event} = OpenILS::Event->new('INTERNAL_SERVER_ERROR', debug => "$err");
575 ol_append_result($res, $last);
576 $session->num_complete( $session->num_complete + 1 );
578 $logger->debug("offline: set session [".$session->key."] num_complete to ".$session->num_complete);
581 $session->end_time(CORE::time);
582 $session->in_process(0);
586 # --------------------------------------------------------------------
587 # Runs an in_house_use action
588 # --------------------------------------------------------------------
589 sub ol_handle_inhouse {
592 my $realtime = $command->{_realtime};
593 my $ws = $command->{_workstation};
594 my $barcode = $command->{barcode};
595 my $count = $command->{count} || 1;
596 my $use_time = $command->{use_time} || "";
598 $logger->activity("offline: in_house_use : requestor=". $requestor->id.", realtime=$realtime, ".
599 "workstation=$ws, barcode=$barcode, count=$count, use_time=$use_time");
602 return OpenILS::Event->new(
603 'INTERNAL_SERVER_ERROR', payload => 'TOO MANY IN HOUSE USE');
606 my $ids = $U->simplereq(
608 'open-ils.circ.in_house_use.create', $authtoken,
609 { barcode => $barcode, count => $count, location => $org, use_time => $use_time } );
611 return OpenILS::Event->new('SUCCESS', payload => $ids) if( ref($ids) eq 'ARRAY' );
617 # --------------------------------------------------------------------
618 # Pulls the relevant circ args from the command, fetches data where
620 # --------------------------------------------------------------------
622 sub ol_circ_args_from_command {
625 my $type = $command->{type};
626 my $realtime = $command->{_realtime};
627 my $ws = $command->{_workstation};
628 my $barcode = $command->{barcode} || "";
629 my $cotime = $command->{checkout_time} || "";
630 my $pbc = $command->{patron_barcode};
631 my $due_date = $command->{due_date} || "";
632 my $noncat = ($command->{noncat}) ? "yes" : "no"; # for logging
634 $logger->activity("offline: $type : requestor=". $requestor->id.
635 ", realtime=$realtime, workstation=$ws, checkout_time=$cotime, ".
636 "patron=$pbc, due_date=$due_date, noncat=$noncat");
640 permit_override => 1,
642 checkout_time => $cotime,
643 patron_barcode => $pbc,
644 due_date => $due_date
647 if(ol_get_org_setting('circ.offline.username_allowed')) {
649 my $format = ol_get_org_setting('opac.barcode_regex');
652 $format =~ s/^\/|\/$//g; # remove any couching //'s
653 if($pbc !~ qr/$format/) {
655 # the patron barcode does not match the configured barcode format
656 # assume they passed a username instead
658 my $user_id = $user_id_cache{$pbc} ||
661 'open-ils.actor.username.exists',
666 # a valid username was provided, update the args and cache
667 $user_id_cache{$pbc} = $user_id;
668 $args->{patron_id} = $user_id;
669 delete $args->{patron_barcode};
676 if( $command->{noncat} ) {
678 $args->{noncat_type} = $command->{noncat_type};
679 $args->{noncat_count} = $command->{noncat_count};
685 sub ol_get_org_setting {
687 return $U->simplereq(
689 'open-ils.actor.ou_setting.ancestor_default',
690 $org, $name, $authtoken);
695 # --------------------------------------------------------------------
696 # Performs a checkout action
697 # --------------------------------------------------------------------
698 sub ol_handle_checkout {
700 my $args = ol_circ_args_from_command($command);
702 if( $args->{noncat} and $args->{noncat_count} > 99 ) {
703 return OpenILS::Event->new(
704 'INTERNAL_SERVER_ERROR', payload => 'TOO MANY NON CATS');
707 if( $args->{barcode} ) {
709 # $c becomes the Copy
710 # $e possibily becomes the Exception
711 my( $c, $e ) = $U->fetch_copy_by_barcode($args->{barcode});
714 my $barcode = $args->{barcode};
715 # Have to have this config option (or org setting) and a
716 # status_changed_time for skippage, and barcode not seen before
719 'circ.offline.skip_checkout_if_newer_status_changed_time'
721 || $config{skip_late}
723 && length($c->status_changed_time())
724 && ! $seen_barcode{$barcode}
726 $seen_barcode{$barcode} = 1;
727 my $cts = DateTime::Format::ISO8601->parse_datetime( clean_ISO8601($c->status_changed_time()) )->epoch();
728 my $xts = $command->{timestamp}; # Transaction Time Stamp
729 $logger->activity("offline: ol_handle_checkout: considering status_changed_time for barcode=$barcode, cts=$cts, xts=$xts");
731 # Asset has changed after this transaction, ignore
733 $skip_barcode_for_status_changed{$barcode} = 1;
736 $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'));
738 if ($skip_barcode_for_status_changed{$barcode}) {
739 $logger->activity("offline: ol_handle_checkout: barcode=$barcode has SKIP_ASSET_CHANGED");
740 return OpenILS::Event->new(
746 my $evt = $U->simplereq(
747 'open-ils.circ', 'open-ils.circ.checkout', $authtoken, $args );
749 # if the item is already checked out to this user and we are past
750 # the configured auto-renewal interval, try to renew the circ.
751 if( ref $evt ne 'ARRAY' and
752 $evt->{textcode} == 'OPEN_CIRCULATION_EXISTS' and
753 $evt->{payload}->{auto_renew}) {
755 return ol_handle_renew($command);
762 # --------------------------------------------------------------------
763 # Performs the renewal action
764 # --------------------------------------------------------------------
765 sub ol_handle_renew {
767 my $args = ol_circ_args_from_command($command);
770 if( $args->{barcode} ) {
772 # $c becomes the Copy
773 # $e possibily becomes the Exception
774 my( $c, $e ) = $U->fetch_copy_by_barcode($args->{barcode});
777 my $barcode = $args->{barcode};
778 # Have to have this config option (or org setting) and a
779 # status_changed_time for skippage, and barcode not seen before
782 'circ.offline.skip_renew_if_newer_status_changed_time'
784 || $config{skip_late}
786 && length($c->status_changed_time())
787 && ! $seen_barcode{$barcode}
789 $seen_barcode{$barcode} = 1;
790 my $cts = DateTime::Format::ISO8601->parse_datetime( clean_ISO8601($c->status_changed_time()) )->epoch();
791 my $xts = $command->{timestamp}; # Transaction Time Stamp
792 $logger->activity("offline: ol_handle_renew: considering status_changed_time for barcode=$barcode, cts=$cts, xts=$xts");
794 # Asset has changed after this transaction, ignore
796 $skip_barcode_for_status_changed{$barcode} = 1;
799 $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'));
801 if ($skip_barcode_for_status_changed{$barcode}) {
802 $logger->activity("offline: ol_handle_renew: barcode=$barcode has SKIP_ASSET_CHANGED");
803 return OpenILS::Event->new(
809 return $U->simplereq(
810 'open-ils.circ', 'open-ils.circ.renew.override', $authtoken, $args );
814 # --------------------------------------------------------------------
815 # Runs a checkin action
816 # --------------------------------------------------------------------
817 sub ol_handle_checkin {
820 my $realtime = $command->{_realtime};
821 my $ws = $command->{_workstation};
822 my $barcode = $command->{barcode};
823 my $backdate = $command->{backdate} || "";
825 $logger->activity("offline: checkin : requestor=". $requestor->id.
826 ", realtime=$realtime, ". "workstation=$ws, barcode=$barcode, backdate=$backdate");
830 # $c becomes the Copy
831 # $e possibily becomes the Exception
832 my( $c, $e ) = $U->fetch_copy_by_barcode($barcode);
835 # Have to have this config option (or org setting) and a
836 # status_changed_time for skippage, and barcode not seen before
839 'circ.offline.skip_checkin_if_newer_status_changed_time'
841 || $config{skip_late}
843 && length($c->status_changed_time())
844 && ! $seen_barcode{$barcode}
846 $seen_barcode{$barcode} = 1;
847 my $cts = DateTime::Format::ISO8601->parse_datetime( clean_ISO8601($c->status_changed_time()) )->epoch();
848 my $xts = $command->{timestamp}; # Transaction Time Stamp
849 $logger->activity("offline: ol_handle_checkin: considering status_changed_time for barcode=$barcode, cts=$cts, xts=$xts");
851 # Asset has changed after this transaction, ignore
853 $skip_barcode_for_status_changed{$barcode} = 1;
856 $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'));
858 if ($skip_barcode_for_status_changed{$barcode}) {
859 $logger->activity("offline: ol_handle_checkin: barcode=$barcode has SKIP_ASSET_CHANGED");
860 return OpenILS::Event->new(
866 return $U->simplereq(
868 'open-ils.circ.checkin', $authtoken,
869 { barcode => $barcode, backdate => $backdate } );
874 # --------------------------------------------------------------------
875 # Registers a new patron
876 # --------------------------------------------------------------------
877 sub ol_handle_register {
880 my $barcode = $command->{user}->{card}->{barcode};
881 delete $command->{user}->{card};
882 delete $command->{user}->{cards} if $command->{user}->{cards};
884 $logger->info("offline: creating new user with barcode $barcode");
886 # now, create the user
887 my $actor = Fieldmapper::actor::user->new;
888 my $card = Fieldmapper::actor::card->new;
891 # username defaults to the barcode
892 $actor->usrname( ($actor->usrname) ? $actor->usrname : $barcode );
894 # Set up all of the virtual IDs, isnew, etc.
898 $actor->cards([$card]);
903 $card->barcode($barcode);
909 for my $resp (@{$command->{user}->{survey_responses}}) {
910 my $sr = Fieldmapper::action::survey_response->new;
911 $sr->$_( $resp->{$_} ) for keys %$resp;
915 $logger->debug("offline: created new survey response for survey ".$sr->survey);
917 delete $command->{user}->{survey_responses};
918 $actor->survey_responses(\@sresp) if @sresp;
921 # extract the billing address
922 if( my $addr = $command->{user}->{billing_address} ) {
923 $bid = $command->{user}->{billing_address}->{id};
924 $billing_address = Fieldmapper::actor::user_address->new;
925 $billing_address->$_($addr->{$_}) for keys %$addr;
926 $billing_address->isnew(1);
927 $billing_address->id(-1);
928 $billing_address->usr(-1);
929 delete $command->{user}->{billing_address};
930 $logger->debug("offline: read billing address ".$billing_address->street1);
934 # extract the mailing address
935 if( my $addr = $command->{user}->{mailing_address} ) {
936 $mid = $command->{user}->{mailing_address}->{id};
937 if ($webclient && $mid != $bid) {
938 $mailing_address = Fieldmapper::actor::user_address->new;
939 $mailing_address->$_($addr->{$_}) for keys %$addr;
940 $mailing_address->isnew(1);
941 $mailing_address->id(-2);
942 $mailing_address->usr(-1);
943 $logger->debug("offline: read mailing address ".$mailing_address->street1);
944 } elsif (!$webclient) {
945 $mailing_address = Fieldmapper::actor::user_address->new;
946 $mailing_address->$_($addr->{$_}) for keys %$addr;
947 $mailing_address->isnew(1);
948 $mailing_address->id(-2);
949 $mailing_address->usr(-1);
950 $logger->debug("offline: read mailing address ".$mailing_address->street1);
952 delete $command->{user}->{mailing_address};
955 # make sure we have values for both
956 $billing_address ||= $mailing_address;
957 $mailing_address ||= $billing_address;
959 $actor->billing_address($billing_address->id);
960 $actor->mailing_address($mailing_address->id);
961 $actor->addresses([$mailing_address]);
963 push( @{$actor->addresses}, $billing_address )
964 unless $billing_address->id eq $mailing_address->id;
967 for my $a ( @{$command->{user}->{addresses}} ) {
968 next if ($a->{id} == $bid || $a->{id} == $mid);
969 # extract all other addresses
970 my $addr = Fieldmapper::actor::user_address->new;
971 $addr->$_($a->{$_}) for keys %$a;
975 $logger->debug("offline: read other address ".$addr->street1);
977 push( @{$actor->addresses}, $addr );
980 # pull all of the rest of the data from the command blob
981 $actor->$_( $command->{user}->{$_} ) for grep { $_ ne 'addresses' } keys %{$command->{user}};
983 # calculate the expire date for the patron based on the profile group
984 my ($grp) = grep {$_->id == $actor->profile} @$user_groups;
986 my $seconds = OpenILS::Utils::DateTime->interval_to_seconds($grp->perm_interval);
987 my $expire_date = DateTime->from_epoch(epoch => DateTime->now->epoch + $seconds)->epoch;
988 $logger->debug("offline: setting expire date to $expire_date");
989 $actor->expire_date($U->epoch2ISO8601($expire_date));
992 $logger->debug("offline: creating user object...");
993 $actor = $U->simplereq(
995 'open-ils.actor.patron.update', $authtoken, $actor);
997 return $actor if(ref($actor) eq 'HASH'); # an event occurred
999 return OpenILS::Event->new('SUCCESS', payload => $actor);