use DateTime::Format::ISO8601;
use Digest::MD5 qw/md5_hex/;
use OpenSRF::System;
+use OpenSRF::AppSession;
use OpenSRF::Utils qw/:datetime/;
use OpenSRF::Utils::SettingsClient;
use OpenILS::Utils::Fieldmapper;
+use OpenILS::Utils::Normalize qw(clean_marc);
use OpenILS::Application::AppUtils;
use OpenILS::Const qw/:const/;
use MARC::Record;
use MARC::Field;
use MARC::File::XML;
use List::MoreUtils qw/uniq/;
+use POSIX qw/strftime/;
# We need a bunch of NCIP::* objects.
use NCIP::Response;
use NCIP::StructuredPersonalUserName;
use NCIP::StructuredAddress;
use NCIP::ElectronicAddress;
+use NCIP::RequestId;
+use NCIP::Item::Id;
# Inherit from NCIP::ILS.
use parent qw(NCIP::ILS);
+=head1 NAME
+
+Evergreen - Evergreen driver for NCIPServer
+
+=head1 SYNOPSIS
+
+ my $ils = NCIP::ILS::Evergreen->new(name => $config->{NCIP.ils.value});
+
+=head1 DESCRIPTION
+
+NCIP::ILS::Evergreen is the default driver for Evergreen and
+NCIPServer. It was initially developed to work with Auto-Graphics'
+SHAREit software using a subset of an unspecified ILL/DCB profile.
+
+=cut
+
# Default values we define for things that might be missing in our
# runtime environment or configuration file that absolutely must have
# values.
return $self;
}
+=head1 HANDLER METHODS
+
+=head2 lookupuser
+
+ $ils->lookupuser($request);
+
+Processes a LookupUser request.
+
+=cut
+
sub lookupuser {
my $self = shift;
my $request = shift;
# Need to parse the request object to get the user barcode.
my ($barcode, $idfield) = $self->find_user_barcode($request);
- # If we can't find a barcode, report a problem.
- unless ($barcode) {
- $idfield = 'AuthenticationInputType' unless ($idfield);
- # Fill in a problem object and stuff it in the response.
- my $problem = NCIP::Problem->new();
- $problem->ProblemType('Needed Data Missing');
- $problem->ProblemDetail('Cannot find user barcode in message.');
- $problem->ProblemElement($idfield);
- $problem->ProblemValue('Barcode');
- $response->problem($problem);
+ # If we did not find a barcode, then report the problem.
+ if (ref($barcode) eq 'NCIP::Problem') {
+ $response->problem($barcode);
return $response;
}
# Look up our patron by barcode:
- my $user = $U->simplereq(
- 'open-ils.actor',
- 'open-ils.actor.user.fleshed.retrieve_by_barcode',
- $self->{session}->{authtoken},
- $barcode,
- 1
- );
-
- # Check for a failure, or a deleted, inactive, or expired user,
- # and if so, return empty userdata.
- if (!$user || $U->event_code($user) || $U->is_true($user->deleted())
- || !grep {$_->barcode() eq $barcode && $U->is_true($_->active())} @{$user->cards()}) {
-
- my $problem = NCIP::Problem->new();
- $problem->ProblemType('Unknown User');
- $problem->ProblemDetail("User with barcode $barcode unknown");
- $problem->ProblemElement($idfield);
- $problem->ProblemValue($barcode);
- $response->problem($problem);
+ my $user = $self->retrieve_user_by_barcode($barcode, $idfield);
+ if (ref($user) eq 'NCIP::Problem') {
+ $response->problem($user);
return $response;
}
return $response;
}
-# Implementation functions that might be useful to a subclass.
+=head2 acceptitem
+
+ $ils->acceptitem($request);
+
+Processes an AcceptItem request.
+
+=cut
+
+sub acceptitem {
+ my $self = shift;
+ my $request = shift;
+
+ # Check our session and login if necessary.
+ $self->login() unless ($self->checkauth());
+
+ # Common preparation.
+ my $message = $self->parse_request_type($request);
+ my $response = NCIP::Response->new(type => $message . 'Response');
+ $response->header($self->make_header($request));
+
+ # We only accept holds for the time being.
+ if ($request->{$message}->{RequestedActionType} =~ /^hold\w/i) {
+ # We need the item id or we can't do anything at all.
+ my ($item_barcode, $item_idfield) = $self->find_item_barcode($request);
+ if (ref($item_barcode) eq 'NCIP::Problem') {
+ $response->problem($item_barcode);
+ return $response;
+ }
+
+ # We need to find a patron barcode or we can't look anyone up
+ # to place a hold.
+ my ($user_barcode, $user_idfield) = $self->find_user_barcode($request, 'UserIdentifierValue');
+ if (ref($user_barcode) eq 'NCIP::Problem') {
+ $response->problem($user_barcode);
+ return $response;
+ }
+ # Look up our patron by barcode:
+ my $user = $self->retrieve_user_by_barcode($user_barcode, $user_idfield);
+ if (ref($user) eq 'NCIP::Problem') {
+ $response->problem($user);
+ return $response;
+ }
+ # We're doing patron checks before looking for bibliographic
+ # information and creating the item because problems with the
+ # patron are more likely to occur.
+ my $problem = $self->check_user_for_problems($user, 'HOLD');
+ if ($problem) {
+ $response->problem($problem);
+ return $response;
+ }
+
+ # Check if the item barcode already exists:
+ my $item = $self->retrieve_copy_by_barcode($item_barcode);
+ if ($item) {
+ # What to do here was not defined in the
+ # specification. Since the copies that we create this way
+ # should get deleted when checked in, it would be an error
+ # if we try to create another one. It means that something
+ # has gone wrong somewhere.
+ $response->problem(
+ NCIP::Problem->new(
+ {
+ ProblemType => 'Duplicate Item',
+ ProblemDescription => "Item with barcode $barcode already exists.",
+ ProblemElement => $item_idfield,
+ ProblemValue => $barcode
+ }
+ );
+ );
+ return $response;
+ }
+
+ # Now, we have to create our new copy and/or bib and call number.
+
+ # First, we have to gather the necessary information from the
+ # request. Store in a hashref for convenience. We may write a
+ # method to get this information in the future if we find we
+ # need it in other handlers. Such a function would be a
+ # candidate to go into our parent, NCIP::ILS.
+ my $item_info = {
+ barcode => $barcode,
+ call_number => $request->{$message}->{ItemOptionalFields}->{ItemDescription}->{CallNumber},
+ title => $request->{$message}->{ItemOptionalFields}->{BibliographicDescription}->{Author},
+ author => $request->{$message}->{ItemOptionalFields}->{BibliographicDescription}->{Title},
+ publisher => $request->{$message}->{ItemOptionalFields}->{BibliographicDescription}->{Publisher},
+ publication_date => $request->{$message}->{ItemOptionalFields}->{BibliographicDescription}->{PublicationDate},
+ medium => $request->{$message}->{ItemOptionalFields}->{BibliographicDescription}->{MediumType},
+ electronic => $request->{$message}->{ItemOptionalFields}->{BibliographicDescription}->{ElectronicResource}
+ };
+
+ if ($self->{config}->{items}->{use_precats}) {
+ # We only need to create a precat copy.
+ $item = $self->create_precat_copy($item_info);
+ } else {
+ # We have to create a "partial" bib record, a call number and a copy.
+ $item = $self->create_fuller_copy($item_info);
+ }
+
+ # If we failed to create the copy, report a problem.
+ unless ($item) {
+ $response->problem(
+ {
+ ProblemType => 'Temporary Processing Failure',
+ ProblemDetail => 'Failed to create the item in the system',
+ ProblemElement => $item_idfield,
+ ProblemValue => $barcode
+ }
+ );
+ return $response;
+ }
+
+ # We try to find the pickup location in our database. It's OK
+ # if it does not exist, the user's home library will be used
+ # instead.
+ my $location = $request->{$message}->{PickupLocation};
+ if ($location) {
+ $location = $self->retrieve_org_unit_by_shortname($location);
+ }
+
+ # Now, we place the hold on the newly created copy on behalf
+ # of the patron retrieved above.
+ my $hold = $self->place_hold($item, $user, $location);
+ if (ref($hold) eq 'NCIP::Problem') {
+ $response->problem($hold);
+ return $response;
+ }
+
+ # We return the RequestId and optionally, the ItemID. We'll
+ # just return what was sent to us, since we ignored all of it
+ # but the barcode.
+ my $data = {};
+ $data->{RequestId} = NCIP::RequestId->new(
+ {
+ AgencyId => $request->{$message}->{RequestId}->{AgencyId},
+ RequestIdentifierType => $request->{$message}->{RequestId}->{RequestIdentifierType};
+ RequestIdentifierValue => $request->{$message}->{RequestId}->{RequestIdentifierValue};
+ }
+ );
+ $data->{ItemId} = NCIP::Item::Id->new(
+ {
+ AgencyId => $request->{$message}->{ItemId}->{AgencyId},
+ ItemIdentifierType => $request->{$message}->{ItemId}->{ItemIdentifierType};
+ ItemIdentifierValue => $request->{$message}->{ItemId}->{ItemIdentifierValue};
+ }
+ );
+ $response->data($data);
+
+ } else {
+ my $problem = NCIP::Problem->new();
+ $problem->ProblemType('Unauthorized Combination Of Element Values For System');
+ $problem->ProblemDetail('We only support Hold For Pickup');
+ $problem->ProblemElement('RequestedActionType');
+ $problem->ProblemValue($request->{$message}->{RequestedActionType});
+ $response->problem($problem);
+ }
+
+ return $response;
+}
+
+=head1 METHODS USEFUL to SUBCLASSES
+
+=head2 login
+
+ $ils->login();
+
+Login to Evergreen via OpenSRF. It uses internal state from the
+configuration file to login.
+
+=cut
# Login via OpenSRF to Evergreen.
sub login {
if ($response) {
$self->{session}->{authtoken} = $response->{payload}->{authtoken};
$self->{session}->{authtime} = $response->{payload}->{authtime};
+
+ # Set/reset the work_ou and user data in case something changed.
+
+ # Retrieve the work_ou as an object.
+ $self->{session}->{work_ou} = $U->simplereq(
+ 'open-ils.pcrud',
+ 'open-ils.pcrud.search.aou',
+ $self->{session}->{authtoken},
+ {shortname => $self->{config}->{credentials}->{work_ou}}
+ );
+
+ # We need the user information in order to do some things.
+ $self->{session}->{user} = $U->check_user_session($self->{session}->{authtoken});
+
}
}
}
-# Return 1 if we have a 'valid' authtoken, 0 if not.
+=head2 checkauth
+
+ $valid = $ils->checkauth();
+
+Returns 1 if the object a 'valid' authtoken, 0 if not.
+
+=cut
+
sub checkauth {
my $self = shift;
return 0;
}
+=head2 retrieve_user_by_barcode
+
+ $user = $ils->retrieve_user_by_barcode($user_barcode, $user_idfield);
+
+Do a fleshed retrieve of a patron by barcode. Return the patron if
+found and valid. Return a NCIP::Problem of 'Unknown User' otherwise.
+
+The id field argument is used for the ProblemElement field in the
+NCIP::Problem object.
+
+An invalid patron is one where the barcode is not found in the
+database, the patron is deleted, or the barcode used to retrieve the
+patron is not active. The problem element is also returned if an error
+occurs during the retrieval.
+
+=cut
+
+sub retrieve_user_by_barcode {
+ my ($self, $barcode, $idfield) = @_;
+ my $result = $U->simplereq(
+ 'open-ils.actor',
+ 'open-ils.actor.user.fleshed.retrieve_by_barcode',
+ $self->{session}->{authtoken},
+ $barcode,
+ 1
+ );
+
+ # Check for a failure, or a deleted, inactive, or expired user,
+ # and if so, return empty userdata.
+ if (!$result || $U->event_code($result) || $U->is_true($result->deleted())
+ || !grep {$_->barcode() eq $barcode && $U->is_true($_->active())} @{$result->cards()}) {
+
+ my $problem = NCIP::Problem->new();
+ $problem->ProblemType('Unknown User');
+ $problem->ProblemDetail("User with barcode $barcode unknown");
+ $problem->ProblemElement($idfield);
+ $problem->ProblemValue($barcode);
+ $result = $problem;
+ }
+
+ return $result;
+}
+
+=head2 check_user_for_problems
+
+ $problem = $ils>check_user_for_problems($user, 'HOLD, 'CIRC', 'RENEW');
+
+This function checks if a user has a blocked profile or any from a
+list of provided blocks. If it does, then a NCIP::Problem object is
+returned, otherwise an undefined value is returned.
+
+The list of blocks appears as additional arguments after the user. You
+can provide any value(s) that might appear in a standing penalty block
+lit in Evergreen. The example above checks for HOLD, CIRC, and
+RENEW. Any number of such values can be provided. If none are
+provided, the function only checks if the patron's profiles appears in
+the object's blocked profiles list.
+
+It stops on the first matching block, if any.
+
+=cut
+
+sub check_user_for_problems {
+ my $self = shift;
+ my $user = shift;
+ my @blocks = @_;
+
+ # Fill this in if we have a problem, otherwise just return it.
+ my $problem;
+
+ # First, check the user's profile.
+ if (grep {$_->id() == $user->profile()} @{$self->{blocked_profiles}}) {
+ $problem = NCIP::Problem->new(
+ {
+ ProblemType => 'User Blocked',
+ ProblemDetail => 'User blocked from inter-library loan',
+ ProblemElement => 'NULL',
+ ProblemValue => 'NULL'
+ }
+ );
+ }
+
+ # Next, check if the patron has one of the indicated blocks.
+ unless ($problem) {
+ foreach my $block (@blocks) {
+ if (grep {$_->standing_penalty->block_list() =~ /$block/} @{$user->standing_penalties()}) {
+ $problem = NCIP::Problem->new(
+ {
+ ProblemType => 'User Blocked',
+ ProblemDetail => 'User blocked from ' .
+ ($block eq 'HOLD') ? 'holds' : (($block eq 'RENEW') ? 'renewals' :
+ (($block eq 'CIRC') ? 'checkout' : lc($block))),
+ ProblemElement => 'NULL',
+ ProblemValue => 'NULL'
+ }
+ );
+ last;
+ }
+ }
+ }
+
+ return $problem;
+}
+
+=head2 retrieve_copy_by_barcode
+
+ $copy = $ils->retrieve_copy_by_barcode($copy_barcode);
+
+Look up and retrieve some copy details by the copy barcode. This
+method returns either a valid copy object or undefined if no copy
+exists with that barcode or if some error occurs.
+
+This method differs from C<retrieve_user_by_barcode> in that a copy
+cannot be invalid if it exists and it is not always an error if no
+copy exists. In some cases, when handling AcceptItem, we might prefer
+there to be no copy.
+
+=cut
+
+sub retrieve_copy_by_barcode {
+ my $self = shift;
+ my $barcode = shift;
+
+ my $copy = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.copy_details.retrieve.barcode',
+ $self->{session}->{authtoken},
+ $barcode
+ );
+
+ # If $copy is an event, return undefined.
+ if ($copy && $U->event_code($copy)) {
+ undef($copy);
+ }
+
+ return $copy;
+}
+
+=head2 retrieve_org_unit_by_shortname
+
+ $org_unit = $ils->retrieve_org_unit_by_shortname($shortname);
+
+Retrieves an org. unit from the database by shortname. Returns the
+org. unit as a Fieldmapper object or undefined.
+
+=cut
+
+sub retrieve_org_unit_by_shortname {
+ my $self = shift;
+ my $shortname = shift;
+
+ my $aou = $U->simplereq(
+ 'open-ils.pcrud',
+ 'open-ils.pcrud.search.aou',
+ $self->{session}->{authtoken},
+ {shortname => {'=' => {transform => 'lower', value => ['lower', $shortname]}}}
+ );
+
+ return $aou;
+}
+
+=head2 create_precat_copy
+
+ $item_info->{
+ barcode => '312340123456789',
+ author => 'Public, John Q.',
+ title => 'Magnum Opus',
+ call_number => '005.82',
+ publisher => 'Brick House',
+ publication_date => '2014'
+ };
+
+ $item = $ils->create_precat_copy($item_info);
+
+
+Create a "precat" copy to use for the incoming item using a hashref of
+item information. At a minimum, the barcode, author and title fields
+need to be filled in. The other fields are ignored if provided.
+
+This method is called by the AcceptItem handler if the C<use_precats>
+configuration option is turned on.
+
+=cut
+
+sub create_precat_copy {
+ my $self = shift;
+ my $item_info = shift;
+
+ my $item = Fieldmapper::asset::copy->new();
+ $item->barcode($item_info->{barcode});
+ $item->call_number(OILS_PRECAT_CALL_NUMBER);
+ $item->dummy_title($item_info->{title});
+ $item->dummy_author($item_info->{author});
+ $item->circ_lib($self->{session}->{work_ou}->id());
+ $item->circulate('t');
+ $item->holdable('t');
+ $item->opac_visible('f');
+ $item->deleted('f');
+ $item->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
+ $item->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
+ $item->location(1);
+ $item->status(0);
+ $item->editor($self->{session}->{user}->id());
+ $item->creator($self->{session}->{user}->id());
+ $item->isnew(1);
+
+ # Actually create it:
+ my $xact;
+ my $ses = OpenSRF::AppSession->create('open-ils.pcrud');
+ $ses->connect();
+ eval {
+ $xact = $ses->request(
+ 'open-ils.pcrud.transaction.begin',
+ $self->{session}->{authtoken}
+ )->gather(1);
+ $item = $ses->request(
+ 'open-ils.pcrud.create.acp',
+ $self->{session}->{authtoken},
+ $item
+ )->gather(1);
+ $xact = $ses->request(
+ 'open-ils.pcrud.transaction.commit',
+ $self->{session}->{authtoken}
+ )->gather(1);
+ };
+ if ($@) {
+ undef($item);
+ if ($xact) {
+ eval {
+ $ses->request(
+ 'open-ils.pcrud.transaction.rollback',
+ $self->{session}->{authtoken}
+ )->gather(1);
+ };
+ }
+ }
+ $ses->disconnect();
+
+ return $item;
+}
+
+=head2 create_fuller_copy
+
+ $item_info->{
+ barcode => '31234003456789',
+ author => 'Public, John Q.',
+ title => 'Magnum Opus',
+ call_number => '005.82',
+ publisher => 'Brick House',
+ publication_date => '2014'
+ };
+
+ $item = $ils->create_fuller_copy($item_info);
+
+Creates a skeletal bibliographic record, call number, and copy for the
+incoming item using a hashref with item information in it. At a
+minimum, the barcode, author, title, and call_number fields must be
+filled in.
+
+This method is used by the AcceptItem handler if the C<use_precats>
+configuration option is NOT set.
+
+=cut
+
+sub create_fuller_copy {
+ my $self = shift;
+ my $item_info = shift;
+
+ my $item;
+
+ # We do everything in one transaction, because it should be atomic.
+ my $ses = OpenSRF::AppSession->create('open-ils.pcrud');
+ $ses->connect();
+ my $xact;
+ eval {
+ $xact = $ses->request(
+ 'open-ils.pcrud.transaction.begin',
+ $self->{session}->{authtoken}
+ )->gather(1);
+ };
+ if ($@) {
+ undef($xact);
+ }
+
+ # The rest depends on there being a transaction.
+ if ($xact) {
+
+ # Create the MARC record.
+ my $record = MARC::Record->new();
+ $record->encoding('UTF-8');
+ $record->leader('00881nam a2200193 4500');
+ my $datespec = strftime("%Y%m%d%H%M%S.0", localtime);
+ my @fields = ();
+ push(@fields, MARC::Field->new('005', $datespec));
+ push(@fields, MARC::Field->new('082', '0', '4', 'a' => $item_info->{call_number}));
+ push(@fields, MARC::Field->new('245', '0', '0', 'a' => $item_info->{title}));
+ # Publisher is a little trickier:
+ if ($item_info->{publisher}) {
+ my $pub = MARC::Field->new('260', ' ', ' ', 'a' => '[S.l.]', 'b' => $item_info->{publisher});
+ $pub->add_subfields('c' => $item_info->{publication_date}) if ($item_info->{publication_date});
+ push(@fields, $pub);
+ }
+ # We have no idea if the author is personal corporate or something else, so we use a 720.
+ push(@fields, MARC::Field->new('720', ' ', ' ', 'a' => $item_info->{author}, '4' => 'aut'));
+ $record->append_fields(@fields);
+ my $marc = clean_marc($record);
+
+ # Create the bib object.
+ my $bib = Fieldmapper::biblio::record_entry->new();
+ $bib->creator($self->{session}->{user}->id());
+ $bib->editor($self->{session}->{user}->id());
+ $bib->source($self->{bib_source}->id());
+ $bib->active('t');
+ $bib->deleted('f');
+ $bib->marc($marc);
+ $bib->isnew(1);
+
+ eval {
+ $bib = $ses->request(
+ 'open-ils.pcrud.create.bre',
+ $authtoken,
+ $bib
+ )->gather(1);
+ };
+ if ($@) {
+ undef($bib);
+ eval {
+ $ses->request(
+ 'open-ils.pcrud.transaction.rollback',
+ $authtoken
+ )->gather(1);
+ };
+ }
+
+ # Create the call number
+ my $acn;
+ if ($bib) {
+ $acn = Fieldmapper::asset::call_number->new();
+ $acn->creator($self->{session}->{user}->id());
+ $acn->editor($self->{session}->{user}->id());
+ $acn->label($item_info->{call_number});
+ $acn->record($bib->id());
+ $acn->owning_lib($self->{session}->{work_ou}->id());
+ $acn->deleted('f');
+ $acn->isnew(1);
+
+ eval {
+ $acn = $ses->request(
+ 'open-ils.pcrud.create.acn',
+ $authtoken,
+ $acn
+ )->gather(1);
+ };
+ if ($@) {
+ undef($acn);
+ eval {
+ $ses->request(
+ 'open-ils.pcrud.transaction.rollback',
+ $authtoken
+ )->gather(1);
+ };
+ }
+ }
+
+ # create the copy
+ if ($acn) {
+ $item = Fieldmapper::asset::copy->new();
+ $item->barcode($item_info->{barcode});
+ $item->call_number($acn->id());
+ $item->circ_lib($self->{session}->{work_ou}->id);
+ $item->circulate('t');
+ $item->holdable('t');
+ $item->opac_visible('f');
+ $item->deleted('f');
+ $item->fine_level(OILS_PRECAT_COPY_FINE_LEVEL);
+ $item->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION);
+ $item->location(1);
+ $item->status(0);
+ $item->editor($self->{session}->{user}->id);
+ $item->creator($self->{session}->{user}->id);
+ $item->isnew(1);
+
+ eval {
+ $item = $ses->request(
+ 'open-ils.pcrud.create.acp',
+ $self->{session}->{authtoken},
+ $item
+ )->gather(1);
+
+ # Cross our fingers and commit the work.
+ $xact = $ses->request(
+ 'open-ils.pcrud.transaction.commit',
+ $self->{session}->{authtoken}
+ )->gather(1);
+ };
+ if ($@) {
+ undef($item);
+ eval {
+ $ses->request(
+ 'open-ils.pcrud.transaction.rollback',
+ $authtoken
+ )->gather(1) if ($xact);
+ };
+ }
+ }
+ }
+
+ # We need to disconnect our session.
+ $ses->disconnect();
+
+ # Now, we handle our asset stat_cat entries.
+ if ($item) {
+ # It would be nice to do these in the above transaction, but
+ # pcrud does not support the ascecm object, yet.
+ foreach my $entry (@{$self->{stat_cat_entries}}) {
+ my $map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
+ $map->isnew(1);
+ $map->stat_cat($entry->stat_cat());
+ $map->stat_cat_entry($entry->id());
+ $map->owning_copy($item->id());
+ # We don't really worry if it succeeds or not.
+ $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.stat_cat.asset.copy_map.create',
+ $authtoken,
+ $map
+ );
+ }
+ }
+
+ return $item;
+}
+
+=head2 place_hold
+
+ $hold = $ils->place_hold($item, $user, $location);
+
+This function places a hold on $item for $user for pickup at
+$location. If location is not provided or undefined, the user's home
+library is used as a fallback.
+
+$item can be a copy (asset::copy), volume (asset::call_number), or bib
+(biblio::record_entry). The appropriate hold type will be placed
+depending on the object.
+
+On success, the method returns the object representing the hold. On
+failure, a NCIP::Problem object, describing the failure, is returned.
+
+=cut
+
+sub place_hold {
+ my $self = shift;
+ my $item = shift;
+ my $user = shift;
+ my $location = shift;
+
+ # If $location is undefined, use the user's home_ou, which should
+ # have been fleshed when the user was retrieved.
+ $location = $user->home_ou() unless ($location);
+
+ # $hold is the hold. $params is for the is_possible check.
+ my ($hold, $params);
+
+ # Prep the hold with fields common to all hold types:
+ $hold = Fieldmapper::action::hold_request->new();
+ $hold->isnew(1); # Just to make sure.
+ $hold->target($item->id());
+ $hold->usr($user->id());
+ $hold->pickup_lib($location->id());
+ if (!$user->email()) {
+ $hold->email_notify('f');
+ $hold->phone_notify($user->day_phone()) if ($user->day_phone());
+ } else {
+ $hold->email_notify('t');
+ }
+
+ # Ditto the params:
+ $params = { pickup_lib => $location->id(), patronid => $user->id() };
+
+ if (ref($item) eq 'Fieldmapper::asset::copy') {
+ $hold->hold_type('C');
+ $hold->current_copy($item->id());
+ $params->{hold_type} = 'C';
+ $params->{copy_id} = $item->id();
+ } elsif (ref($item) eq 'Fieldmapper::asset::call_number') {
+ $hold->hold_type('V');
+ $params->{hold_type} = 'V';
+ $params->{volume_id} = $item->id();
+ } elsif (ref($item) eq 'Fieldmapper::biblio::record_entry') {
+ $hold->hold_type('T');
+ $params->{hold_type} = 'T';
+ $params->{titleid} = $item->id();
+ }
+
+ # Check if the hold is possible:
+ my $r = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.title_hold.is_possible',
+ $self->{session}->{authtoken},
+ $params
+ );
+
+ if ($r->{success}) {
+ $hold = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.holds.create.override',
+ $self->{session}->{authtoken},
+ $hold
+ );
+ if (ref($hold) eq 'HASH') {
+ $hold = _problem_from_event('Request Not Possible', $hold);
+ }
+ } elsif ($r->{last_event}) {
+ $hold = _problem_from_event('Request Not Possible', $r->{last_event});
+ } elsif ($r->{text_code}) {
+ $hold = _problem_from_event('Request Not Possible', $r);
+ } else {
+ $hold = _problem_from_event('Request Not Possible');
+ }
+
+ return $hold;
+}
+
+=head1 OVERRIDDEN PARENT METHODS
+
+=head2 find_user_barcode
+
+We dangerously override our parent's C<find_user_barcode> to return
+either the $barcode or a Problem object. In list context the barcode
+or problem will be the first argument and the id field, if any, will
+be the second. We also add a second, optional, argument to indicate a
+default value for the id field in the event of a failure to find
+anything at all. (Perl lets us get away with this.)
+
+=cut
+
+sub find_user_barcode {
+ my $self = shift;
+ my $request = shift;
+ my $default = shift;
+
+ unless ($default) {
+ my $message = $self->parse_request_type($request);
+ if ($message eq 'LookupUser') {
+ $default = 'AuthenticationInputData';
+ } else {
+ $default = 'UserIdentifierValue';
+ }
+ }
+
+ my ($value, $idfield) = $self->SUPER::find_user_barcode($request);
+
+ unless ($value) {
+ $idfield = $default unless ($idfield);
+ $value = NCIP::Problem->new();
+ $value->ProblemType('Needed Data Missing');
+ $value->ProblemDetail('Cannot find user barcode in message.');
+ $value->ProblemElement($idfield);
+ $value->ProblemValue('NULL');
+ }
+
+ return (wantarray) ? ($value, $idfield) : $value;
+}
+
+=head2 find_item_barcode
+
+We do pretty much the same thing as with C<find_user_barcode> for
+C<find_item_barcode>.
+
+=cut
+
+sub find_item_barcode {
+ my $self = shift;
+ my $request = shift;
+ my $default = shift || 'ItemIdentifierValue';
+
+ my ($value, $idfield) = $self->SUPER::find_item_barcode($request);
+
+ unless ($value) {
+ $idfield = $default unless ($idfield);
+ $value = NCIP::Problem->new();
+ $value->ProblemType('Needed Data Missing');
+ $value->ProblemDetail('Cannot find item barcode in message.');
+ $value->ProblemElement($idfield);
+ $value->ProblemValue('NULL');
+ }
+
+ return (wantarray) ? ($value, $idfield) : $value;
+}
+
# private subroutines not meant to be used directly by subclasses.
# Most have to do with setup and/or state checking of implementation
# components.
# Login to Evergreen.
$self->login();
- # Retrieve the work_ou as an object.
- $self->{work_ou} = $U->simplereq(
- 'open-ils.pcrud',
- 'open-ils.pcrud.search.aou',
- $self->{session}->{authtoken},
- {shortname => $self->{config}->{credentials}->{work_ou}}
- );
-
# Load the barred groups as pgt objects into a blocked_profiles
# list.
$self->{blocked_profiles} = [];
# Load the required asset.stat_cat_entries:
$self->{stat_cat_entries} = [];
# First, make a regex for our ou and ancestors:
- my $ancestors = join("|", @{$U->get_org_ancestors($self->{work_ou}->id())});
+ my $ancestors = join("|", @{$U->get_org_ancestors($self->{session}->{work_ou}->id())});
my $re = qr/(?:$ancestors)/;
# Get the uniq stat_cat ids from the configuration:
my @cats = uniq map {$_->{stat_cat}} @{$self->{config}->{items}->{stat_cat_entry}};
return $expired;
}
+# Creates a NCIP Problem from an event. Takes a string for the problem
+# type, the event hashref, and optional arguments for the
+# ProblemElement and ProblemValue fields.
+sub _problem_from_event {
+ my ($type, $evt, $element, $value) = @_;
+
+ my $detail;
+
+ # This block will likely need to get smarter in the near future.
+ if ($evt) {
+ if ($evt->{text_code} eq 'PERM_FAILURE') {
+ $detail = 'Permission Failure: ' . $evt->{ilsperm};
+ $detail =~ s/\.override$//;
+ } else {
+ $detail = 'ILS returned ' . $evt->{text_code} . ' error.';
+ }
+ } else {
+ $detail = 'Detail not available.';
+ }
+
+ return NCIP::Problem->new(
+ {
+ ProblemType => $type,
+ ProblemDetail => $detail,
+ ProblemElement => ($element) ? $element : 'NULL';
+ ProblemValue => ($value) ? $vale : 'NULL';
+ }
+ );
+}
+
1;