adding
authorerickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Thu, 13 Jul 2006 22:00:10 +0000 (22:00 +0000)
committererickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Thu, 13 Jul 2006 22:00:10 +0000 (22:00 +0000)
much work required

git-svn-id: svn://svn.open-ils.org/ILS/trunk@4989 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/src/perlmods/OpenILS/SIP.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/SIP/Item.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/SIP/Patron.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/SIP/Transaction.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/SIP/Transaction/Checkin.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/SIP/Transaction/Checkout.pm [new file with mode: 0644]

diff --git a/Open-ILS/src/perlmods/OpenILS/SIP.pm b/Open-ILS/src/perlmods/OpenILS/SIP.pm
new file mode 100644 (file)
index 0000000..91beb5e
--- /dev/null
@@ -0,0 +1,555 @@
+#
+# ILS.pm: Test ILS interface module
+#
+
+package OpenILS::SIP;
+use warnings; use strict;
+
+use Sys::Syslog qw(syslog);
+
+use OpenILS::SIP::Item;
+use OpenILS::SIP::Patron;
+use OpenILS::SIP::Transaction;
+use OpenILS::SIP::Transaction::Checkout;
+use OpenILS::SIP::Transaction::Checkin;
+use OpenILS::SIP::Transaction::FeePayment;
+use OpenILS::SIP::Transaction::Hold;
+use OpenILS::SIP::Transaction::Renew;
+use OpenILS::SIP::Transaction::RenewAll;
+
+
+use OpenSRF::System;
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::SettingsClient;
+use OpenILS::Application::AppUtils;
+
+use Digest::MD5 qw(md5_hex);
+
+my $U = 'OpenILS::Application::AppUtils';
+
+# PUT ME IN THE CONFIG XXX
+my %supports = (
+               'magnetic media' => 1,
+               'security inhibit' => 0,
+               'offline operation' => 0
+               );
+
+
+sub new {
+       my ($class, $institution, $login) = @_;
+       my $type = ref($class) || $class;
+       my $self = {};
+
+       syslog("LOG_DEBUG", "new ILS '%s'", $institution->{id});
+       $self->{institution} = $institution;
+
+       my $config = $institution->{implementation_config}->{bootstrap};
+
+       syslog('LOG_DEBUG', "loading bootstrap config: $config");
+       
+       local $/ = "\n";
+       OpenSRF::System->bootstrap_client(config_file => $config);
+       syslog('LOG_DEBUG', "bootstrap loaded..");
+
+       $self->{osrf_config} = OpenSRF::Utils::SettingsClient->new;
+
+       Fieldmapper->import($self->{osrf_config}->config_value('IDL'));
+
+       bless( $self, $type );
+
+       return undef unless 
+               $self->login( $login->{id}, $login->{password} );
+
+       return $self;
+}
+
+sub login {
+       my( $self, $username, $password ) = @_;
+       syslog('LOG_DEBUG', "OpenILS: Logging in with username $username");
+
+       my $seed = $U->simplereq( 
+               'open-ils.auth',
+               'open-ils.auth.authenticate.init', $username );
+
+       my $response = $U->simplereq(
+               'open-ils.auth', 
+               'open-ils.auth.authenticate.complete', 
+               {       
+                       username => $username, 
+                       password => md5_hex($seed . md5_hex($password)), 
+                       type            => 'opac',
+               }
+       );
+
+       my $key;
+       if( ref($response) eq 'HASH' and $response->{payload} 
+               and $key = $response->{payload}->{authtoken} ) {
+               syslog('LOG_INFO', "OpenILS: Login succeeded for $username : authkey = $key");
+
+       } else {
+               syslog('LOG_WARN', "OpenILS: Login failed for $username");
+       }
+
+       return $self->{authtoken} = $key;
+}
+
+
+sub find_patron {
+       my $self = shift;
+       return OpenILS::SIP::Patron->new(@_);
+}
+
+
+sub find_item {
+       my $self = shift;
+       return OpenILS::SIP::Item->new(@_);
+}
+
+
+sub institution {
+    my $self = shift;
+
+    return $self->{institution}->{id};
+}
+
+
+# XXX Get me from the config?
+sub supports {
+    my ($self, $op) = @_;
+    return exists($supports{$op}) ? $supports{$op} : 0;
+}
+
+sub check_inst_id {
+    my ($self, $id, $whence) = @_;
+
+    if ($id ne $self->{institution}->{id}) {
+       syslog("LOG_WARNING", "%s: received institution '%s', expected '%s'",
+              $whence, $id, $self->{institution}->{id});
+    }
+}
+
+
+
+
+# XXX by default, these should come from the config
+sub checkout_ok {
+    return 1;
+}
+
+sub checkin_ok {
+    return 0;
+}
+
+sub status_update_ok {
+    return 1;
+}
+
+sub offline_ok {
+    return 0;
+}
+
+
+##
+## Checkout(patron_id, item_id, sc_renew):
+##    patron_id & item_id are the identifiers send by the terminal
+##    sc_renew is the renewal policy configured on the terminal
+## returns a status opject that can be queried for the various bits
+## of information that the protocol (SIP or NCIP) needs to generate
+## the response.
+##
+
+sub checkout {
+       my ($self, $patron_id, $item_id, $sc_renew) = @_;
+       
+       syslog('LOG_DEBUG', "OpenILS::Checkout attempt: patron=$patron_id, item=$item_id");
+       
+       my $xact                = OpenILS::SIP::Transaction::Checkout->new( authtoken => $self->{authtoken} );
+       my $patron      = OpenILS::SIP::Patron->new($patron_id);
+       my $item                = OpenILS::SIP::Item->new($item_id);
+       
+       $xact->patron($patron);
+       $xact->item($item);
+
+       if (!$patron) {
+               $xact->screen_msg("Invalid Patron");
+               return $xact;
+       }
+
+       if (!$patron->charge_ok) {
+               $xact->screen_msg("Patron Blocked");
+               return $xact;
+       }
+
+       if( !$item ) {
+               $xact->screen_msg("Invalid Item");
+               return $xact;
+       }
+
+       syslog('LOG_DEBUG', "OpenILS::Checkout data loaded OK, checking out...");
+       $xact->do_checkout();
+
+       if ($item->{patron} && ($item->{patron} ne $patron_id)) {
+               # I can't deal with this right now
+               # XXX check in then check out?
+               $xact->screen_msg("Item checked out to another patron");
+               $xact->ok(0);
+       } 
+
+       $xact->desensitize(!$item->magnetic);
+
+       if( $xact->ok ) {
+
+               $xact->editor->commit;
+               syslog("LOG_DEBUG", "OpenILS::Checkout: " .
+                       "patron %s checke out %s succeeded", $patron_id, $item_id);
+
+       } else {
+
+               $xact->editor->xact_rollback;
+               syslog("LOG_DEBUG", "OpenILS::Checkout: " .
+                       "patron %s checke out %s FAILED, rolling back xact...", $patron_id, $item_id);
+       }
+
+       return $xact;
+}
+
+
+sub checkin {
+       my ($self, $item_id, $trans_date, $return_date,
+       $current_loc, $item_props, $cancel) = @_;
+
+       syslog('LOG_DEBUG', "OpenILS::Checkin on item=$item_id");
+       
+       my $patron;
+       my $xact                = OpenILS::SIP::Transaction::Checkin->new(authtoken => $self->{authtoken});
+       my $item                = OpenILS::SIP::Item->new($item_id);
+
+       $xact->item($item);
+
+       $xact->do_checkin( $trans_date, $return_date, $current_loc, $item_props );
+       
+       if ($xact->ok) {
+
+               $xact->patron($patron = OpenILS::SIP::Patron->new($item->{patron}));
+               delete $item->{patron};
+               delete $item->{due_date};
+               syslog('LOG_INFO', "OpenILS: Checkin succeeded");
+               $xact->editor->commit;
+
+       } else {
+
+               $xact->editor->xact_rollback;
+               syslog('LOG_WARNING', "OpenILS: Checkin failed");
+       }
+       # END TRANSACTION
+
+       return $xact;
+}
+
+
+
+
+## If the ILS caches patron information, this lets it free
+## it up
+#sub end_patron_session {
+#    my ($self, $patron_id) = @_;
+#
+#    # success?, screen_msg, print_line
+#    return (1, 'Thank you for using OpenILS!', '');
+#}
+#
+#sub pay_fee {
+#    my ($self, $patron_id, $patron_pwd, $fee_amt, $fee_type,
+#      $pay_type, $fee_id, $trans_id, $currency) = @_;
+#    my $trans;
+#    my $patron;
+#
+#    $trans = new ILS::Transaction::FeePayment;
+#
+#    $patron = new ILS::Patron $patron_id;
+#
+#    $trans->transaction_id($trans_id);
+#    $trans->patron($patron);
+#    $trans->ok(1);
+#
+#    return $trans;
+#}
+#
+#sub add_hold {
+#    my ($self, $patron_id, $patron_pwd, $item_id, $title_id,
+#      $expiry_date, $pickup_location, $hold_type, $fee_ack) = @_;
+#    my ($patron, $item);
+#    my $hold;
+#    my $trans;
+#
+#
+#    $trans = new ILS::Transaction::Hold;
+#
+#    # BEGIN TRANSACTION
+#    $patron = new ILS::Patron $patron_id;
+#    if (!$patron
+#      || (defined($patron_pwd) && !$patron->check_password($patron_pwd))) {
+#      $trans->screen_msg("Invalid Patron.");
+#
+#      return $trans;
+#    }
+#
+#    $item = new ILS::Item ($item_id || $title_id);
+#    if (!$item) {
+#      $trans->screen_msg("No such item.");
+#
+#      # END TRANSACTION (conditionally)
+#      return $trans;
+#    } elsif ($item->fee && ($fee_ack ne 'Y')) {
+#      $trans->screen_msg = "Fee required to place hold.";
+#
+#      # END TRANSACTION (conditionally)
+#      return $trans;
+#    }
+#
+#    $hold = {
+#      item_id         => $item->id,
+#      patron_id       => $patron->id,
+#      expiration_date => $expiry_date,
+#      pickup_location => $pickup_location,
+#      hold_type       => $hold_type,
+#    };
+#
+#    $trans->ok(1);
+#    $trans->patron($patron);
+#    $trans->item($item);
+#    $trans->pickup_location($pickup_location);
+#
+#    push(@{$item->hold_queue}, $hold);
+#    push(@{$patron->{hold_items}}, $hold);
+#
+#
+#    # END TRANSACTION
+#    return $trans;
+#}
+#
+#sub cancel_hold {
+#    my ($self, $patron_id, $patron_pwd, $item_id, $title_id) = @_;
+#    my ($patron, $item, $hold);
+#    my $trans;
+#
+#    $trans = new ILS::Transaction::Hold;
+#
+#    # BEGIN TRANSACTION
+#    $patron = new ILS::Patron $patron_id;
+#    if (!$patron) {
+#      $trans->screen_msg("Invalid patron barcode.");
+#
+#      return $trans;
+#    } elsif (defined($patron_pwd) && !$patron->check_password($patron_pwd)) {
+#      $trans->screen_msg('Invalid patron password.');
+#
+#      return $trans;
+#    }
+#
+#    $item = new ILS::Item ($item_id || $title_id);
+#    if (!$item) {
+#      $trans->screen_msg("No such item.");
+#
+#      # END TRANSACTION (conditionally)
+#      return $trans;
+#    }
+#
+#    # Remove the hold from the patron's record first
+#    $trans->ok($patron->drop_hold($item_id));
+#
+#    if (!$trans->ok) {
+#      # We didn't find it on the patron record
+#      $trans->screen_msg("No such hold on patron record.");
+#
+#      # END TRANSACTION (conditionally)
+#      return $trans;
+#    }
+#
+#    # Now, remove it from the item record.  If it was on the patron
+#    # record but not on the item record, we'll treat that as success.
+#    foreach my $i (0 .. scalar @{$item->hold_queue}) {
+#      $hold = $item->hold_queue->[$i];
+#
+#      if ($hold->{patron_id} eq $patron->id) {
+#          # found it: delete it.
+#          splice @{$item->hold_queue}, $i, 1;
+#          last;
+#      }
+#    }
+#
+#    $trans->screen_msg("Hold Cancelled.");
+#    $trans->patron($patron);
+#    $trans->item($item);
+#
+#    return $trans;
+#}
+#
+#
+## The patron and item id's can't be altered, but the
+## date, location, and type can.
+#sub alter_hold {
+#    my ($self, $patron_id, $patron_pwd, $item_id, $title_id,
+#      $expiry_date, $pickup_location, $hold_type, $fee_ack) = @_;
+#    my ($patron, $item);
+#    my $hold;
+#    my $trans;
+#
+#    $trans = new ILS::Transaction::Hold;
+#
+#    # BEGIN TRANSACTION
+#    $patron = new ILS::Patron $patron_id;
+#    if (!$patron) {
+#      $trans->screen_msg("Invalid patron barcode.");
+#
+#      return $trans;
+#    }
+#
+#    foreach my $i (0 .. scalar @{$patron->{hold_items}}) {
+#      $hold = $patron->{hold_items}[$i];
+#
+#      if ($hold->{item_id} eq $item_id) {
+#          # Found it.  So fix it.
+#          $hold->{expiration_date} = $expiry_date if $expiry_date;
+#          $hold->{pickup_location} = $pickup_location if $pickup_location;
+#          $hold->{hold_type} = $hold_type if $hold_type;
+#
+#          $trans->ok(1);
+#          $trans->screen_msg("Hold updated.");
+#          $trans->patron($patron);
+#          $trans->item(new ILS::Item $hold->{item_id});
+#          last;
+#      }
+#    }
+#
+#    # The same hold structure is linked into both the patron's
+#    # list of hold items and into the queue of outstanding holds
+#    # for the item, so we don't need to search the hold queue for
+#    # the item, since it's already been updated by the patron code.
+#
+#    if (!$trans->ok) {
+#      $trans->screen_msg("No such outstanding hold.");
+#    }
+#
+#    return $trans;
+#}
+#
+#sub renew {
+#    my ($self, $patron_id, $patron_pwd, $item_id, $title_id,
+#      $no_block, $nb_due_date, $third_party,
+#      $item_props, $fee_ack) = @_;
+#    my ($patron, $item);
+#    my $trans;
+#
+#    $trans = new ILS::Transaction::Renew;
+#
+#    $trans->patron($patron = new ILS::Patron $patron_id);
+#    if (!$patron) {
+#      $trans->screen_msg("Invalid patron barcode.");
+#
+#      return $trans;
+#    } elsif (!$patron->renew_ok) {
+#
+#      $trans->screen_msg("Renewals not allowed.");
+#
+#      return $trans;
+#    }
+#
+#    if (defined($title_id)) {
+#      # renewing a title, rather than an item (sort of)
+#      # This is gross, but in a real ILS it would be better
+#      foreach my $i (@{$patron->{items}}) {
+#          $item = new ILS::Item $i;
+#          last if ($title_id eq $item->title_id);
+#          $item = undef;
+#      }
+#    } else {
+#      foreach my $i (@{$patron->{items}}) {
+#          if ($i == $item_id) {
+#              # We have it checked out
+#              $item = new ILS::Item $item_id;
+#              last;
+#          }
+#      }
+#    }
+#
+#    if (!defined($item)) {
+#      # It's not checked out to $patron_id
+#      $trans->screen_msg("Item not checked out to " . $patron->name);
+#    } elsif (!$item->available($patron_id)) {
+#       $trans->screen_msg("Item has outstanding holds");
+#    } else {
+#      $trans->item($item);
+#      $trans->renewal_ok(1);
+#
+#      $trans->desensitize(0); # It's already checked out
+#
+#      if ($no_block eq 'Y') {
+#          $item->{due_date} = $nb_due_date;
+#      } else {
+#          $item->{due_date} = time + (14*24*60*60); # two weeks
+#      }
+#      if ($item_props) {
+#          $item->{sip_item_properties} = $item_props;
+#      }
+#      $trans->ok(1);
+#      $trans->renewal_ok(1);
+#
+#      return $trans;
+#    }
+#
+#    return $trans;
+#}
+#
+#sub renew_all {
+#    my ($self, $patron_id, $patron_pwd, $fee_ack) = @_;
+#    my ($patron, $item_id);
+#    my $trans;
+#
+#    $trans = new ILS::Transaction::RenewAll;
+#
+#    $trans->patron($patron = new ILS::Patron $patron_id);
+#    if (defined $patron) {
+#      syslog("LOG_DEBUG", "ILS::renew_all: patron '%s': renew_ok: %s",
+#             $patron->name, $patron->renew_ok);
+#    } else {
+#      syslog("LOG_DEBUG", "ILS::renew_all: Invalid patron id: '%s'",
+#             $patron_id);
+#    }
+#
+#    if (!defined($patron)) {
+#      $trans->screen_msg("Invalid patron barcode.");
+#      return $trans;
+#    } elsif (!$patron->renew_ok) {
+#      $trans->screen_msg("Renewals not allowed.");
+#      return $trans;
+#    } elsif (defined($patron_pwd) && !$patron->check_password($patron_pwd)) {
+#      $trans->screen_msg("Invalid patron password.");
+#      return $trans;
+#    }
+#
+#    foreach $item_id (@{$patron->{items}}) {
+#      my $item = new ILS::Item $item_id;
+#
+#      if (!defined($item)) {
+#          syslog("LOG_WARNING",
+#                 "renew_all: Invalid item id associated with patron '%s'",
+#                 $patron->id);
+#          next;
+#      }
+#
+#      if (@{$item->hold_queue}) {
+#          # Can't renew if there are outstanding holds
+#          push @{$trans->unrenewed}, $item_id;
+#      } else {
+#          $item->{due_date} = time + (14*24*60*60); # two weeks hence
+#          push @{$trans->renewed}, $item_id;
+#      }
+#    }
+#
+#    $trans->ok(1);
+#
+#    return $trans;
+#}
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/SIP/Item.pm b/Open-ILS/src/perlmods/OpenILS/SIP/Item.pm
new file mode 100644 (file)
index 0000000..f7e02be
--- /dev/null
@@ -0,0 +1,215 @@
+#
+#
+# A Class for hiding the ILS's concept of the item from the OpenSIP
+# system
+#
+
+package OpenILS::SIP::Item;
+
+use strict;
+use warnings;
+
+use Sys::Syslog qw(syslog);
+
+use OpenILS::SIP::Transaction;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+my %item_db;
+
+sub new {
+    my ($class, $item_id) = @_;
+    my $type = ref($class) || $class;
+    my $self = {};
+    bless $self, $type;
+
+       require OpenILS::Utils::CStoreEditor;
+       my $e = OpenILS::Utils::CStoreEditor->new;
+
+       if(!UNIVERSAL::can($e, 'search_actor_card')) {
+               syslog("LOG_WARNING", "Reloading CStoreEditor...");
+               delete $INC{'OpenILS/Utils/CStoreEditor.pm'};
+               require OpenILS::Utils::CStoreEditor;
+               $e = OpenILS::Utils::CStoreEditor->new;
+       }
+
+
+        # FLESH ME
+        my $copy = $e->search_asset_copy(
+               [
+                       { barcode => $item_id },
+                       {
+                               flesh => 3,
+                               flesh_fields => {
+                                       acp => [ 'circ_lib', 'call_number' ],
+                                       acn => [ 'owning_lib', 'record' ],
+                               }
+                       }
+               ]
+       );
+
+       if(!@$copy) {
+               syslog("LOG_DEBUG", "OpenILS: Item '%s' : not found", $item_id);
+               return undef;
+    }
+
+       $copy = $$copy[0];
+
+        # XXX See if i am checked out, if so set $self->{patron} to the user's barcode
+       my ($circ) = $U->fetch_open_circulation($copy->id);
+       if($circ) {
+               my $user = $e->retrieve_actor_user(
+                       [
+                               $circ->usr,
+                               {
+                                       flesh => 1,
+                                       flesh_fields => {
+                                               "au" => [ 'card' ],
+                                       }
+                               }
+                       ]
+               );
+
+               $self->{patron} = $user->card->barcode if $user;
+               $self->{patron_object} = $user;
+       }
+
+       $self->{id}                     = $item_id;
+       $self->{copy}           = $copy;
+       $self->{volume} = $copy->call_number;
+       $self->{record} = $copy->call_number->record;
+       
+       $self->{mods}   = $U->record_to_mvr($self->{record}) if $self->{record}->marc;
+
+    syslog("LOG_DEBUG", "new OpenILS Item('%s'): found with title '%s'",
+          $item_id, $self->title_id);
+
+    return $self;
+}
+
+sub magnetic {
+    my $self = shift;
+        return 0;
+}
+
+sub sip_media_type {
+    my $self = shift;
+        return '001';
+}
+
+sub sip_item_properties {
+    my $self = shift;
+        return "";
+}
+
+sub status_update {
+    my ($self, $props) = @_;
+    my $status = new OpenILS::SIP::Transaction;
+    $self->{sip_item_properties} = $props;
+    $status->{ok} = 1;
+    return $status;
+}
+
+
+sub id {
+    my $self = shift;
+    return $self->{id};
+}
+
+sub title_id {
+    my $self = shift;
+    return ($self->{mods}) ? $self->{mods}->title : $self->{copy}->dummy_title;
+}
+
+sub permanent_location {
+    my $self = shift;
+        return $self->{volume}->owning_lib->name;
+}
+
+sub current_location {
+    my $self = shift;
+        return $self->{copy}->circ_lib->name;
+}
+
+
+# 2 chars 0-99 
+sub sip_circulation_status {
+    my $self = shift;
+        return '01';
+}
+
+sub sip_security_marker {
+    return '02';
+}
+
+sub sip_fee_type {
+    return '01';
+}
+
+sub fee {
+    my $self = shift;
+        return 0;
+}
+
+
+sub fee_currency {
+    my $self = shift;
+    'CAD';
+}
+
+sub owner {
+    my $self = shift;
+        return $self->{volume}->owning_lib->name;
+}
+
+sub hold_queue {
+    my $self = shift;
+        return [];
+}
+
+sub hold_queue_position {
+    my ($self, $patron_id) = @_;
+        return 1;
+}
+
+sub due_date {
+    my $self = shift;
+        return 0;
+}
+
+sub recall_date {
+    my $self = shift;
+    return 0;
+}
+
+sub hold_pickup_date {
+    my $self = shift;
+        return 0;
+}
+
+# message to display on console
+sub screen_msg {
+    my $self = shift;
+    return $self->{screen_msg} || '';
+}
+
+
+# reciept printer
+sub print_line {
+     my $self = shift;
+     return $self->{print_line} || '';
+}
+
+
+# An item is available for a patron if
+# 1) It's not checked out and (there's no hold queue OR patron
+#    is at the front of the queue)
+# OR
+# 2) It's checked out to the patron and there's no hold queue
+sub available {
+     my ($self, $for_patron) = @_;
+         return 1;
+}
+
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/SIP/Patron.pm b/Open-ILS/src/perlmods/OpenILS/SIP/Patron.pm
new file mode 100644 (file)
index 0000000..6487830
--- /dev/null
@@ -0,0 +1,397 @@
+#
+# 
+# A Class for hiding the ILS's concept of the patron from the OpenSIP
+# system
+#
+
+package OpenILS::SIP::Patron;
+
+use strict;
+use warnings;
+use Exporter;
+
+use Sys::Syslog qw(syslog);
+use Data::Dumper;
+use Digest::MD5 qw(md5_hex);
+
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+our (@ISA, @EXPORT_OK);
+
+@ISA = qw(Exporter);
+
+@EXPORT_OK = qw(invalid_patron);
+
+sub new {
+    my ($class, $patron_id) = @_;
+    my $type = ref($class) || $class;
+    my $self = {};
+
+       syslog("LOG_DEBUG", "new OpenILS Patron(%s): searching...", $patron_id);
+
+       require OpenILS::Utils::CStoreEditor;
+       my $e = OpenILS::Utils::CStoreEditor->new;
+
+       if(!UNIVERSAL::can($e, 'search_actor_card')) {
+               syslog("LOG_WARNING", "Reloading CStoreEditor...");
+               delete $INC{'OpenILS/Utils/CStoreEditor.pm'};
+               require OpenILS::Utils::CStoreEditor;
+               $e = OpenILS::Utils::CStoreEditor->new;
+       }
+
+
+        my $c = $e->search_actor_card({barcode => $patron_id}, {idlist=>1});
+        my $user;
+
+        if( @$c ) {
+
+               $user = $e->search_actor_user(
+                       [
+                               { card => $$c[0] },
+                               {
+                                       flesh => 1,
+                                       flesh_fields => {
+                                               "au" => [
+                                                       #"cards",
+                                                       "card",
+                                                       "standing_penalties",
+                                                       "addresses",
+                                                       "billing_address",
+                                                       "mailing_address",
+                                                       #"stat_cat_entries",
+                                                       'profile',
+                                               ]
+                                       }
+                               }
+                       ]
+               );
+
+               $user = (@$user) ? $$user[0] : undef;
+        }
+
+        if(!$user) {
+               syslog("LOG_WARNING", "Unable to find patron %s", $patron_id);
+               return undef;
+        }
+
+       $self->{user}           = $user;
+       $self->{id}                     = $patron_id;
+       $self->{editor} = $e;
+
+       syslog("LOG_DEBUG", "new OpenILS Patron(%s): found patron '%s'", $patron_id);
+
+       bless $self, $type;
+       return $self;
+}
+
+sub id {
+    my $self = shift;
+    return $self->{id};
+}
+
+sub name {
+    my $self = shift;
+        my $u = $self->{user};
+        return $u->first_given_name . ' ' . 
+               $u->second_given_name . ' ' . $u->family_name;
+}
+
+sub __addr_string {
+       my $addr = shift;
+       return "" unless $addr;
+       return $addr->street1 .' '. 
+               $addr->street2 .' '.
+               $addr->city .' '.
+               $addr->county .' '.
+               $addr->state .' '.
+               $addr->country .' '.
+               $addr->post_code;
+}
+
+sub address {
+       my $self = shift;
+       my $u = $self->{user};
+       my $addr = $u->billing_address;
+       my $str = __addr_string($addr);
+       my $maddr = $u->mailing_address;
+       $str .= "\n" . __addr_string($maddr) 
+               if $maddr and $maddr->id ne $addr->id;
+       return $str;
+}
+
+sub email_addr {
+    my $self = shift;
+       return $self->{user}->email;
+}
+
+sub home_phone {
+    my $self = shift;
+       return $self->{user}->day_phone;
+}
+
+sub sip_birthdate {
+    my $self = shift;
+       return $self->{user}->dob;
+}
+
+sub ptype {
+    my $self = shift;
+       return $self->{user}->profile->name;
+}
+
+sub language {
+    my $self = shift;
+    return '000'; # Unspecified
+}
+
+# How much more detail do we need to check here?
+sub charge_ok {
+    my $self = shift;
+        my $u = $self->{user};
+        return ($u->barred ne 't') and ($u->card->active ne 'f');
+}
+
+# How much more detail do we need to check here?
+sub renew_ok {
+    my $self = shift;
+        my $u = $self->{user};
+        return ($u->barred ne 'f') and ($u->card->active ne 'f');
+}
+
+sub recall_ok {
+    my $self = shift;
+    return 0;
+}
+
+sub hold_ok {
+    my $self = shift;
+    return 0;
+}
+
+# return true if the card provided is marked as lost
+sub card_lost {
+    my $self = shift;
+    return 0;
+}
+
+sub recall_overdue {
+    my $self = shift;
+    return 0;
+}
+
+
+sub check_password {
+       my ($self, $pwd) = @_;
+       return md5_hex($pwd) eq $self->{user}->passwd;
+}
+
+
+sub currency {
+       my $self = shift;
+       return 'usd';
+}
+
+
+sub fee_amount {
+       my $self = shift;
+       return 0;
+}
+
+sub screen_msg {
+    my $self = shift;
+       return '';
+}
+
+sub print_line {
+    my $self = shift;
+       return '';
+}
+
+sub too_many_charged {
+    my $self = shift;
+       return 0;
+}
+
+sub too_many_overdue {
+       my $self = shift;
+       if( $self->{user}->standing_penalties ) {
+               return grep { $_->penalty_type eq 'PATRON_EXCEEDS_OVERDUE_COUNT' } 
+                       @{$self->{user}->standing_penalties};
+       }
+       return 0;
+}
+
+# not completely sure what this means
+sub too_many_renewal {
+    my $self = shift;
+       return 0;
+}
+
+# not relevant, handled by fines/fees
+sub too_many_claim_return {
+    my $self = shift;
+       return 0;
+}
+
+# not relevant, handled by fines/fees
+sub too_many_lost {
+    my $self = shift;
+       return 0;
+}
+
+sub excessive_fines {
+    my $self = shift;
+       if( $self->{user}->standing_penalties ) {
+               return grep { $_->penalty_type eq 'PATRON_EXCEEDS_FINES' } 
+                       @{$self->{user}->standing_penalties};
+       }
+       return 0;
+}
+
+
+# Until someone suggests otherwise, fees and fines are the same
+
+sub excessive_fees {
+       my $self = shift;
+       if( $self->{user}->standing_penalties ) {
+               return grep { $_->penalty_type eq 'PATRON_EXCEEDS_FINES' } 
+                       @{$self->{user}->standing_penalties};
+       }
+       return 0;
+}
+
+# not relevant, handled by fines/fees
+sub too_many_billed {
+    my $self = shift;
+       return 0;
+}
+
+
+
+#
+# List of outstanding holds placed
+#
+sub hold_items {
+    my ($self, $start, $end) = @_;
+
+        my $holds = $self->{editor}->search_action_hold_request(
+               { usr => $self->{user}->id, fulfillment_time => undef }
+        );
+
+       my @holds;
+       push( @holds, $self->__hold_to_title($_) ) for @$holds;
+
+       return (defined $start and defined $end) ? 
+               [ $holds[($start-1)..($end-1)] ] : 
+               \@holds;
+}
+
+sub __hold_to_title {
+       my $self = shift;
+       my $hold = shift;
+       my $e = $self->{editor};
+
+       my( $id, $mods, $title, $volume, $copy );
+
+       if( $hold->hold_type eq 'C' ) {
+               $copy = $e->retrieve_asset_copy($hold->target);
+       }
+
+       if( $copy || $hold->hold_type eq 'V' ) {
+               return $copy->dummy_title if $copy and $copy->call_number == -1;
+               $id = ($copy) ? $copy->call_number : $hold->target;
+               $volume = $e->retrieve_asset_call_number($id);
+       }
+
+       if( $volume || $hold->hold_type eq 'T' ) {
+               $id = ($volume) ? $volume->record : $hold->target;
+               $mods = $U->simplereq(
+                       'open-ils.search',
+                       'open-ils.search.biblio.record.mods_slim.retrieve', $id );
+       }
+
+       if( $hold->hold_type eq 'M' ) {
+               $mods = $U->simplereq(
+                       'open-ils.search',
+                       'open-ils.search.biblio.metarecord.mods_slim.retrieve', $hold->target);
+       }
+
+
+       return ($mods) ? $mods->title : "";
+}
+
+#
+# remove the hold on item item_id from my hold queue.
+# return true if I was holding the item, false otherwise.
+# 
+sub drop_hold {
+    my ($self, $item_id) = @_;
+    return 0;
+}
+
+sub overdue_items {
+    my ($self, $start, $end) = @_;
+        my @overdues;
+
+       return (defined $start and defined $end) ? 
+               [ $overdues[($start-1)..($end-1)] ] : 
+               \@overdues;
+}
+
+sub charged_items {
+       my ($self, $start, $end) = shift;
+       my @charges;
+
+       return (defined $start and defined $end) ? 
+               [ $charges[($start-1)..($end-1)] ] : 
+               \@charges;
+}
+
+sub fine_items {
+       my ($self, $start, $end) = @_;
+       my @fines;
+       return (defined $start and defined $end) ? 
+               [ $fines[($start-1)..($end-1)] ] : \@fines;
+}
+
+# not currently supported
+sub recall_items {
+    my ($self, $start, $end) = @_;
+        return [];
+}
+
+sub unavail_holds {
+       my ($self, $start, $end) = @_;
+       my @holds;
+       return (defined $start and defined $end) ? 
+               [ $holds[($start-1)..($end-1)] ] : \@holds;
+}
+
+sub block {
+    my ($self, $card_retained, $blocked_card_msg) = @_;
+        # Mark the card as inactive, set patron alert
+    return $self;
+}
+
+# Testing purposes only
+sub enable {
+    my $self = shift;
+        # Un-mark card as inactive, grep out the patron alert
+    $self->{screen_msg} = "All privileges restored.";
+    return $self;
+}
+
+#
+# Messages
+#
+
+sub invalid_patron {
+    return "Please contact library staff";
+}
+
+sub charge_denied {
+    return "Please contact library staff";
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/SIP/Transaction.pm b/Open-ILS/src/perlmods/OpenILS/SIP/Transaction.pm
new file mode 100644 (file)
index 0000000..6aabd43
--- /dev/null
@@ -0,0 +1,126 @@
+#
+# Transaction: Superclass of all the transactional status objects
+#
+
+package OpenILS::SIP::Transaction;
+
+use Carp;
+use strict; use warnings;
+use Sys::Syslog qw(syslog);
+
+
+my %fields = (
+             ok            => 0,
+             patron        => undef,
+             item          => undef,
+             desensitize   => 0,
+             alert         => '',
+             transation_id => undef,
+             sip_fee_type  => '01', # Other/Unknown
+             fee_amount    => undef,
+             sip_currency  => 'CAD',
+             screen_msg    => '',
+             print_line    => '',
+                       editor                  => undef,
+                       authtoken               => '',
+             );
+
+our $AUTOLOAD;
+
+# returns the global transaction pointer
+#sub get_xact {
+#      my $class = shift;
+#      return $XACT;
+#}
+#
+#sub session {
+#      my( $self, $session ) = @_;
+#      $self->{session} = $session if $session;
+#      return $self->{session};
+#}
+#
+#
+#sub create_session {
+#      my( $self, $patron ) = @_;
+#      $self->commit_session if $self->session_is_alive;
+#      require OpenILS::Utils::CStoreEditor;
+#      return $self->{session} = {
+#              editor => OpenILS::Utils::CStoreEditor->new(xact=>1),
+#              patron => $patron
+#      }
+#}
+#
+#sub commit_session {
+#      my $self = shift;
+#      if( my $session = $self->session ) {
+#              $session->{editor}->commit;
+#              delete $$session{editor};
+#              delete $$session{patron};
+#      }
+#}
+#
+#
+#sub rollback_session {
+#      my $self = shift;
+#      if( my $session = $self->session ) {
+#              $session->{editor}->xact_rollback;
+#              delete $$session{editor};
+#              delete $$session{patron};
+#      }
+#}
+#
+#sub session_is_alive {
+#      my $self = shift;
+#      return $self->session and $self->session->{editor};
+#}
+
+
+
+sub new {
+    my( $class, %args ) = @_;
+
+       use Data::Dumper;
+       warn 'ARGS = ' .  Dumper(\@_);
+
+       warn "AUTH = " . $args{authtoken} . "\n";
+
+    my $self = {
+               _permitted => \%fields,
+               %fields,
+    };
+
+       bless $self, $class;
+       $self->authtoken($args{authtoken});
+
+       syslog('LOG_DEBUG', "OpenILS: Created new transaction with authtoken %s", $self->authtoken);
+
+       require OpenILS::Utils::CStoreEditor;
+       $self->editor(OpenILS::Utils::CStoreEditor->new(
+               xact=>1, authtoken => $self->authtoken));
+
+       return $self;
+}
+
+sub DESTROY {
+    # be cool
+}
+
+sub AUTOLOAD {
+    my $self = shift;
+    my $class = ref($self) or croak "$self is not an object";
+    my $name = $AUTOLOAD;
+
+    $name =~ s/.*://;
+
+    unless (exists $self->{_permitted}->{$name}) {
+       croak "Can't access '$name' field of class '$class'";
+    }
+
+    if (@_) {
+       return $self->{$name} = shift;
+    } else {
+       return $self->{$name};
+    }
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/SIP/Transaction/Checkin.pm b/Open-ILS/src/perlmods/OpenILS/SIP/Transaction/Checkin.pm
new file mode 100644 (file)
index 0000000..e349879
--- /dev/null
@@ -0,0 +1,72 @@
+#
+# An object to handle checkin status
+#
+
+package OpenILS::SIP::Transaction::Checkin;
+
+use warnings;
+use strict;
+
+use POSIX qw(strftime);
+
+use OpenILS::SIP;
+use OpenILS::SIP::Transaction;
+use Data::Dumper;
+
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+our @ISA = qw(OpenILS::SIP::Transaction);
+
+my %fields = (
+             magnetic => 0,
+             sort_bin => undef,
+             );
+
+sub new {
+    my $class = shift;;
+    my $self = $class->SUPER::new(@_);
+    my $element;
+
+       foreach $element (keys %fields) {
+               $self->{_permitted}->{$element} = $fields{$element};
+       }
+
+    @{$self}{keys %fields} = values %fields;
+
+    return bless $self, $class;
+}
+
+sub resensitize {
+    my $self = shift;
+    return !$self->{item}->magnetic;
+}
+
+
+sub do_checkin {
+       my $self = shift;
+
+       my $resp = $U->simplereq( 
+               'open-ils.circ', 
+               'open-ils.circ.checkin', 
+               $self->{authtoken}, { barcode => $self->{item}->id } );
+
+
+       my $circ = $resp->{payload}->{circ};
+
+       if(!$circ) {
+               warn 'CHECKIN: ' . Dumper($resp) . "\n";
+               $self->ok(0);
+               return 0;
+       }
+
+       $self->{item}->{patron} = $self->editor->search_actor_card(
+               { usr => $circ->usr, active => 't' } )->[0]->barcode;
+
+       $self->ok(1);
+
+       return 1;
+}
+
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/SIP/Transaction/Checkout.pm b/Open-ILS/src/perlmods/OpenILS/SIP/Transaction/Checkout.pm
new file mode 100644 (file)
index 0000000..0cefd22
--- /dev/null
@@ -0,0 +1,111 @@
+#
+# An object to handle checkout status
+#
+
+package OpenILS::SIP::Transaction::Checkout;
+
+use warnings;
+use strict;
+
+use POSIX qw(strftime);
+
+use OpenILS::SIP;
+use OpenILS::SIP::Transaction;
+use Sys::Syslog qw(syslog);
+
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+
+our @ISA = qw(OpenILS::SIP::Transaction);
+
+# Most fields are handled by the Transaction superclass
+my %fields = (
+             security_inhibit => 0,
+             due              => undef,
+             renew_ok         => 0,
+             );
+
+sub new {
+        my $class = shift;
+
+        use Data::Dumper;
+        warn 'ARGS = ' .  Dumper(\@_);
+
+    my $self = $class->SUPER::new(@_);
+
+    my $element;
+
+       foreach $element (keys %fields) {
+               $self->{_permitted}->{$element} = $fields{$element};
+       }
+
+    @{$self}{keys %fields} = values %fields;
+        
+    return bless $self, $class;
+}
+
+
+# if this item is already checked out to the requested patron,
+# renew the item and set $self->renew_ok to true.  
+# XXX if it's a renewal and the renewal is not permitted, set 
+# $self->screen_msg("Item on Hold for Another User"); (or somesuch)
+# XXX Set $self->ok(0) on any errors
+sub do_checkout {
+       my $self = shift;
+       syslog('LOG_DEBUG', "OpenILS: performing checkout...");
+
+       my $args = { 
+               barcode => $self->{item}->id, 
+               patron_barcode => $self->{patron}->id
+       };
+
+       my $resp = $U->simplereq(
+               'open-ils.circ',
+               'open-ils.circ.checkout.permit', 
+               $self->{authtoken}, $args );
+
+       my $key;
+
+       if( ref($resp) eq 'HASH' and $key = $resp->{payload} ) {
+               syslog('LOG_INFO', "OpenILS: circ permit key => $key");
+
+       } else {
+               syslog('LOG_INFO', "OpenILS: Circ permit failed :\n" . Dumper($resp) );
+               $self->ok(0);
+               return 0;
+       }
+
+       $args = { 
+               permit_key              => $key, 
+               patron_barcode => $self->{patron}->id, 
+               barcode                 => $self->{item}->id
+       };
+
+       $resp = $U->simplereq(
+               'open-ils.circ',
+               'open-ils.circ.checkout', $self->{authtoken}, $args );
+
+       # XXX Check for events
+       if( $resp ) {
+               syslog('LOG_INFO', "OpenILS: Checkout succeeded");
+               my $evt = $resp->{ilsevent};
+               my $circ = $resp->{payload}->{circ};
+
+               if(!$circ or $evt ne 0) { 
+                       $self->ok(0); 
+                       warn 'CHECKOUT RESPONSE: ' .  Dumper($resp) . "\n";
+                       return 0; 
+               }
+
+               $self->{'due'} = $circ->due_date;
+               $self->ok(1);
+               return 1;
+       }
+
+       return 0;
+}
+
+
+
+1;