<field name="cc_first_name" reporter:datatype="text"/>
<field name="cc_last_name" reporter:datatype="text"/>
<field name="cc_number" reporter:datatype="text"/>
+ <field name="cc_order_number" reporter:datatype="text"/>
<field name="cc_type" reporter:datatype="text"/>
<field name="cc_processor" reporter:datatype="text"/>
<field name="expire_month" reporter:datatype="int" />
<class id="sbsum" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::basic_summary" oils_persist:tablename="serial.basic_summary" reporter:label="Basic Issue Summary">
<fields oils_persist:primary="id" oils_persist:sequence="serial.basic_summary_id_seq">
- <field name="id" reporter:datatype="id" />
- <field name="distribution" reporter:datatype="link"/>
- <field name="generated_coverage" reporter:datatype="text"/>
- <field name="textual_holdings" reporter:datatype="text"/>
- <field name="show_generated" reporter:datatype="bool"/>
+ <field reporter:label="ID" name="id" reporter:datatype="id" />
+ <field reporter:label="Distribution" name="distribution" reporter:datatype="link"/>
+ <field reporter:label="Generated Coverage" name="generated_coverage" reporter:datatype="text"/>
+ <field reporter:label="Textual Holdings" name="textual_holdings" reporter:datatype="text"/>
+ <field reporter:label="Show Generated?" name="show_generated" reporter:datatype="bool"/>
</fields>
<links>
<link field="distribution" reltype="has_a" key="id" map="" class="sdist"/>
<class id="sssum" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::supplement_summary" oils_persist:tablename="serial.supplement_summary" reporter:label="Supplemental Issue Summary">
<fields oils_persist:primary="id" oils_persist:sequence="serial.supplement_summary_id_seq">
- <field name="id" reporter:datatype="id" />
- <field name="distribution" reporter:datatype="link"/>
- <field name="generated_coverage" reporter:datatype="text"/>
- <field name="textual_holdings" reporter:datatype="text"/>
- <field name="show_generated" reporter:datatype="bool"/>
+ <field reporter:label="ID" name="id" reporter:datatype="id" />
+ <field reporter:label="Distribution" name="distribution" reporter:datatype="link"/>
+ <field reporter:label="Generated Coverage" name="generated_coverage" reporter:datatype="text"/>
+ <field reporter:label="Textual Holdings" name="textual_holdings" reporter:datatype="text"/>
+ <field reporter:label="Show Generated?" name="show_generated" reporter:datatype="bool"/>
</fields>
<links>
<link field="distribution" reltype="has_a" key="id" map="" class="sdist"/>
<class id="sisum" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::index_summary" oils_persist:tablename="serial.index_summary" reporter:label="Index Issue Summary">
<fields oils_persist:primary="id" oils_persist:sequence="serial.index_summary_id_seq">
- <field name="id" reporter:datatype="id" />
- <field name="distribution" reporter:datatype="link"/>
- <field name="generated_coverage" reporter:datatype="text"/>
- <field name="textual_holdings" reporter:datatype="text"/>
- <field name="show_generated" reporter:datatype="bool"/>
+ <field reporter:label="ID" name="id" reporter:datatype="id" />
+ <field reporter:label="Distribution" name="distribution" reporter:datatype="link"/>
+ <field reporter:label="Generated Coverage" name="generated_coverage" reporter:datatype="text"/>
+ <field reporter:label="Textual Holdings" name="textual_holdings" reporter:datatype="text"/>
+ <field reporter:label="Show Generated?" name="show_generated" reporter:datatype="bool"/>
</fields>
<links>
<link field="distribution" reltype="has_a" key="id" map="" class="sdist"/>
int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
OSRF_METHOD_VERIFY_CONTEXT(ctx);
+ bool returnFull = false;
const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
+
+ if(ctx->params->size > 1) {
+ // caller wants full cached object, with authtime, etc.
+ const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
+ if(rt && strcmp(rt, "0") != 0)
+ returnFull = true;
+ }
+
jsonObject* cacheObj = NULL;
oilsEvent* evt = NULL;
cacheObj = osrfCacheGetObject( key );
if(cacheObj) {
// Return a copy of the cached user object
- osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
+ if(returnFull)
+ osrfAppRespondComplete( ctx, cacheObj);
+ else
+ osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
jsonObjectFree(cacheObj);
} else {
// Auth token is invalid or expired
use OpenILS::Utils::CStoreEditor qw/:funcs/;
use OpenILS::Utils::Penalty;
-use List::Util qw/max/;
+use List::Util qw/max reduce/;
use UUID::Tiny qw/:std/;
->run($auth => $user_id);
return $out if (defined($U->event_code($out)));
+ $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
+
return {
user => {
first_given_name => $user->first_given_name,
($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
$copy->circ_lib : $copy->call_number->owning_lib;
- return $e->die_event unless $e->allowed('UPDATE_COPY', $owning_lib);
-
-
my $perm = 'MARK_ITEM_MISSING';
my $stat = OILS_COPY_STATUS_MISSING;
my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
return $evt if $evt;
- my $ses = OpenSRF::AppSession->create('open-ils.trigger');
- $ses->request('open-ils.trigger.event.autocreate', 'damaged', $copy, $owning_lib);
-
} elsif ( $self->api_name =~ /bindery/ ) {
$perm = 'MARK_ITEM_BINDERY';
$stat = OILS_COPY_STATUS_BINDERY;
$stat = OILS_COPY_STATUS_DISCARD;
}
+ # caller may proceed if either perm is allowed
+ return $e->die_event unless $e->allowed([$perm, 'UPDATE_COPY'], $owning_lib);
$copy->status($stat);
$copy->edit_date('now');
$e->commit;
+ if( $self->api_name =~ /damaged/ ) {
+ # now that we've committed the changes, create related A/T events
+ my $ses = OpenSRF::AppSession->create('open-ils.trigger');
+ $ses->request('open-ils.trigger.event.autocreate', 'damaged', $copy, $owning_lib);
+ }
+
$logger->debug("resetting holds that target the marked copy");
OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
}
);
+# XXX Need to add type I (and, soon, type P) holds to these counts
sub rec_hold_count {
my($self, $conn, $target_id) = @_;
my %orgs;
# unless/until determined by payment processor API
- my ($approval_code, $cc_processor, $cc_type) = (undef,undef,undef);
+ my ($approval_code, $cc_processor, $cc_type, $cc_order_number) = (undef,undef,undef, undef);
my $patron = $e->retrieve_actor_user($user_id) or return $e->die_event;
$approval_code = $cc_payload->{"authorization"};
$cc_type = $cc_payload->{"card_type"};
$cc_processor = $cc_payload->{"processor"};
+ $cc_order_number = $cc_payload->{"order_number"};
$logger->info("Credit card payment for user $user_id succeeded");
}
} else {
}
$payment->approval_code($approval_code) if $approval_code;
+ $payment->cc_order_number($cc_order_number) if $cc_order_number;
$payment->cc_type($cc_type) if $cc_type;
$payment->cc_processor($cc_processor) if $cc_processor;
$payment->cc_first_name($cc_args->{'billing_first'}) if $cc_args->{'billing_first'};
sub biblio_search_isbn {
my( $self, $client, $isbn ) = @_;
$logger->debug("Searching ISBN $isbn");
+ # Strip hyphens from incoming ISBNs
+ $isbn =~ s/-//g;
my $recs = $U->storagereq('open-ils.storage.id_list.biblio.record_entry.search.isbn.atomic', $isbn);
return { ids => $recs, count => scalar(@$recs) };
}
$logger->debug("Searching ISBNs @$isbn_list");
my @recs = (); my %rec_set = ();
foreach my $isbn ( @$isbn_list ) {
+ # Strip hyphens from incoming ISBNs
+ $isbn =~ s/-//g;
foreach my $rec ( @{ $U->storagereq(
'open-ils.storage.id_list.biblio.record_entry.search.isbn.atomic', $isbn )
} ) {
+use strict;
+use warnings;
+
package OpenILS::Application::Storage::Publisher::authority;
use base qw/OpenILS::Application::Storage::Publisher/;
use vars qw/$VERSION/;
my $sf = $$search{subfield};
my $term = naco_normalize($$search{term}, $sf);
- $tag = [$tag] if (!ref($tag));
-
push @values, $t, $sf, $term;
push @selects,
sub checkauth {
my $self = shift;
$self->log(D, "checking auth token ".$self->authtoken);
- my ($reqr, $evt) = $U->checkses($self->authtoken);
- $self->event($evt) if $evt;
- return $self->{requestor} = $reqr;
-}
+ my $content = $U->simplereq(
+ 'open-ils.auth',
+ 'open-ils.auth.session.retrieve', $self->authtoken, 1);
+
+ if(!$content or $U->event_code($content)) {
+ $self->event( ($content) ? $content : OpenILS::Event->new('NO_SESSION'));
+ return undef;
+ }
+
+ $self->{authtime} = $content->{authtime};
+ return $self->{requestor} = $content->{userobj};
+}
=head test
sub checkauth {
return $self->{authtoken};
}
+sub authtime {
+ my( $self, $auth ) = @_;
+ $self->{authtime} = $auth if $auth;
+ return $self->{authtime};
+}
+
sub timeout {
my($self, $to) = @_;
$self->{timeout} = $to if defined $to;
use warnings;
use Data::Dumper;
use Test::More 'no_plan';
+use File::Basename qw(dirname);
use MARC::Record;
use OpenILS::Utils::MFHD;
my $rec;
my @captions;
-open(my $testdata, "<mfhddata.txt") or die("Cannot open 'mfhddata.txt': $!");
+my $testfile = dirname(__FILE__) . "/mfhddata.txt";
+open(my $testdata, "<", $testfile) or die("Cannot open '$testfile': $!");
while ($rec = testlib::load_MARC_rec($testdata, $testno++)) {
$rec = MFHD->new($rec);
use OpenILS::Utils::CStoreEditor qw/:funcs/;
use OpenILS::Utils::Fieldmapper;
use DateTime::Format::ISO8601;
+
+# EGCatLoader sub-modules
+use OpenILS::WWW::EGCatLoader::Util;
+use OpenILS::WWW::EGCatLoader::Account;
+use OpenILS::WWW::EGCatLoader::Search;
+use OpenILS::WWW::EGCatLoader::Record;
+
my $U = 'OpenILS::Application::AppUtils';
sub new {
}
-# load common data, then load page data
+# -----------------------------------------------------------------------------
+# Perform initial setup, load common data, then load page data
+# -----------------------------------------------------------------------------
sub load {
my $self = shift;
- $self->load_helpers;
+ $self->init_ro_object_cache;
+
my $stat = $self->load_common;
return $stat unless $stat == Apache2::Const::OK;
my $path = $self->apache->path_info;
- return $self->load_home if $path =~ /opac\/home/;
+ return $self->load_simple("home") if $path =~ /opac\/home/;
+ return $self->load_simple("advanced") if $path =~ /opac\/advanced/;
return $self->load_login if $path =~ /opac\/login/;
return $self->load_logout if $path =~ /opac\/logout/;
return $self->load_rresults if $path =~ /opac\/results/;
return $self->load_record if $path =~ /opac\/record/;
# ----------------------------------------------------------------
- # These pages require authentication
+ # Everything below here requires authentication
# ----------------------------------------------------------------
- unless($self->cgi->https and $self->editor->requestor) {
- # If a secure resource is requested insecurely, redirect to the login page
- my $url = 'https://' . $self->apache->hostname . $self->ctx->{base_path} . "/opac/login";
- $self->apache->print($self->cgi->redirect(-url => $url));
- return Apache2::Const::REDIRECT;
- }
+ return $self->redirect_secure($path)
+ unless $self->cgi->https and $self->editor->requestor;
return $self->load_place_hold if $path =~ /opac\/place_hold/;
return $self->load_myopac_holds if $path =~ /opac\/myopac\/holds/;
return $self->load_myopac_circs if $path =~ /opac\/myopac\/circs/;
- return $self->load_myopac_fines if $path =~ /opac\/myopac\/fines/;
+ return $self->load_myopac_fines if $path =~ /opac\/myopac\/main/;
return $self->load_myopac_update_email if $path =~ /opac\/myopac\/update_email/;
return $self->load_myopac_bookbags if $path =~ /opac\/myopac\/bookbags/;
return $self->load_myopac if $path =~ /opac\/myopac/;
- # ----------------------------------------------------------------
return Apache2::Const::OK;
}
-# general purpose utility functions added to the environment
-
-my %cache = (
- map => {aou => {}}, # others added dynamically as needed
- list => {},
- org_settings => {}
-);
-
-sub load_helpers {
- my $self = shift;
- my $e = $self->editor;
- my $ctx = $self->ctx;
-
- # fetch-on-demand-and-cache subs for commonly used public data
- my @public_classes = qw/ccs aout cifm citm clm/;
-
- for my $hint (@public_classes) {
-
- my ($class) = grep {
- $Fieldmapper::fieldmap->{$_}->{hint} eq $hint
- } keys %{ $Fieldmapper::fieldmap };
-
- my $ident_field = $Fieldmapper::fieldmap->{$class}->{identity};
-
- $class =~ s/Fieldmapper:://o;
- $class =~ s/::/_/g;
-
- # copy statuses
- my $list_key = $hint . '_list';
- my $find_key = "find_$hint";
-
- $ctx->{$list_key} = sub {
- my $method = "retrieve_all_$class";
- $cache{list}{$hint} = $e->$method() unless $cache{list}{$hint};
- return $cache{list}{$hint};
- };
-
- $cache{map}{$hint} = {} unless $cache{map}{$hint};
-
- $ctx->{$find_key} = sub {
- my $id = shift;
- return $cache{map}{$hint}{$id} if $cache{map}{$hint}{$id};
- ($cache{map}{$hint}{$id}) = grep { $_->$ident_field eq $id } @{$ctx->{$list_key}->()};
- return $cache{map}{$hint}{$id};
- };
-
- }
-
- $ctx->{aou_tree} = sub {
-
- # fetch the org unit tree
- unless($cache{aou_tree}) {
- my $tree = $e->search_actor_org_unit([
- { parent_ou => undef},
- { flesh => -1,
- flesh_fields => {aou => ['children']},
- order_by => {aou => 'name'}
- }
- ])->[0];
-
- # flesh the org unit type for each org unit
- # and simultaneously set the id => aou map cache
- sub flesh_aout {
- my $node = shift;
- my $ctx = shift;
- $node->ou_type( $ctx->{find_aout}->($node->ou_type) );
- $cache{map}{aou}{$node->id} = $node;
- flesh_aout($_, $ctx) foreach @{$node->children};
- };
- flesh_aout($tree, $ctx);
-
- $cache{aou_tree} = $tree;
- }
-
- return $cache{aou_tree};
- };
-
- # Add a special handler for the tree-shaped org unit cache
- $ctx->{find_aou} = sub {
- my $org_id = shift;
- $ctx->{aou_tree}->(); # force the org tree to load
- return $cache{map}{aou}{$org_id};
- };
-
- # turns an ISO date into something TT can understand
- $ctx->{parse_datetime} = sub {
- my $date = shift;
- $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date));
- return sprintf(
- "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
- $date->hour,
- $date->minute,
- $date->second,
- $date->day,
- $date->month,
- $date->year
- );
- };
+# -----------------------------------------------------------------------------
+# If a secure resource is requested insecurely, redirect to the login page,
+# then return to the originally requrested resource upon successful login.
+# -----------------------------------------------------------------------------
+sub redirect_secure {
+ my ($self, $path) = @_;
+ my $login_page = sprintf('https://%s%s/login', $self->apache->hostname, $self->ctx->{opac_root});
+ my $redirect_to = uri_escape($self->apache->unparsed_uri);
+ $self->apache->print($self->cgi->redirect(-url => "$login_page?redirect_to=$redirect_to"));
+ return Apache2::Const::REDIRECT;
+}
- $ctx->{get_org_setting} = sub {
- my($org_id, $setting) = @_;
- $cache{org_settings}{$org_id} = {} unless $cache{org_settings}{$org_id};
- $cache{org_settings}{$org_id}{$setting} = $U->ou_ancestor_setting_value($org_id, $setting)
- unless exists $cache{org_settings}{$org_id}{$setting};
- return $cache{org_settings}{$org_id}{$setting};
- };
+# -----------------------------------------------------------------------------
+# Fall-through for loading a basic page
+# -----------------------------------------------------------------------------
+sub load_simple {
+ my ($self, $page) = @_;
+ $self->ctx->{page} = $page;
+ return Apache2::Const::OK;
}
-# context additions:
-# authtoken : string
-# user : au object
-# user_status : hash of user circ numbers
+# -----------------------------------------------------------------------------
+# Tests to see if the user is authenticated and sets some common context values
+# -----------------------------------------------------------------------------
sub load_common {
my $self = shift;
my $ctx = $self->ctx;
$ctx->{referer} = $self->cgi->referer;
+ $ctx->{path_info} = $self->cgi->path_info;
+ $ctx->{opac_root} = $ctx->{base_path} . "/opac"; # absolute base url
$ctx->{is_staff} = ($self->apache->headers_in->get('User-Agent') =~ 'oils_xulrunner');
+ $ctx->{home_page} = 'http://' . $self->apache->hostname . $self->ctx->{opac_root} . "/home";
if($e->authtoken($self->cgi->cookie('ses'))) {
if($e->checkauth) {
$ctx->{authtoken} = $e->authtoken;
+ $ctx->{authtime} = $e->authtime;
$ctx->{user} = $e->requestor;
+
$ctx->{user_stats} = $U->simplereq(
'open-ils.actor',
'open-ils.actor.user.opac.vital_stats',
return Apache2::Const::OK;
}
-sub load_home {
- my $self = shift;
- $self->ctx->{page} = 'home';
- return Apache2::Const::OK;
-}
-
+# -----------------------------------------------------------------------------
+# Log in and redirect to the redirect_to URL (or home)
+# -----------------------------------------------------------------------------
sub load_login {
my $self = shift;
my $cgi = $self->cgi;
+ my $ctx = $self->ctx;
- $self->ctx->{page} = 'login';
+ $ctx->{page} = 'login';
my $username = $cgi->param('username');
my $password = $cgi->param('password');
+ my $org_unit = $cgi->param('loc') || $ctx->{aou_tree}->()->id;
+ my $persist = $cgi->param('persist');
+ # initial log form only
return Apache2::Const::OK unless $username and $password;
my $seed = $U->simplereq(
'open-ils.auth',
- 'open-ils.auth.authenticate.init',
- $username);
+ 'open-ils.auth.authenticate.init', $username);
+
+ my $args = {
+ username => $username,
+ password => md5_hex($seed . md5_hex($password)),
+ type => ($persist) ? 'persist' : 'opac'
+ };
+
+ my $bc_regex = $ctx->{get_org_setting}->($org_unit, 'opac.barcode_regex');
+
+ $args->{barcode} = delete $args->{username}
+ if $bc_regex and $username =~ /$bc_regex/;
my $response = $U->simplereq(
- 'open-ils.auth',
- 'open-ils.auth.authenticate.complete',
- { username => $username,
- password => md5_hex($seed . md5_hex($password)),
- type => 'opac'
- }
- );
+ 'open-ils.auth', 'open-ils.auth.authenticate.complete', $args);
- # XXX check event, redirect as necessary
+ if($U->event_code($response)) {
+ # login failed, report the reason to the template
+ $ctx->{login_failed_event} = $response;
+ return Apache2::Const::OK;
+ }
+
+ # login succeeded, redirect as necessary
my $home = $self->apache->unparsed_uri;
$home =~ s/\/login/\/home/;
-path => '/',
-secure => 1,
-value => $response->{payload}->{authtoken},
- -expires => CORE::time + $response->{payload}->{authtime}
+ -expires => ($persist) ? CORE::time + $response->{payload}->{authtime} : undef
)
)
);
return Apache2::Const::REDIRECT;
}
+# -----------------------------------------------------------------------------
+# Log out and redirect to the home page
+# -----------------------------------------------------------------------------
sub load_logout {
my $self = shift;
- my $url = 'http://' . $self->apache->hostname . $self->ctx->{base_path} . "/opac/home";
-
$self->apache->print(
$self->cgi->redirect(
- -url => $url,
+ -url => $self->ctx->{home_page},
-cookie => $self->cgi->cookie(
-name => 'ses',
-path => '/',
return Apache2::Const::REDIRECT;
}
-# context additions:
-# page_size
-# hit_count
-# records : list of bre's and copy-count objects
-sub load_rresults {
- my $self = shift;
- my $cgi = $self->cgi;
- my $ctx = $self->ctx;
- my $e = $self->editor;
-
- $ctx->{page} = 'rresult';
- my $page = $cgi->param('page') || 0;
- my $facet = $cgi->param('facet');
- my $query = $cgi->param('query');
- my $limit = $cgi->param('limit') || 10; # TODO user settings
-
- my $loc = $cgi->param('loc') || $ctx->{aou_tree}->()->id;
- my $depth = defined $cgi->param('depth') ?
- $cgi->param('depth') : $ctx->{find_aou}->($loc)->ou_type->depth;
-
- my $args = {limit => $limit, offset => $page * $limit, org_unit => $loc, depth => $depth};
-
- $query = "$query $facet" if $facet; # TODO
- my $results;
-
- try {
-
- my $method = 'open-ils.search.biblio.multiclass.query';
- $method .= '.staff' if $ctx->{is_staff};
- $results = $U->simplereq('open-ils.search', $method, $args, $query, 1);
-
- } catch Error with {
- my $err = shift;
- $logger->error("multiclass search error: $err");
- $results = {count => 0, ids => []};
- };
-
- my $rec_ids = [map { $_->[0] } @{$results->{ids}}];
-
- $ctx->{records} = [];
- $ctx->{search_facets} = {};
- $ctx->{page_size} = $limit;
- $ctx->{hit_count} = $results->{count};
-
- return Apache2::Const::OK if @$rec_ids == 0;
-
- my $cstore1 = OpenSRF::AppSession->create('open-ils.cstore');
- my $bre_req = $cstore1->request(
- 'open-ils.cstore.direct.biblio.record_entry.search', {id => $rec_ids});
-
- my $search = OpenSRF::AppSession->create('open-ils.search');
- my $facet_req = $search->request('open-ils.search.facet_cache.retrieve', $results->{facet_key}, 10);
-
- unless($cache{cmf}) {
- $cache{cmf} = $e->search_config_metabib_field({id => {'!=' => undef}});
- $ctx->{metabib_field} = $cache{cmf};
- #$cache{cmc} = $e->search_config_metabib_class({name => {'!=' => undef}});
- #$ctx->{metabib_class} = $cache{cmc};
- }
-
- my @data;
- while(my $resp = $bre_req->recv) {
- my $bre = $resp->content;
-
- # XXX farm out to multiple cstore sessions before loop, then collect after
- my $copy_counts = $e->json_query(
- {from => ['asset.record_copy_count', 1, $bre->id, 0]})->[0];
-
- push(@data,
- {
- bre => $bre,
- marc_xml => XML::LibXML->new->parse_string($bre->marc),
- copy_counts => $copy_counts
- }
- );
- }
-
- $cstore1->kill_me;
-
- # shove recs into context in search results order
- for my $rec_id (@$rec_ids) {
- push(
- @{$ctx->{records}},
- grep { $_->{bre}->id == $rec_id } @data
- );
- }
-
- my $facets = $facet_req->gather(1);
-
- for my $cmf_id (keys %$facets) { # quick-n-dirty
- my ($cmf) = grep { $_->id eq $cmf_id } @{$cache{cmf}};
- $facets->{$cmf_id} = {cmf => $cmf, data => $facets->{$cmf_id}};
- }
- $ctx->{search_facets} = $facets;
-
- return Apache2::Const::OK;
-}
-
-# context additions:
-# record : bre object
-sub load_record {
- my $self = shift;
- $self->ctx->{page} = 'record';
-
- my $rec_id = $self->ctx->{page_args}->[0]
- or return Apache2::Const::HTTP_BAD_REQUEST;
-
- $self->ctx->{record} = $self->editor->retrieve_biblio_record_entry([
- $rec_id,
- {
- flesh => 2,
- flesh_fields => {
- bre => ['call_numbers'],
- acn => ['copies'] # limit, paging, etc.
- }
- }
- ]);
-
- $self->ctx->{marc_xml} = XML::LibXML->new->parse_string($self->ctx->{record}->marc);
-
- return Apache2::Const::OK;
-}
-
-# context additions:
-# user : au object, fleshed
-sub load_myopac {
- my $self = shift;
- $self->ctx->{page} = 'myopac';
-
- $self->ctx->{user} = $self->editor->retrieve_actor_user([
- $self->ctx->{user}->id,
- {
- flesh => 1,
- flesh_fields => {
- au => ['card']
- # ...
- }
- }
- ]);
-
- return Apache2::Const::OK;
-}
-
-
-sub fetch_user_holds {
- my $self = shift;
- my $hold_ids = shift;
- my $ids_only = shift;
- my $flesh = shift;
- my $limit = shift;
- my $offset = shift;
-
- my $e = $self->editor;
-
- my $circ = OpenSRF::AppSession->create('open-ils.circ');
-
- if(!$hold_ids) {
-
- $hold_ids = $circ->request(
- 'open-ils.circ.holds.id_list.retrieve.authoritative',
- $e->authtoken,
- $e->requestor->id
- )->gather(1);
-
- $hold_ids = [ grep { defined $_ } @$hold_ids[$offset..($offset + $limit - 1)] ] if $limit or $offset;
- }
-
-
- return $hold_ids if $ids_only or @$hold_ids == 0;
-
- my $args = {
- suppress_notices => 1,
- suppress_transits => 1,
- suppress_mvr => 1,
- suppress_patron_details => 1,
- include_bre => $flesh ? 1 : 0
- };
-
- # ----------------------------------------------------------------
- # batch version for testing; initial test show 40% speed
- # savings on larger sets (>20) of holds.
- # ----------------------------------------------------------------
- my $batch_size = 8;
- my $batch_idx = 0;
- my $mk_req_batch = sub {
- my @ses;
- my $top_idx = $batch_idx + $batch_size;
- while($batch_idx < $top_idx) {
- my $hold_id = $hold_ids->[$batch_idx++];
- last unless $hold_id;
- my $ses = OpenSRF::AppSession->create('open-ils.circ');
- my $req = $ses->request(
- 'open-ils.circ.hold.details.retrieve',
- $e->authtoken, $hold_id, $args);
- push(@ses, {ses => $ses, req => $req});
- }
- return @ses;
- };
-
- my $first = 1;
- my @collected;
- my @holds;
- my @ses;
- while(1) {
- @ses = $mk_req_batch->() if $first;
- last if $first and not @ses;
- if(@collected) {
- while(my $blob = pop(@collected)) {
- $blob->{marc_xml} = XML::LibXML->new->parse_string($blob->{hold}->{bre}->marc) if $flesh;
- push(@holds, $blob);
- }
- }
- for my $req_data (@ses) {
- push(@collected, {hold => $req_data->{req}->gather(1)});
- $req_data->{ses}->kill_me;
- }
- @ses = $mk_req_batch->();
- last unless @collected or @ses;
- $first = 0;
- }
- # ----------------------------------------------------------------
-
-=head
- my $req = $circ->request(
- # TODO .authoritative version is chewing up cstores
- # 'open-ils.circ.hold.details.batch.retrieve.authoritative',
- 'open-ils.circ.hold.details.batch.retrieve',
- $e->authtoken, $hold_ids, $args
- );
-
- my @holds;
- while(my $resp = $req->recv) {
- my $hold = $resp->content;
- push(@holds, {
- hold => $hold,
- marc_xml => ($flesh) ? XML::LibXML->new->parse_string($hold->{bre}->marc) : undef
- });
- }
-
- $circ->kill_me;
-=cut
-
- return \@holds;
-}
-
-sub handle_hold_update {
- my $self = shift;
- my $action = shift;
- my $e = $self->editor;
-
-
- my @hold_ids = $self->cgi->param('hold_id'); # for non-_all actions
- @hold_ids = @{$self->fetch_user_holds(undef, 1)} if $action =~ /_all/;
-
- my $circ = OpenSRF::AppSession->create('open-ils.circ');
-
- if($action =~ /cancel/) {
-
- for my $hold_id (@hold_ids) {
- my $resp = $circ->request(
- 'open-ils.circ.hold.cancel', $e->authtoken, $hold_id, 6 )->gather(1); # 6 == patron-cancelled-via-opac
- }
-
- } else {
-
- my $vlist = [];
- for my $hold_id (@hold_ids) {
- my $vals = {id => $hold_id};
-
- if($action =~ /activate/) {
- $vals->{frozen} = 'f';
- $vals->{thaw_date} = undef;
-
- } elsif($action =~ /suspend/) {
- $vals->{frozen} = 't';
- # $vals->{thaw_date} = TODO;
- }
- push(@$vlist, $vals);
- }
-
- $circ->request('open-ils.circ.hold.update.batch.atomic', $e->authtoken, undef, $vlist)->gather(1);
- }
-
- $circ->kill_me;
- return undef;
-}
-
-sub load_myopac_holds {
- my $self = shift;
- my $e = $self->editor;
- my $ctx = $self->ctx;
-
-
- my $limit = $self->cgi->param('limit') || 0;
- my $offset = $self->cgi->param('offset') || 0;
- my $action = $self->cgi->param('action') || '';
-
- $self->handle_hold_update($action) if $action;
-
- $ctx->{holds} = $self->fetch_user_holds(undef, 0, 1, $limit, $offset);
-
- return Apache2::Const::OK;
-}
-
-sub load_place_hold {
- my $self = shift;
- my $ctx = $self->ctx;
- my $e = $self->editor;
- my $cgi = $self->cgi;
- $self->ctx->{page} = 'place_hold';
-
- $ctx->{hold_target} = $cgi->param('hold_target');
- $ctx->{hold_type} = $cgi->param('hold_type');
- $ctx->{default_pickup_lib} = $e->requestor->home_ou; # XXX staff
-
- if($ctx->{hold_type} eq 'T') {
- $ctx->{record} = $e->retrieve_biblio_record_entry($ctx->{hold_target});
- }
- # ...
-
- $ctx->{marc_xml} = XML::LibXML->new->parse_string($ctx->{record}->marc);
-
- if(my $pickup_lib = $cgi->param('pickup_lib')) {
-
- my $args = {
- patronid => $e->requestor->id,
- titleid => $ctx->{hold_target}, # XXX
- pickup_lib => $pickup_lib,
- depth => 0, # XXX
- };
-
- my $allowed = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.title_hold.is_possible',
- $e->authtoken, $args
- );
-
- if($allowed->{success} == 1) {
- my $hold = Fieldmapper::action::hold_request->new;
-
- $hold->pickup_lib($pickup_lib);
- $hold->requestor($e->requestor->id);
- $hold->usr($e->requestor->id); # XXX staff
- $hold->target($ctx->{hold_target});
- $hold->hold_type($ctx->{hold_type});
- # frozen, expired, etc..
-
- my $stat = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.holds.create',
- $e->authtoken, $hold
- );
-
- if($stat and $stat > 0) {
-
- # if successful, return the user to the requesting page
- $self->apache->log->info("Redirecting back to " . $cgi->param('redirect_to'));
- $self->apache->print($cgi->redirect(-url => $cgi->param('redirect_to')));
- return Apache2::Const::REDIRECT;
-
- } else {
-
- $ctx->{hold_failed} = 1; # XXX process the events, etc
- }
- }
-
- # hold permit failed
- $logger->info('hold permit result ' . OpenSRF::Utils::JSON->perl2JSON($allowed));
- }
-
- return Apache2::Const::OK;
-}
-
-
-sub fetch_user_circs {
- my $self = shift;
- my $flesh = shift; # flesh bib data, etc.
- my $circ_ids = shift;
- my $limit = shift;
- my $offset = shift;
-
- my $e = $self->editor;
-
- my @circ_ids;
-
- if($circ_ids) {
- @circ_ids = @$circ_ids;
-
- } else {
-
- my $circ_data = $U->simplereq(
- 'open-ils.actor',
- 'open-ils.actor.user.checked_out',
- $e->authtoken,
- $e->requestor->id
- );
-
- @circ_ids = ( @{$circ_data->{overdue}}, @{$circ_data->{out}} );
-
- if($limit or $offset) {
- @circ_ids = grep { defined $_ } @circ_ids[0..($offset + $limit - 1)];
- }
- }
-
- return [] unless @circ_ids;
-
- my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
-
- my $qflesh = {
- flesh => 3,
- flesh_fields => {
- circ => ['target_copy'],
- acp => ['call_number'],
- acn => ['record']
- }
- };
-
- $e->xact_begin;
- my $circs = $e->search_action_circulation(
- [{id => \@circ_ids}, ($flesh) ? $qflesh : {}], {substream => 1});
-
- my @circs;
- for my $circ (@$circs) {
- push(@circs, {
- circ => $circ,
- marc_xml => ($flesh and $circ->target_copy->call_number->id != -1) ?
- XML::LibXML->new->parse_string($circ->target_copy->call_number->record->marc) :
- undef # pre-cat copy, use the dummy title/author instead
- });
- }
- $e->xact_rollback;
-
- # make sure the final list is in the correct order
- my @sorted_circs;
- for my $id (@circ_ids) {
- push(
- @sorted_circs,
- (grep { $_->{circ}->id == $id } @circs)
- );
- }
-
- return \@sorted_circs;
-}
-
-
-sub handle_circ_renew {
- my $self = shift;
- my $action = shift;
- my $ctx = $self->ctx;
-
- my @renew_ids = $self->cgi->param('circ');
-
- my $circs = $self->fetch_user_circs(0, ($action eq 'renew') ? [@renew_ids] : undef);
-
- # TODO: fire off renewal calls in batches to speed things up
- my @responses;
- for my $circ (@$circs) {
-
- my $evt = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.renew',
- $self->editor->authtoken,
- {
- patron_id => $self->editor->requestor->id,
- copy_id => $circ->{circ}->target_copy,
- opac_renewal => 1
- }
- );
-
- # TODO return these, then insert them into the circ data
- # blob that is shoved into the template for each circ
- # so the template won't have to match them
- push(@responses, {copy => $circ->{circ}->target_copy, evt => $evt});
- }
-
- return @responses;
-}
-
-
-sub load_myopac_circs {
- my $self = shift;
- my $e = $self->editor;
- my $ctx = $self->ctx;
-
- $ctx->{circs} = [];
- my $limit = $self->cgi->param('limit') || 0; # 0 == unlimited
- my $offset = $self->cgi->param('offset') || 0;
- my $action = $self->cgi->param('action') || '';
-
- # perform the renewal first if necessary
- my @results = $self->handle_circ_renew($action) if $action =~ /renew/;
-
- $ctx->{circs} = $self->fetch_user_circs(1, undef, $limit, $offset);
-
- my $success_renewals = 0;
- my $failed_renewals = 0;
- for my $data (@{$ctx->{circs}}) {
- my ($resp) = grep { $_->{copy} == $data->{circ}->target_copy->id } @results;
-
- if($resp) {
- my $evt = ref($resp->{evt}) eq 'ARRAY' ? $resp->{evt}->[0] : $resp->{evt};
- $data->{renewal_response} = $evt;
- $success_renewals++ if $evt->{textcode} eq 'SUCCESS';
- $failed_renewals++ if $evt->{textcode} ne 'SUCCESS';
- }
- }
-
- $ctx->{success_renewals} = $success_renewals;
- $ctx->{failed_renewals} = $failed_renewals;
-
- return Apache2::Const::OK;
-}
-
-sub load_myopac_fines {
- my $self = shift;
- my $e = $self->editor;
- my $ctx = $self->ctx;
- $ctx->{"fines"} = {
- "circulation" => [],
- "grocery" => [],
- "total_paid" => 0,
- "total_owed" => 0,
- "balance_owed" => 0
- };
-
- my $limit = $self->cgi->param('limit') || 0;
- my $offset = $self->cgi->param('offset') || 0;
-
- my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
-
- # TODO: This should really be a ML call, but the existing calls
- # return an excessive amount of data and don't offer streaming
-
- my %paging = ($limit or $offset) ? (limit => $limit, offset => $offset) : ();
-
- my $req = $cstore->request(
- 'open-ils.cstore.direct.money.open_billable_transaction_summary.search',
- {
- usr => $e->requestor->id,
- balance_owed => {'!=' => 0}
- },
- {
- flesh => 4,
- flesh_fields => {
- mobts => ['circulation', 'grocery'],
- mg => ['billings'],
- mb => ['btype'],
- circ => ['target_copy'],
- acp => ['call_number'],
- acn => ['record']
- },
- order_by => { mobts => 'xact_start' },
- %paging
- }
- );
-
- while(my $resp = $req->recv) {
- my $mobts = $resp->content;
- my $circ = $mobts->circulation;
-
- my $last_billing;
- if($mobts->grocery) {
- my @billings = sort { $a->billing_ts cmp $b->billing_ts } @{$mobts->grocery->billings};
- $last_billing = pop(@billings);
- }
-
- # XXX TODO switch to some money-safe non-fp library for math
- $ctx->{"fines"}->{$_} += $mobts->$_ for (
- qw/total_paid total_owed balance_owed/
- );
-
- push(
- @{$ctx->{"fines"}->{$mobts->grocery ? "grocery" : "circulation"}},
- {
- xact => $mobts,
- last_grocery_billing => $last_billing,
- marc_xml => ($mobts->xact_type ne 'circulation' or $circ->target_copy->call_number->id == -1) ?
- undef :
- XML::LibXML->new->parse_string($circ->target_copy->call_number->record->marc),
- }
- );
- }
-
- return Apache2::Const::OK;
-}
-
-sub load_myopac_update_email {
- my $self = shift;
- my $e = $self->editor;
- my $ctx = $self->ctx;
- my $email = $self->cgi->param('email') || '';
-
- unless($email =~ /.+\@.+\..+/) { # TODO better regex?
- $ctx->{invalid_email} = $email;
- return Apache2::Const::OK;
- }
-
- my $stat = $U->simplereq(
- 'open-ils.actor',
- 'open-ils.actor.user.email.update',
- $e->authtoken, $email);
-
- my $url = $self->apache->unparsed_uri;
- $url =~ s/update_email/main/;
- $self->apache->print($self->cgi->redirect(-url => $url));
-
- return Apache2::Const::REDIRECT;
-}
-
-sub load_myopac_bookbags {
- my $self = shift;
- my $e = $self->editor;
- my $ctx = $self->ctx;
- my $limit = $self->cgi->param('limit') || 0;
- my $offset = $self->cgi->param('offset') || 0;
-
- my $args = {order_by => {cbreb => 'name'}};
- $args->{limit} = $limit if $limit;
- $args->{offset} = $limit if $limit;
-
- $ctx->{bookbags} = $e->search_container_biblio_record_entry_bucket([
- {owner => $self->editor->requestor->id, btype => 'bookbag'},
- $args
- ]);
-
- return Apache2::Const::OK;
-}
-
-
1;
+
--- /dev/null
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+
+# context additions:
+# user : au object, fleshed
+sub load_myopac {
+ my $self = shift;
+ $self->ctx->{page} = 'myopac';
+
+ $self->ctx->{user} = $self->editor->retrieve_actor_user([
+ $self->ctx->{user}->id,
+ {
+ flesh => 1,
+ flesh_fields => {
+ au => ['card']
+ # ...
+ }
+ }
+ ]);
+
+ return Apache2::Const::OK;
+}
+
+
+sub fetch_user_holds {
+ my $self = shift;
+ my $hold_ids = shift;
+ my $ids_only = shift;
+ my $flesh = shift;
+ my $available = shift;
+ my $limit = shift;
+ my $offset = shift;
+
+ my $e = $self->editor;
+
+ my $circ = OpenSRF::AppSession->create('open-ils.circ');
+
+ if(!$hold_ids) {
+
+ $hold_ids = $circ->request(
+ 'open-ils.circ.holds.id_list.retrieve.authoritative',
+ $e->authtoken,
+ $e->requestor->id
+ )->gather(1);
+
+ $hold_ids = [ grep { defined $_ } @$hold_ids[$offset..($offset + $limit - 1)] ] if $limit or $offset;
+ }
+
+
+ return $hold_ids if $ids_only or @$hold_ids == 0;
+
+ my $args = {
+ suppress_notices => 1,
+ suppress_transits => 1,
+ suppress_mvr => 1,
+ suppress_patron_details => 1,
+ include_bre => $flesh ? 1 : 0
+ };
+
+ # ----------------------------------------------------------------
+ # Collect holds in batches of $batch_size for faster retrieval
+
+ my $batch_size = 8;
+ my $batch_idx = 0;
+ my $mk_req_batch = sub {
+ my @ses;
+ my $top_idx = $batch_idx + $batch_size;
+ while($batch_idx < $top_idx) {
+ my $hold_id = $hold_ids->[$batch_idx++];
+ last unless $hold_id;
+ my $ses = OpenSRF::AppSession->create('open-ils.circ');
+ my $req = $ses->request(
+ 'open-ils.circ.hold.details.retrieve',
+ $e->authtoken, $hold_id, $args);
+ push(@ses, {ses => $ses, req => $req});
+ }
+ return @ses;
+ };
+
+ my $first = 1;
+ my(@collected, @holds, @ses);
+
+ while(1) {
+ @ses = $mk_req_batch->() if $first;
+ last if $first and not @ses;
+
+ if(@collected) {
+ # If desired by the caller, filter any holds that are not available.
+ if ($available) {
+ @collected = grep { $_->{hold}->{status} == 4 } @collected;
+ }
+ while(my $blob = pop(@collected)) {
+ $blob->{marc_xml} = XML::LibXML->new->parse_string($blob->{hold}->{bre}->marc) if $flesh;
+ push(@holds, $blob);
+ }
+ }
+
+ for my $req_data (@ses) {
+ push(@collected, {hold => $req_data->{req}->gather(1)});
+ $req_data->{ses}->kill_me;
+ }
+
+ @ses = $mk_req_batch->();
+ last unless @collected or @ses;
+ $first = 0;
+ }
+
+ # put the holds back into the original server sort order
+ my @sorted;
+ for my $id (@$hold_ids) {
+ push @sorted, grep { $_->{hold}->{hold}->id == $id } @holds;
+ }
+
+ return \@sorted;
+}
+
+sub handle_hold_update {
+ my $self = shift;
+ my $action = shift;
+ my $e = $self->editor;
+
+
+ my @hold_ids = $self->cgi->param('hold_id'); # for non-_all actions
+ @hold_ids = @{$self->fetch_user_holds(undef, 1)} if $action =~ /_all/;
+
+ my $circ = OpenSRF::AppSession->create('open-ils.circ');
+
+ if($action =~ /cancel/) {
+
+ for my $hold_id (@hold_ids) {
+ my $resp = $circ->request(
+ 'open-ils.circ.hold.cancel', $e->authtoken, $hold_id, 6 )->gather(1); # 6 == patron-cancelled-via-opac
+ }
+
+ } else {
+
+ my $vlist = [];
+ for my $hold_id (@hold_ids) {
+ my $vals = {id => $hold_id};
+
+ if($action =~ /activate/) {
+ $vals->{frozen} = 'f';
+ $vals->{thaw_date} = undef;
+
+ } elsif($action =~ /suspend/) {
+ $vals->{frozen} = 't';
+ # $vals->{thaw_date} = TODO;
+ }
+ push(@$vlist, $vals);
+ }
+
+ $circ->request('open-ils.circ.hold.update.batch.atomic', $e->authtoken, undef, $vlist)->gather(1);
+ }
+
+ $circ->kill_me;
+ return undef;
+}
+
+sub load_myopac_holds {
+ my $self = shift;
+ my $e = $self->editor;
+ my $ctx = $self->ctx;
+
+
+ my $limit = $self->cgi->param('limit') || 0;
+ my $offset = $self->cgi->param('offset') || 0;
+ my $action = $self->cgi->param('action') || '';
+ my $available = int($self->cgi->param('available') || 0);
+
+ $self->handle_hold_update($action) if $action;
+
+ $ctx->{holds} = $self->fetch_user_holds(undef, 0, 1, $available, $limit, $offset);
+
+ return Apache2::Const::OK;
+}
+
+sub load_place_hold {
+ my $self = shift;
+ my $ctx = $self->ctx;
+ my $e = $self->editor;
+ my $cgi = $self->cgi;
+ $self->ctx->{page} = 'place_hold';
+
+ $ctx->{hold_target} = $cgi->param('hold_target');
+ $ctx->{hold_type} = $cgi->param('hold_type');
+ $ctx->{default_pickup_lib} = $e->requestor->home_ou; # XXX staff
+
+ if($ctx->{hold_type} eq 'T') {
+ $ctx->{record} = $e->retrieve_biblio_record_entry($ctx->{hold_target});
+ }
+ # ...
+
+ $ctx->{marc_xml} = XML::LibXML->new->parse_string($ctx->{record}->marc);
+
+ if(my $pickup_lib = $cgi->param('pickup_lib')) {
+
+ my $args = {
+ patronid => $e->requestor->id,
+ titleid => $ctx->{hold_target}, # XXX
+ pickup_lib => $pickup_lib,
+ depth => 0, # XXX
+ };
+
+ my $allowed = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.title_hold.is_possible',
+ $e->authtoken, $args
+ );
+
+ if($allowed->{success} == 1) {
+ my $hold = Fieldmapper::action::hold_request->new;
+
+ $hold->pickup_lib($pickup_lib);
+ $hold->requestor($e->requestor->id);
+ $hold->usr($e->requestor->id); # XXX staff
+ $hold->target($ctx->{hold_target});
+ $hold->hold_type($ctx->{hold_type});
+ # frozen, expired, etc..
+
+ my $stat = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.holds.create',
+ $e->authtoken, $hold
+ );
+
+ if($stat and $stat > 0) {
+ # if successful, return the user to the requesting page
+ $self->apache->log->info("Redirecting back to " . $cgi->param('redirect_to'));
+ $self->apache->print($cgi->redirect(-url => $cgi->param('redirect_to')));
+ return Apache2::Const::REDIRECT;
+
+ } else {
+ $ctx->{hold_failed} = 1;
+ }
+ } else { # hold *check* failed
+ $ctx->{hold_failed} = 1; # XXX process the events, etc
+ $ctx->{hold_failed_event} = $allowed->{last_event};
+ }
+
+ # hold permit failed
+ $logger->info('hold permit result ' . OpenSRF::Utils::JSON->perl2JSON($allowed));
+ }
+
+ return Apache2::Const::OK;
+}
+
+
+sub fetch_user_circs {
+ my $self = shift;
+ my $flesh = shift; # flesh bib data, etc.
+ my $circ_ids = shift;
+ my $limit = shift;
+ my $offset = shift;
+
+ my $e = $self->editor;
+
+ my @circ_ids;
+
+ if($circ_ids) {
+ @circ_ids = @$circ_ids;
+
+ } else {
+
+ my $circ_data = $U->simplereq(
+ 'open-ils.actor',
+ 'open-ils.actor.user.checked_out',
+ $e->authtoken,
+ $e->requestor->id
+ );
+
+ @circ_ids = ( @{$circ_data->{overdue}}, @{$circ_data->{out}} );
+
+ if($limit or $offset) {
+ @circ_ids = grep { defined $_ } @circ_ids[0..($offset + $limit - 1)];
+ }
+ }
+
+ return [] unless @circ_ids;
+
+ my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
+
+ my $qflesh = {
+ flesh => 3,
+ flesh_fields => {
+ circ => ['target_copy'],
+ acp => ['call_number'],
+ acn => ['record']
+ }
+ };
+
+ $e->xact_begin;
+ my $circs = $e->search_action_circulation(
+ [{id => \@circ_ids}, ($flesh) ? $qflesh : {}], {substream => 1});
+
+ my @circs;
+ for my $circ (@$circs) {
+ push(@circs, {
+ circ => $circ,
+ marc_xml => ($flesh and $circ->target_copy->call_number->id != -1) ?
+ XML::LibXML->new->parse_string($circ->target_copy->call_number->record->marc) :
+ undef # pre-cat copy, use the dummy title/author instead
+ });
+ }
+ $e->xact_rollback;
+
+ # make sure the final list is in the correct order
+ my @sorted_circs;
+ for my $id (@circ_ids) {
+ push(
+ @sorted_circs,
+ (grep { $_->{circ}->id == $id } @circs)
+ );
+ }
+
+ return \@sorted_circs;
+}
+
+
+sub handle_circ_renew {
+ my $self = shift;
+ my $action = shift;
+ my $ctx = $self->ctx;
+
+ my @renew_ids = $self->cgi->param('circ');
+
+ my $circs = $self->fetch_user_circs(0, ($action eq 'renew') ? [@renew_ids] : undef);
+
+ # TODO: fire off renewal calls in batches to speed things up
+ my @responses;
+ for my $circ (@$circs) {
+
+ my $evt = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.renew',
+ $self->editor->authtoken,
+ {
+ patron_id => $self->editor->requestor->id,
+ copy_id => $circ->{circ}->target_copy,
+ opac_renewal => 1
+ }
+ );
+
+ # TODO return these, then insert them into the circ data
+ # blob that is shoved into the template for each circ
+ # so the template won't have to match them
+ push(@responses, {copy => $circ->{circ}->target_copy, evt => $evt});
+ }
+
+ return @responses;
+}
+
+
+sub load_myopac_circs {
+ my $self = shift;
+ my $e = $self->editor;
+ my $ctx = $self->ctx;
+
+ $ctx->{circs} = [];
+ my $limit = $self->cgi->param('limit') || 0; # 0 == unlimited
+ my $offset = $self->cgi->param('offset') || 0;
+ my $action = $self->cgi->param('action') || '';
+
+ # perform the renewal first if necessary
+ my @results = $self->handle_circ_renew($action) if $action =~ /renew/;
+
+ $ctx->{circs} = $self->fetch_user_circs(1, undef, $limit, $offset);
+
+ my $success_renewals = 0;
+ my $failed_renewals = 0;
+ for my $data (@{$ctx->{circs}}) {
+ my ($resp) = grep { $_->{copy} == $data->{circ}->target_copy->id } @results;
+
+ if($resp) {
+ my $evt = ref($resp->{evt}) eq 'ARRAY' ? $resp->{evt}->[0] : $resp->{evt};
+ $data->{renewal_response} = $evt;
+ $success_renewals++ if $evt->{textcode} eq 'SUCCESS';
+ $failed_renewals++ if $evt->{textcode} ne 'SUCCESS';
+ }
+ }
+
+ $ctx->{success_renewals} = $success_renewals;
+ $ctx->{failed_renewals} = $failed_renewals;
+
+ return Apache2::Const::OK;
+}
+
+sub load_myopac_fines {
+ my $self = shift;
+ my $e = $self->editor;
+ my $ctx = $self->ctx;
+ $ctx->{"fines"} = {
+ "circulation" => [],
+ "grocery" => [],
+ "total_paid" => 0,
+ "total_owed" => 0,
+ "balance_owed" => 0
+ };
+
+ my $limit = $self->cgi->param('limit') || 0;
+ my $offset = $self->cgi->param('offset') || 0;
+
+ my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
+
+ # TODO: This should really be a ML call, but the existing calls
+ # return an excessive amount of data and don't offer streaming
+
+ my %paging = ($limit or $offset) ? (limit => $limit, offset => $offset) : ();
+
+ my $req = $cstore->request(
+ 'open-ils.cstore.direct.money.open_billable_transaction_summary.search',
+ {
+ usr => $e->requestor->id,
+ balance_owed => {'!=' => 0}
+ },
+ {
+ flesh => 4,
+ flesh_fields => {
+ mobts => ['circulation', 'grocery'],
+ mg => ['billings'],
+ mb => ['btype'],
+ circ => ['target_copy'],
+ acp => ['call_number'],
+ acn => ['record']
+ },
+ order_by => { mobts => 'xact_start' },
+ %paging
+ }
+ );
+
+ while(my $resp = $req->recv) {
+ my $mobts = $resp->content;
+ my $circ = $mobts->circulation;
+
+ my $last_billing;
+ if($mobts->grocery) {
+ my @billings = sort { $a->billing_ts cmp $b->billing_ts } @{$mobts->grocery->billings};
+ $last_billing = pop(@billings);
+ }
+
+ # XXX TODO switch to some money-safe non-fp library for math
+ $ctx->{"fines"}->{$_} += $mobts->$_ for (
+ qw/total_paid total_owed balance_owed/
+ );
+
+ push(
+ @{$ctx->{"fines"}->{$mobts->grocery ? "grocery" : "circulation"}},
+ {
+ xact => $mobts,
+ last_grocery_billing => $last_billing,
+ marc_xml => ($mobts->xact_type ne 'circulation' or $circ->target_copy->call_number->id == -1) ?
+ undef :
+ XML::LibXML->new->parse_string($circ->target_copy->call_number->record->marc),
+ }
+ );
+ }
+
+ return Apache2::Const::OK;
+}
+
+sub load_myopac_update_email {
+ my $self = shift;
+ my $e = $self->editor;
+ my $ctx = $self->ctx;
+ my $email = $self->cgi->param('email') || '';
+
+ unless($email =~ /.+\@.+\..+/) { # TODO better regex?
+ $ctx->{invalid_email} = $email;
+ return Apache2::Const::OK;
+ }
+
+ my $stat = $U->simplereq(
+ 'open-ils.actor',
+ 'open-ils.actor.user.email.update',
+ $e->authtoken, $email);
+
+ my $url = $self->apache->unparsed_uri;
+ $url =~ s/update_email/main/;
+ $self->apache->print($self->cgi->redirect(-url => $url));
+
+ return Apache2::Const::REDIRECT;
+}
+
+sub load_myopac_bookbags {
+ my $self = shift;
+ my $e = $self->editor;
+ my $ctx = $self->ctx;
+ my $limit = $self->cgi->param('limit') || 0;
+ my $offset = $self->cgi->param('offset') || 0;
+
+ my $args = {order_by => {cbreb => 'name'}};
+ $args->{limit} = $limit if $limit;
+ $args->{offset} = $limit if $limit;
+
+ $ctx->{bookbags} = $e->search_container_biblio_record_entry_bucket([
+ {owner => $self->editor->requestor->id, btype => 'bookbag'},
+ $args
+ ]);
+
+ return Apache2::Const::OK;
+}
+
+
+1
--- /dev/null
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+# context additions:
+# record : bre object
+sub load_record {
+ my $self = shift;
+ $self->ctx->{page} = 'record';
+
+ my $org = $self->cgi->param('loc') || $self->ctx->{aou_tree}->()->id;
+ my $depth = $self->cgi->param('depth') || 0;
+ my $copy_limit = $self->cgi->param('copy_limit');
+ my $copy_offset = $self->cgi->param('copy_offset');
+
+ my $rec_id = $self->ctx->{page_args}->[0]
+ or return Apache2::Const::HTTP_BAD_REQUEST;
+
+ # run copy retrieval in parallel to bib retrieval
+ my $copy_rec = OpenSRF::AppSession->create('open-ils.cstore')->request(
+ 'open-ils.cstore.json_query.atomic',
+ $self->mk_copy_query($rec_id, $org, $depth, $copy_limit, $copy_offset));
+
+ $self->ctx->{record} = $self->editor->retrieve_biblio_record_entry($rec_id);
+ $self->ctx->{marc_xml} = XML::LibXML->new->parse_string($self->ctx->{record}->marc);
+
+ $self->ctx->{copies} = $copy_rec->gather(1);
+
+ return Apache2::Const::OK;
+}
+
+sub mk_copy_query {
+ my $self = shift;
+ my $rec_id = shift;
+ my $org = shift;
+ my $depth = shift;
+ my $copy_limit = shift || 20;
+ my $copy_offset = shift || 0;
+
+ my $query = {
+ select => {
+ acp => ['id', 'barcode', 'circ_lib', 'create_date', 'age_protect', 'holdable'],
+ acpl => [
+ {column => 'name', alias => 'copy_location'},
+ {column => 'holdable', alias => 'location_holdable'}
+ ],
+ ccs => [
+ {column => 'name', alias => 'copy_status'},
+ {column => 'holdable', alias => 'status_holdable'}
+ ],
+ acn => [
+ {column => 'label', alias => 'call_number_label'},
+ {column => 'id', alias => 'call_number'}
+ ],
+ circ => ['due_date'],
+ },
+ from => {
+ acp => {
+ acn => {},
+ acpl => {},
+ ccs => {},
+ circ => {type => 'left'},
+ aou => {}
+ }
+ },
+ where => {
+ '+acp' => {
+ deleted => 'f',
+ call_number => {
+ in => {
+ select => {acn => ['id']},
+ from => 'acn',
+ where => {record => $rec_id}
+ }
+ },
+ circ_lib => {
+ in => {
+ select => {aou => [{
+ column => 'id',
+ transform => 'actor.org_unit_descendants',
+ result_field => 'id',
+ params => [$depth]
+ }]},
+ from => 'aou',
+ where => {id => $org}
+ }
+ }
+ },
+ '+acn' => {deleted => 'f'},
+ '+circ' => {checkin_time => undef}
+ },
+
+ # Order is: copies with circ_lib=org, followed by circ_lib name, followed by call_number label
+ order_by => [
+ {class => 'aou', field => 'name'},
+ {class => 'acn', field => 'label'}
+ ],
+
+ limit => $copy_limit,
+ offset => $copy_offset
+ };
+
+ # Filter hidden items if this is the public catalog
+ unless($self->ctx->{is_staff}) {
+ $query->{where}->{'+acp'}->{opac_visible} = 't';
+ $query->{where}->{'+acpl'}->{opac_visible} = 't';
+ $query->{where}->{'+ccs'}->{opac_visible} = 't';
+ }
+
+ return $query;
+ #return $self->editor->json_query($query);
+}
+
+sub mk_marc_html {
+ my($self, $rec_id) = @_;
+
+ $self->ctx->{marc_html} = $U->simplereq(
+ 'open-ils.search', 'open-ils.search.biblio.record.html', $rec_id);
+}
+
+1;
--- /dev/null
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+
+# context additions:
+# page_size
+# hit_count
+# records : list of bre's and copy-count objects
+sub load_rresults {
+ my $self = shift;
+ my $cgi = $self->cgi;
+ my $ctx = $self->ctx;
+ my $e = $self->editor;
+
+ $ctx->{page} = 'rresult';
+ my $page = $cgi->param('page') || 0;
+ my $item_type = $cgi->param('item_type');
+ my $facet = $cgi->param('facet');
+ my $query = $cgi->param('query');
+ my $limit = $cgi->param('limit') || 10; # TODO user settings
+
+ my $loc = $cgi->param('loc') || $ctx->{aou_tree}->()->id;
+ my $depth = defined $cgi->param('depth') ?
+ $cgi->param('depth') : $ctx->{find_aou}->($loc)->ou_type->depth;
+
+ my $args = {
+ limit => $limit, offset => $page * $limit,
+ org_unit => $loc, depth => $depth, $item_type ? (item_type => [$item_type]) : ()
+ };
+
+ $query = "$query $facet" if $facet; # TODO
+ my $results;
+
+ try {
+
+ my $method = 'open-ils.search.biblio.multiclass.query';
+ $method .= '.staff' if $ctx->{is_staff};
+ $results = $U->simplereq('open-ils.search', $method, $args, $query, 1);
+
+ } catch Error with {
+ my $err = shift;
+ $logger->error("multiclass search error: $err");
+ $results = {count => 0, ids => []};
+ };
+
+ my $rec_ids = [map { $_->[0] } @{$results->{ids}}];
+
+ $ctx->{records} = [];
+ $ctx->{search_facets} = {};
+ $ctx->{page_size} = $limit;
+ $ctx->{hit_count} = $results->{count};
+
+ return Apache2::Const::OK if @$rec_ids == 0;
+
+ my $cstore1 = OpenSRF::AppSession->create('open-ils.cstore');
+ my $bre_req = $cstore1->request(
+ 'open-ils.cstore.direct.biblio.record_entry.search', {id => $rec_ids});
+
+ my $search = OpenSRF::AppSession->create('open-ils.search');
+ my $facet_req = $search->request('open-ils.search.facet_cache.retrieve', $results->{facet_key}, 10);
+
+ my @data;
+ while(my $resp = $bre_req->recv) {
+ my $bre = $resp->content;
+
+ # XXX farm out to multiple cstore sessions before loop, then collect after
+ my $copy_counts = $e->json_query(
+ {from => ['asset.record_copy_count', 1, $bre->id, 0]})->[0];
+
+ push(@data,
+ {
+ bre => $bre,
+ marc_xml => XML::LibXML->new->parse_string($bre->marc),
+ copy_counts => $copy_counts
+ }
+ );
+ }
+
+ $cstore1->kill_me;
+
+ # shove recs into context in search results order
+ for my $rec_id (@$rec_ids) {
+ push(
+ @{$ctx->{records}},
+ grep { $_->{bre}->id == $rec_id } @data
+ );
+ }
+
+ my $facets = $facet_req->gather(1);
+
+ $facets->{$_} = {cmf => $ctx->{find_cmf}->($_), data => $facets->{$_}} for keys %$facets; # quick-n-dirty
+ $ctx->{search_facets} = $facets;
+
+ return Apache2::Const::OK;
+}
+
+1;
--- /dev/null
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+my %cache = (
+ map => {aou => {}}, # others added dynamically as needed
+ list => {},
+ org_settings => {}
+);
+
+sub init_ro_object_cache {
+ my $self = shift;
+ my $e = $self->editor;
+ my $ctx = $self->ctx;
+
+ # fetch-on-demand-and-cache subs for commonly used public data
+ my @public_classes = qw/ccs aout cifm citm clm cmf/;
+
+ for my $hint (@public_classes) {
+
+ my ($class) = grep {
+ $Fieldmapper::fieldmap->{$_}->{hint} eq $hint
+ } keys %{ $Fieldmapper::fieldmap };
+
+ my $ident_field = $Fieldmapper::fieldmap->{$class}->{identity};
+
+ $class =~ s/Fieldmapper:://o;
+ $class =~ s/::/_/g;
+
+ # copy statuses
+ my $list_key = $hint . '_list';
+ my $find_key = "find_$hint";
+
+ $ctx->{$list_key} = sub {
+ my $method = "retrieve_all_$class";
+ $cache{list}{$hint} = $e->$method() unless $cache{list}{$hint};
+ return $cache{list}{$hint};
+ };
+
+ $cache{map}{$hint} = {} unless $cache{map}{$hint};
+
+ $ctx->{$find_key} = sub {
+ my $id = shift;
+ return $cache{map}{$hint}{$id} if $cache{map}{$hint}{$id};
+ ($cache{map}{$hint}{$id}) = grep { $_->$ident_field eq $id } @{$ctx->{$list_key}->()};
+ return $cache{map}{$hint}{$id};
+ };
+
+ }
+
+ $ctx->{aou_tree} = sub {
+
+ # fetch the org unit tree
+ unless($cache{aou_tree}) {
+ my $tree = $e->search_actor_org_unit([
+ { parent_ou => undef},
+ { flesh => -1,
+ flesh_fields => {aou => ['children']},
+ order_by => {aou => 'name'}
+ }
+ ])->[0];
+
+ # flesh the org unit type for each org unit
+ # and simultaneously set the id => aou map cache
+ sub flesh_aout {
+ my $node = shift;
+ my $ctx = shift;
+ $node->ou_type( $ctx->{find_aout}->($node->ou_type) );
+ $cache{map}{aou}{$node->id} = $node;
+ flesh_aout($_, $ctx) foreach @{$node->children};
+ };
+ flesh_aout($tree, $ctx);
+
+ $cache{aou_tree} = $tree;
+ }
+
+ return $cache{aou_tree};
+ };
+
+ # Add a special handler for the tree-shaped org unit cache
+ $ctx->{find_aou} = sub {
+ my $org_id = shift;
+ $ctx->{aou_tree}->(); # force the org tree to load
+ return $cache{map}{aou}{$org_id};
+ };
+
+ # turns an ISO date into something TT can understand
+ $ctx->{parse_datetime} = sub {
+ my $date = shift;
+ $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date));
+ return sprintf(
+ "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
+ $date->hour,
+ $date->minute,
+ $date->second,
+ $date->day,
+ $date->month,
+ $date->year
+ );
+ };
+
+ # retrieve and cache org unit setting values
+ $ctx->{get_org_setting} = sub {
+ my($org_id, $setting) = @_;
+ $cache{org_settings}{$org_id} = {} unless $cache{org_settings}{$org_id};
+ $cache{org_settings}{$org_id}{$setting} = $U->ou_ancestor_setting_value($org_id, $setting)
+ unless exists $cache{org_settings}{$org_id}{$setting};
+ return $cache{org_settings}{$org_id}{$setting};
+ };
+}
+
+
+1;
my $tt = Template->new({
OUTPUT => ($as_xml) ? sub { parse_as_xml($r, $ctx, @_); } : $r,
INCLUDE_PATH => $ctx->{template_paths},
- DEBUG => $ctx->{debug_template}
+ DEBUG => $ctx->{debug_template},
+ PLUGINS => {EGI18N => 'OpenILS::WWW::EGWeb::I18NFilter'}
});
unless($tt->process($template, {ctx => $ctx, l => set_text_handler($ctx, $r)})) {
$lh_cache{$locale} = $lh_cache{'en_US'};
}
- return sub { return $lh_cache{$locale}->maketext(@_); };
+ return $OpenILS::WWW::EGWeb::I18NFilter::maketext =
+ sub { return $lh_cache{$locale}->maketext(@_); };
}
my $ctx = shift;
my $locales = $ctx->{locales};
+ $locales->{en_US} = {} unless exists $locales->{en_US};
+
for my $lang (keys %$locales) {
my $messages = $locales->{$lang};
$messages = '' if ref $messages; # empty {}
--- /dev/null
+package OpenILS::WWW::EGWeb::I18NFilter;
+use Template::Plugin::Filter;
+use base qw(Template::Plugin::Filter);
+our $DYNAMIC = 1;
+our $maketext;
+
+sub filter {
+ my ($self, $text, $args) = @_;
+ return $maketext->($text, @$args);
+}
+
+sub init {
+ my $self = shift;
+ $self->install_filter('l');
+ return $self;
+}
+
+1;
+
install_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-INSERT INTO config.upgrade_log (version) VALUES ('0481'); -- dbs
+INSERT INTO config.upgrade_log (version) VALUES ('0486'); -- berick
CREATE TABLE config.bib_source (
id SERIAL PRIMARY KEY,
FROM asset.call_number cn
JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
-CREATE OR REPLACE FUNCTION asset.opac_ou_record_copy_count (org INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.opac_ou_record_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
DECLARE
ans RECORD;
trans INT;
BEGIN
- SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
RETURN QUERY
trans
FROM
actor.org_unit_descendants(ans.id) d
- JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
+ JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
JOIN asset.copy cp ON (cp.id = av.id)
GROUP BY 1,2,6;
END;
$f$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION asset.opac_lasso_record_copy_count (i_lasso INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.opac_lasso_record_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
DECLARE
ans RECORD;
trans INT;
BEGIN
- SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
RETURN QUERY
trans
FROM
actor.org_unit_descendants(ans.id) d
- JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
+ JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
JOIN asset.copy cp ON (cp.id = av.id)
GROUP BY 1,2,6;
END;
$f$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION asset.staff_ou_record_copy_count (org INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.staff_ou_record_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
DECLARE
ans RECORD;
trans INT;
BEGIN
- SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
RETURN QUERY
FROM
actor.org_unit_descendants(ans.id) d
JOIN asset.copy cp ON (cp.circ_lib = d.id)
- JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
+ JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number)
GROUP BY 1,2,6;
IF NOT FOUND THEN
END;
$f$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION asset.staff_lasso_record_copy_count (i_lasso INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.staff_lasso_record_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
DECLARE
ans RECORD;
trans INT;
BEGIN
- SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
RETURN QUERY
FROM
actor.org_unit_descendants(ans.id) d
JOIN asset.copy cp ON (cp.circ_lib = d.id)
- JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
+ JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number)
GROUP BY 1,2,6;
IF NOT FOUND THEN
END;
$f$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION asset.record_copy_count ( place INT, record BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.record_copy_count ( place INT, rid BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
BEGIN
IF staff IS TRUE THEN
IF place > 0 THEN
- RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
+ RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, rid );
ELSE
- RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
+ RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, rid );
END IF;
ELSE
IF place > 0 THEN
- RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
+ RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, rid );
ELSE
- RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
+ RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, rid );
END IF;
END IF;
END;
$f$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION asset.opac_ou_metarecord_copy_count (org INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.opac_ou_metarecord_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
DECLARE
ans RECORD;
trans INT;
BEGIN
- SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
RETURN QUERY
trans
FROM
actor.org_unit_descendants(ans.id) d
- JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
+ JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
JOIN asset.copy cp ON (cp.id = av.id)
JOIN metabib.metarecord_source_map m ON (m.source = av.record)
GROUP BY 1,2,6;
END;
$f$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION asset.opac_lasso_metarecord_copy_count (i_lasso INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.opac_lasso_metarecord_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
DECLARE
ans RECORD;
trans INT;
BEGIN
- SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
RETURN QUERY
trans
FROM
actor.org_unit_descendants(ans.id) d
- JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
+ JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
JOIN asset.copy cp ON (cp.id = av.id)
JOIN metabib.metarecord_source_map m ON (m.source = av.record)
GROUP BY 1,2,6;
END;
$f$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION asset.staff_ou_metarecord_copy_count (org INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.staff_ou_metarecord_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
DECLARE
ans RECORD;
trans INT;
BEGIN
- SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
RETURN QUERY
FROM
actor.org_unit_descendants(ans.id) d
JOIN asset.copy cp ON (cp.circ_lib = d.id)
- JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
+ JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number)
JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
GROUP BY 1,2,6;
END;
$f$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION asset.staff_lasso_metarecord_copy_count (i_lasso INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.staff_lasso_metarecord_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
DECLARE
ans RECORD;
trans INT;
BEGIN
- SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
RETURN QUERY
FROM
actor.org_unit_descendants(ans.id) d
JOIN asset.copy cp ON (cp.circ_lib = d.id)
- JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
+ JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number)
JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
GROUP BY 1,2,6;
END;
$f$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION asset.metarecord_copy_count ( place INT, record BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+CREATE OR REPLACE FUNCTION asset.metarecord_copy_count ( place INT, rid BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
BEGIN
IF staff IS TRUE THEN
IF place > 0 THEN
- RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
+ RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, rid );
ELSE
- RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
+ RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, rid );
END IF;
ELSE
IF place > 0 THEN
- RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
+ RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, rid );
ELSE
- RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
+ RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, rid );
END IF;
END IF;
cc_processor TEXT,
cc_first_name TEXT,
cc_last_name TEXT,
+ cc_order_number TEXT,
expire_month INT,
expire_year INT,
approval_code TEXT
CREATE TABLE config.circ_matrix_matchpoint (
id SERIAL PRIMARY KEY,
active BOOL NOT NULL DEFAULT TRUE,
+ -- Match Fields
org_unit INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best"
grp INT NOT NULL REFERENCES permission.grp_tree (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top applicable group from the group tree; will need descendents and prox functions for filtering
circ_modifier TEXT REFERENCES config.circ_modifier (code) DEFERRABLE INITIALLY DEFERRED,
is_renewal BOOL,
usr_age_lower_bound INTERVAL,
usr_age_upper_bound INTERVAL,
+ -- "Result" Fields
circulate BOOL NOT NULL DEFAULT TRUE, -- Hard "can't circ" flag requiring an override
duration_rule INT NOT NULL REFERENCES config.rule_circ_duration (id) DEFERRABLE INITIALLY DEFERRED,
recurring_fine_rule INT NOT NULL REFERENCES config.rule_recurring_fine (id) DEFERRABLE INITIALLY DEFERRED,
hard_due_date INT REFERENCES config.hard_due_date (id) DEFERRABLE INITIALLY DEFERRED,
script_test TEXT, -- javascript source
total_copy_hold_ratio FLOAT,
- available_copy_hold_ratio FLOAT,
- CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
- grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
- juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
- copy_owning_lib
- )
+ available_copy_hold_ratio FLOAT
);
+-- Nulls don't count for a constraint match, so we have to coalesce them into something that does.
+CREATE UNIQUE INDEX ccmm_once_per_paramset ON config.circ_matrix_matchpoint (org_unit, grp, COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_vr_format, ''), COALESCE(copy_circ_lib::TEXT, ''), COALESCE(copy_owning_lib::TEXT, ''), COALESCE(user_home_ou::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(is_renewal::TEXT, ''), COALESCE(usr_age_lower_bound::TEXT, ''), COALESCE(usr_age_upper_bound::TEXT, '')) WHERE active;
-- Tests for max items out by circ_modifier
CREATE TABLE config.circ_matrix_circ_mod_test (
matchpoint config.circ_matrix_matchpoint%ROWTYPE;
weights config.circ_matrix_weights%ROWTYPE;
user_age INTERVAL;
- denominator INT;
+ denominator NUMERIC(6,2);
BEGIN
SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
-- No weights? Bad admin! Defaults to handle that anyway.
IF weights.id IS NULL THEN
- weights.grp := 11;
- weights.org_unit := 10;
- weights.circ_modifier := 5;
- weights.marc_type := 4;
- weights.marc_form := 3;
- weights.marc_vr_format := 2;
- weights.copy_circ_lib := 8;
- weights.copy_owning_lib := 8;
- weights.user_home_ou := 8;
- weights.ref_flag := 1;
- weights.juvenile_flag := 6;
- weights.is_renewal := 7;
- weights.usr_age_lower_bound := 0;
- weights.usr_age_upper_bound := 0;
+ weights.grp := 11.0;
+ weights.org_unit := 10.0;
+ weights.circ_modifier := 5.0;
+ weights.marc_type := 4.0;
+ weights.marc_form := 3.0;
+ weights.marc_vr_format := 2.0;
+ weights.copy_circ_lib := 8.0;
+ weights.copy_owning_lib := 8.0;
+ weights.user_home_ou := 8.0;
+ weights.ref_flag := 1.0;
+ weights.juvenile_flag := 6.0;
+ weights.is_renewal := 7.0;
+ weights.usr_age_lower_bound := 0.0;
+ weights.usr_age_upper_bound := 0.0;
END IF;
-- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
ORDER BY
-- Permission Groups
- CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0 END +
+ CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
-- Org Units
- CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0 END +
- CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0 END +
- CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0 END +
- CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
-- Circ Type -- Note: 4^x is equiv to 2^(2*x)
- CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0 END +
+ CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
-- Static User Checks
- CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0 END +
- CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0 END +
- CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0 END +
+ CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
+ CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
+ CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
-- Static Item Checks
- CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0 END +
- CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0 END +
- CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0 END +
- CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0 END +
- CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0 END DESC,
+ CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
+ CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
+ CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
+ CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
+ CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
-- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
-- This prevents "we changed the table order by updating a rule, and we started getting different results"
m.id;
id SERIAL PRIMARY KEY,
active BOOL NOT NULL DEFAULT TRUE,
strict_ou_match BOOL NOT NULL DEFAULT FALSE,
+ -- Match Fields
user_home_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best"
request_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best"
pickup_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best"
marc_vr_format TEXT REFERENCES config.videorecording_format_map (code) DEFERRABLE INITIALLY DEFERRED,
juvenile_flag BOOL,
ref_flag BOOL,
+ -- "Result" Fields
holdable BOOL NOT NULL DEFAULT TRUE, -- Hard "can't hold" flag requiring an override
distance_is_from_owner BOOL NOT NULL DEFAULT FALSE, -- How to calculate transit_range. True means owning lib, false means copy circ lib
transit_range INT REFERENCES actor.org_unit_type (id) DEFERRABLE INITIALLY DEFERRED, -- Can circ inside range of cn.owner/cp.circ_lib at depth of the org_unit_type specified here
max_holds INT, -- Total hold requests must be less than this, NULL means skip (always pass)
include_frozen_holds BOOL NOT NULL DEFAULT TRUE, -- Include frozen hold requests in the count for max_holds test
stop_blocked_user BOOL NOT NULL DEFAULT FALSE, -- Stop users who cannot check out items from placing holds
- age_hold_protect_rule INT REFERENCES config.rule_age_hold_protect (id) DEFERRABLE INITIALLY DEFERRED, -- still not sure we want to move this off the copy
- CONSTRAINT hous_once_per_grp_loc_mod_marc UNIQUE (user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, requestor_grp, usr_grp, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag, juvenile_flag)
+ age_hold_protect_rule INT REFERENCES config.rule_age_hold_protect (id) DEFERRABLE INITIALLY DEFERRED -- still not sure we want to move this off the copy
);
+-- Nulls don't count for a constraint match, so we have to coalesce them into something that does.
+CREATE UNIQUE INDEX chmm_once_per_paramset ON config.hold_matrix_matchpoint (COALESCE(user_home_ou::TEXT, ''), COALESCE(request_ou::TEXT, ''), COALESCE(pickup_ou::TEXT, ''), COALESCE(item_owning_ou::TEXT, ''), COALESCE(item_circ_ou::TEXT, ''), COALESCE(usr_grp::TEXT, ''), COALESCE(requestor_grp::TEXT, ''), COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_vr_format, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(ref_flag::TEXT, '')) WHERE active;
+
CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
RETURNS integer AS
$func$
rec_descriptor metabib.rec_descriptor%ROWTYPE;
matchpoint config.hold_matrix_matchpoint%ROWTYPE;
weights config.hold_matrix_weights%ROWTYPE;
- denominator INT;
+ denominator NUMERIC(6,2);
BEGIN
SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
-- No weights? Bad admin! Defaults to handle that anyway.
IF weights.id IS NULL THEN
- weights.user_home_ou := 5;
- weights.request_ou := 5;
- weights.pickup_ou := 5;
- weights.item_owning_ou := 5;
- weights.item_circ_ou := 5;
- weights.usr_grp := 7;
- weights.requestor_grp := 8;
- weights.circ_modifier := 4;
- weights.marc_type := 3;
- weights.marc_form := 2;
- weights.marc_vr_format := 1;
- weights.juvenile_flag := 4;
- weights.ref_flag := 0;
+ weights.user_home_ou := 5.0;
+ weights.request_ou := 5.0;
+ weights.pickup_ou := 5.0;
+ weights.item_owning_ou := 5.0;
+ weights.item_circ_ou := 5.0;
+ weights.usr_grp := 7.0;
+ weights.requestor_grp := 8.0;
+ weights.circ_modifier := 4.0;
+ weights.marc_type := 3.0;
+ weights.marc_form := 2.0;
+ weights.marc_vr_format := 1.0;
+ weights.juvenile_flag := 4.0;
+ weights.ref_flag := 0.0;
END IF;
-- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
ORDER BY
-- Permission Groups
- CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0 END +
- CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0 END +
+ CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
-- Org Units
- CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0 END +
- CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0 END +
- CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0 END +
- CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0 END +
- CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0 END +
+ CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
-- Static User Checks -- Note: 4^x is equiv to 2^(2*x)
- CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0 END +
+ CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
-- Static Item Checks
- CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0 END +
- CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0 END +
- CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0 END +
- CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0 END +
- CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0 END DESC,
+ CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
+ CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
+ CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
+ CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
+ CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
-- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
-- This prevents "we changed the table order by updating a rule, and we started getting different results"
m.id;
series_title.value AS series_title,
series_statement.value AS series_statement,
summary.value AS summary,
- ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
- ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
+ ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
+ ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
FIRST(author.value) AS author,
ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
- ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
- ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
+ ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
+ ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn
FROM biblio.record_entry r
LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
--- /dev/null
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0482');
+
+-- Drop old (non-functional) constraints
+
+ALTER TABLE config.circ_matrix_matchpoint
+ DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
+
+ALTER TABLE config.hold_matrix_matchpoint
+ DROP CONSTRAINT hous_once_per_grp_loc_mod_marc;
+
+-- Clean up tables before making normalized index
+
+CREATE OR REPLACE FUNCTION action.cleanup_matrix_matchpoints() RETURNS void AS $func$
+DECLARE
+ temp_row RECORD;
+BEGIN
+ -- Circ Matrix
+ FOR temp_row IN
+ SELECT org_unit, grp, circ_modifier, marc_type, marc_form, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_lower_bound, usr_age_upper_bound, COUNT(id) as rowcount, MIN(id) as firstrow
+ FROM config.circ_matrix_matchpoint
+ WHERE active
+ GROUP BY org_unit, grp, circ_modifier, marc_type, marc_form, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_lower_bound, usr_age_upper_bound
+ HAVING COUNT(id) > 1 LOOP
+
+ UPDATE config.circ_matrix_matchpoint SET active=false
+ WHERE id > temp_row.firstrow
+ AND org_unit = temp_row.org_unit
+ AND grp = temp_row.grp
+ AND circ_modifier IS NOT DISTINCT FROM temp_row.circ_modifier
+ AND marc_type IS NOT DISTINCT FROM temp_row.marc_type
+ AND marc_form IS NOT DISTINCT FROM temp_row.marc_form
+ AND marc_vr_format IS NOT DISTINCT FROM temp_row.marc_vr_format
+ AND copy_circ_lib IS NOT DISTINCT FROM temp_row.copy_circ_lib
+ AND copy_owning_lib IS NOT DISTINCT FROM temp_row.copy_owning_lib
+ AND user_home_ou IS NOT DISTINCT FROM temp_row.user_home_ou
+ AND ref_flag IS NOT DISTINCT FROM temp_row.ref_flag
+ AND juvenile_flag IS NOT DISTINCT FROM temp_row.juvenile_flag
+ AND is_renewal IS NOT DISTINCT FROM temp_row.is_renewal
+ AND usr_age_lower_bound IS NOT DISTINCT FROM temp_row.usr_age_lower_bound
+ AND usr_age_upper_bound IS NOT DISTINCT FROM temp_row.usr_age_upper_bound;
+ END LOOP;
+
+ -- Hold Matrix
+ FOR temp_row IN
+ SELECT user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_vr_format, juvenile_flag, ref_flag, COUNT(id) as rowcount, MIN(id) as firstrow
+ FROM config.hold_matrix_matchpoint
+ WHERE active
+ GROUP BY user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_vr_format, juvenile_flag, ref_flag
+ HAVING COUNT(id) > 1 LOOP
+
+ UPDATE config.hold_matrix_matchpoint SET active=false
+ WHERE id > temp_row.firstrow
+ AND user_home_ou IS NOT DISTINCT FROM temp_row.user_home_ou
+ AND request_ou IS NOT DISTINCT FROM temp_row.request_ou
+ AND pickup_ou IS NOT DISTINCT FROM temp_row.pickup_ou
+ AND item_owning_ou IS NOT DISTINCT FROM temp_row.item_owning_ou
+ AND item_circ_ou IS NOT DISTINCT FROM temp_row.item_circ_ou
+ AND usr_grp IS NOT DISTINCT FROM temp_row.usr_grp
+ AND requestor_grp IS NOT DISTINCT FROM temp_row.requestor_grp
+ AND circ_modifier IS NOT DISTINCT FROM temp_row.circ_modifier
+ AND marc_type IS NOT DISTINCT FROM temp_row.marc_type
+ AND marc_form IS NOT DISTINCT FROM temp_row.marc_form
+ AND marc_vr_format IS NOT DISTINCT FROM temp_row.marc_vr_format
+ AND juvenile_flag IS NOT DISTINCT FROM temp_row.juvenile_flag
+ AND ref_flag IS NOT DISTINCT FROM temp_row.ref_flag;
+ END LOOP;
+END;
+$func$ LANGUAGE plpgsql;
+
+SELECT action.cleanup_matrix_matchpoints();
+
+DROP FUNCTION IF EXISTS action.cleanup_matrix_matchpoints();
+
+-- Create Normalized indexes
+
+CREATE UNIQUE INDEX ccmm_once_per_paramset ON config.circ_matrix_matchpoint (org_unit, grp, COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_vr_format, ''), COALESCE(copy_circ_lib::TEXT, ''), COALESCE(copy_owning_lib::TEXT, ''), COALESCE(user_home_ou::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(is_renewal::TEXT, ''), COALESCE(usr_age_lower_bound::TEXT, ''), COALESCE(usr_age_upper_bound::TEXT, '')) WHERE active;
+
+CREATE UNIQUE INDEX chmm_once_per_paramset ON config.hold_matrix_matchpoint (COALESCE(user_home_ou::TEXT, ''), COALESCE(request_ou::TEXT, ''), COALESCE(pickup_ou::TEXT, ''), COALESCE(item_owning_ou::TEXT, ''), COALESCE(item_circ_ou::TEXT, ''), COALESCE(usr_grp::TEXT, ''), COALESCE(requestor_grp::TEXT, ''), COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_vr_format, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(ref_flag::TEXT, '')) WHERE active;
+
+COMMIT;
--- /dev/null
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0483');
+
+CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS config.circ_matrix_matchpoint AS $func$
+DECLARE
+ user_object actor.usr%ROWTYPE;
+ item_object asset.copy%ROWTYPE;
+ cn_object asset.call_number%ROWTYPE;
+ rec_descriptor metabib.rec_descriptor%ROWTYPE;
+ matchpoint config.circ_matrix_matchpoint%ROWTYPE;
+ weights config.circ_matrix_weights%ROWTYPE;
+ user_age INTERVAL;
+ denominator NUMERIC(6,2);
+BEGIN
+ SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+ SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+ SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+ SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = cn_object.record;
+
+ -- Pre-generate this so we only calc it once
+ IF user_object.dob IS NOT NULL THEN
+ SELECT INTO user_age age(user_object.dob);
+ END IF;
+
+ -- Grab the closest set circ weight setting.
+ SELECT INTO weights cw.*
+ FROM config.weight_assoc wa
+ JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
+ JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
+ WHERE active
+ ORDER BY d.distance
+ LIMIT 1;
+
+ -- No weights? Bad admin! Defaults to handle that anyway.
+ IF weights.id IS NULL THEN
+ weights.grp := 11.0;
+ weights.org_unit := 10.0;
+ weights.circ_modifier := 5.0;
+ weights.marc_type := 4.0;
+ weights.marc_form := 3.0;
+ weights.marc_vr_format := 2.0;
+ weights.copy_circ_lib := 8.0;
+ weights.copy_owning_lib := 8.0;
+ weights.user_home_ou := 8.0;
+ weights.ref_flag := 1.0;
+ weights.juvenile_flag := 6.0;
+ weights.is_renewal := 7.0;
+ weights.usr_age_lower_bound := 0.0;
+ weights.usr_age_upper_bound := 0.0;
+ END IF;
+
+ -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
+ -- If you break your org tree with funky parenting this may be wrong
+ -- Note: This CTE is duplicated in the find_hold_matrix_matchpoint function, and it may be a good idea to split it off to a function
+ -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
+ WITH all_distance(distance) AS (
+ SELECT depth AS distance FROM actor.org_unit_type
+ UNION
+ SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
+ )
+ SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
+
+ -- Select the winning matchpoint into the matchpoint variable for returning
+ SELECT INTO matchpoint m.*
+ FROM config.circ_matrix_matchpoint m
+ /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
+ /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
+ WHERE m.active
+ -- Permission Groups
+ -- AND (m.grp IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
+ -- Org Units
+ -- AND (m.org_unit IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
+ AND (m.copy_owning_lib IS NULL OR cnoua.id IS NOT NULL)
+ AND (m.copy_circ_lib IS NULL OR iooua.id IS NOT NULL)
+ AND (m.user_home_ou IS NULL OR uhoua.id IS NOT NULL)
+ -- Circ Type
+ AND (m.is_renewal IS NULL OR m.is_renewal = renewal)
+ -- Static User Checks
+ AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
+ AND (m.usr_age_lower_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
+ AND (m.usr_age_upper_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
+ -- Static Item Checks
+ AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
+ AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
+ AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
+ AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
+ AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
+ ORDER BY
+ -- Permission Groups
+ CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
+ -- Org Units
+ CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
+ -- Circ Type -- Note: 4^x is equiv to 2^(2*x)
+ CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
+ -- Static User Checks
+ CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
+ CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
+ CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
+ -- Static Item Checks
+ CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
+ CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
+ CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
+ CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
+ CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
+ -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
+ -- This prevents "we changed the table order by updating a rule, and we started getting different results"
+ m.id;
+
+ -- Return the entire matchpoint
+ RETURN matchpoint;
+END;
+$func$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
+ RETURNS integer AS
+$func$
+DECLARE
+ requestor_object actor.usr%ROWTYPE;
+ user_object actor.usr%ROWTYPE;
+ item_object asset.copy%ROWTYPE;
+ item_cn_object asset.call_number%ROWTYPE;
+ rec_descriptor metabib.rec_descriptor%ROWTYPE;
+ matchpoint config.hold_matrix_matchpoint%ROWTYPE;
+ weights config.hold_matrix_weights%ROWTYPE;
+ denominator NUMERIC(6,2);
+BEGIN
+ SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+ SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
+ SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+ SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+ SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = item_cn_object.record;
+
+ -- The item's owner should probably be the one determining if the item is holdable
+ -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
+ -- This flag will allow for setting it to the owning library (where the call number "lives")
+ PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
+
+ -- Grab the closest set circ weight setting.
+ IF NOT FOUND THEN
+ -- Default to circ library
+ SELECT INTO weights hw.*
+ FROM config.weight_assoc wa
+ JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
+ JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
+ WHERE active
+ ORDER BY d.distance
+ LIMIT 1;
+ ELSE
+ -- Flag is set, use owning library
+ SELECT INTO weights hw.*
+ FROM config.weight_assoc wa
+ JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
+ JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id)
+ WHERE active
+ ORDER BY d.distance
+ LIMIT 1;
+ END IF;
+
+ -- No weights? Bad admin! Defaults to handle that anyway.
+ IF weights.id IS NULL THEN
+ weights.user_home_ou := 5.0;
+ weights.request_ou := 5.0;
+ weights.pickup_ou := 5.0;
+ weights.item_owning_ou := 5.0;
+ weights.item_circ_ou := 5.0;
+ weights.usr_grp := 7.0;
+ weights.requestor_grp := 8.0;
+ weights.circ_modifier := 4.0;
+ weights.marc_type := 3.0;
+ weights.marc_form := 2.0;
+ weights.marc_vr_format := 1.0;
+ weights.juvenile_flag := 4.0;
+ weights.ref_flag := 0.0;
+ END IF;
+
+ -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
+ -- If you break your org tree with funky parenting this may be wrong
+ -- Note: This CTE is duplicated in the find_circ_matrix_matchpoint function, and it may be a good idea to split it off to a function
+ -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
+ WITH all_distance(distance) AS (
+ SELECT depth AS distance FROM actor.org_unit_type
+ UNION
+ SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
+ )
+ SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
+
+ -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
+ -- This may be better implemented as part of the upgrade script?
+ -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
+ -- Then remove this flag, of course.
+ PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
+
+ IF FOUND THEN
+ -- Note: This, to me, is REALLY hacky. I put it in anyway.
+ -- If you can't tell, this is a single call swap on two variables.
+ SELECT INTO user_object.profile, requestor_object.profile
+ requestor_object.profile, user_object.profile;
+ END IF;
+
+ -- Select the winning matchpoint into the matchpoint variable for returning
+ SELECT INTO matchpoint m.*
+ FROM config.hold_matrix_matchpoint m
+ /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
+ LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
+ LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
+ LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
+ WHERE m.active
+ -- Permission Groups
+ -- AND (m.requestor_grp IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
+ AND (m.usr_grp IS NULL OR upgad.id IS NOT NULL)
+ -- Org Units
+ AND (m.pickup_ou IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.request_ou IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.item_owning_ou IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.item_circ_ou IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
+ AND (m.user_home_ou IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
+ -- Static User Checks
+ AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
+ -- Static Item Checks
+ AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
+ AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
+ AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
+ AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
+ AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
+ ORDER BY
+ -- Permission Groups
+ CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
+ -- Org Units
+ CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
+ CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
+ -- Static User Checks -- Note: 4^x is equiv to 2^(2*x)
+ CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
+ -- Static Item Checks
+ CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
+ CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
+ CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
+ CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
+ CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
+ -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
+ -- This prevents "we changed the table order by updating a rule, and we started getting different results"
+ m.id;
+
+ -- Return just the ID for now
+ RETURN matchpoint.id;
+END;
+$func$ LANGUAGE 'plpgsql';
+
+COMMIT;
--- /dev/null
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0484'); -- miker
+
+DROP FUNCTION asset.metarecord_copy_count ( INT, BIGINT, BOOL );
+DROP FUNCTION asset.record_copy_count ( INT, BIGINT, BOOL );
+
+DROP FUNCTION asset.opac_ou_record_copy_count (INT, BIGINT);
+CREATE OR REPLACE FUNCTION asset.opac_ou_record_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE
+ ans RECORD;
+ trans INT;
+BEGIN
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+ FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
+ RETURN QUERY
+ SELECT ans.depth,
+ ans.id,
+ COUNT( av.id ),
+ SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+ COUNT( av.id ),
+ trans
+ FROM
+ actor.org_unit_descendants(ans.id) d
+ JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
+ JOIN asset.copy cp ON (cp.id = av.id)
+ GROUP BY 1,2,6;
+
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+DROP FUNCTION asset.opac_lasso_record_copy_count (INT, BIGINT);
+CREATE OR REPLACE FUNCTION asset.opac_lasso_record_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE
+ ans RECORD;
+ trans INT;
+BEGIN
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+ FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
+ RETURN QUERY
+ SELECT -1,
+ ans.id,
+ COUNT( av.id ),
+ SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+ COUNT( av.id ),
+ trans
+ FROM
+ actor.org_unit_descendants(ans.id) d
+ JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
+ JOIN asset.copy cp ON (cp.id = av.id)
+ GROUP BY 1,2,6;
+
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+DROP FUNCTION asset.staff_ou_record_copy_count (INT, BIGINT);
+CREATE OR REPLACE FUNCTION asset.staff_ou_record_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE
+ ans RECORD;
+ trans INT;
+BEGIN
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+ FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
+ RETURN QUERY
+ SELECT ans.depth,
+ ans.id,
+ COUNT( cp.id ),
+ SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+ COUNT( cp.id ),
+ trans
+ FROM
+ actor.org_unit_descendants(ans.id) d
+ JOIN asset.copy cp ON (cp.circ_lib = d.id)
+ JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number)
+ GROUP BY 1,2,6;
+
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+DROP FUNCTION asset.staff_lasso_record_copy_count (INT, BIGINT);
+CREATE OR REPLACE FUNCTION asset.staff_lasso_record_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE
+ ans RECORD;
+ trans INT;
+BEGIN
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+ FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
+ RETURN QUERY
+ SELECT -1,
+ ans.id,
+ COUNT( cp.id ),
+ SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+ COUNT( cp.id ),
+ trans
+ FROM
+ actor.org_unit_descendants(ans.id) d
+ JOIN asset.copy cp ON (cp.circ_lib = d.id)
+ JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number)
+ GROUP BY 1,2,6;
+
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION asset.record_copy_count ( place INT, rid BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+BEGIN
+ IF staff IS TRUE THEN
+ IF place > 0 THEN
+ RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, rid );
+ ELSE
+ RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, rid );
+ END IF;
+ ELSE
+ IF place > 0 THEN
+ RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, rid );
+ ELSE
+ RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, rid );
+ END IF;
+ END IF;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+DROP FUNCTION asset.opac_ou_metarecord_copy_count (INT, BIGINT);
+CREATE OR REPLACE FUNCTION asset.opac_ou_metarecord_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE
+ ans RECORD;
+ trans INT;
+BEGIN
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+ FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
+ RETURN QUERY
+ SELECT ans.depth,
+ ans.id,
+ COUNT( av.id ),
+ SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+ COUNT( av.id ),
+ trans
+ FROM
+ actor.org_unit_descendants(ans.id) d
+ JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
+ JOIN asset.copy cp ON (cp.id = av.id)
+ JOIN metabib.metarecord_source_map m ON (m.source = av.record)
+ GROUP BY 1,2,6;
+
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+DROP FUNCTION asset.opac_lasso_metarecord_copy_count (INT, BIGINT);
+CREATE OR REPLACE FUNCTION asset.opac_lasso_metarecord_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE
+ ans RECORD;
+ trans INT;
+BEGIN
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+ FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
+ RETURN QUERY
+ SELECT -1,
+ ans.id,
+ COUNT( av.id ),
+ SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+ COUNT( av.id ),
+ trans
+ FROM
+ actor.org_unit_descendants(ans.id) d
+ JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
+ JOIN asset.copy cp ON (cp.id = av.id)
+ JOIN metabib.metarecord_source_map m ON (m.source = av.record)
+ GROUP BY 1,2,6;
+
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+DROP FUNCTION asset.staff_ou_metarecord_copy_count (INT, BIGINT);
+CREATE OR REPLACE FUNCTION asset.staff_ou_metarecord_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE
+ ans RECORD;
+ trans INT;
+BEGIN
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+ FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
+ RETURN QUERY
+ SELECT ans.depth,
+ ans.id,
+ COUNT( cp.id ),
+ SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+ COUNT( cp.id ),
+ trans
+ FROM
+ actor.org_unit_descendants(ans.id) d
+ JOIN asset.copy cp ON (cp.circ_lib = d.id)
+ JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number)
+ JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
+ GROUP BY 1,2,6;
+
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+DROP FUNCTION asset.staff_lasso_metarecord_copy_count (INT, BIGINT);
+CREATE OR REPLACE FUNCTION asset.staff_lasso_metarecord_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE
+ ans RECORD;
+ trans INT;
+BEGIN
+ SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+ FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
+ RETURN QUERY
+ SELECT -1,
+ ans.id,
+ COUNT( cp.id ),
+ SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+ COUNT( cp.id ),
+ trans
+ FROM
+ actor.org_unit_descendants(ans.id) d
+ JOIN asset.copy cp ON (cp.circ_lib = d.id)
+ JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number)
+ JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
+ GROUP BY 1,2,6;
+
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION asset.metarecord_copy_count ( place INT, rid BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+BEGIN
+ IF staff IS TRUE THEN
+ IF place > 0 THEN
+ RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, rid );
+ ELSE
+ RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, rid );
+ END IF;
+ ELSE
+ IF place > 0 THEN
+ RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, rid );
+ ELSE
+ RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, rid );
+ END IF;
+ END IF;
+
+ RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+COMMIT;
--- /dev/null
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0485'); -- dbs
+
+CREATE OR REPLACE VIEW reporter.simple_record AS
+SELECT r.id,
+ s.metarecord,
+ r.fingerprint,
+ r.quality,
+ r.tcn_source,
+ r.tcn_value,
+ title.value AS title,
+ uniform_title.value AS uniform_title,
+ author.value AS author,
+ publisher.value AS publisher,
+ SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
+ series_title.value AS series_title,
+ series_statement.value AS series_statement,
+ summary.value AS summary,
+ ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
+ ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
+ ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
+ ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
+ ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
+ ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
+ ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
+ ARRAY((SELECT value FROM metabib.full_rec WHERE tag = '856' AND subfield IN ('3','y','u') AND record = r.id ORDER BY CASE WHEN subfield IN ('3','y') THEN 0 ELSE 1 END)) AS external_uri
+ FROM biblio.record_entry r
+ JOIN metabib.metarecord_source_map s ON (s.source = r.id)
+ LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
+ LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
+ LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
+ LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
+ LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
+ LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
+ LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
+ LEFT JOIN metabib.full_rec series_title ON (r.id = series_title.record AND series_title.tag IN ('830','440') AND series_title.subfield = 'a')
+ LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
+ LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
+ GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
+
+CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
+SELECT r.id,
+ r.fingerprint,
+ r.quality,
+ r.tcn_source,
+ r.tcn_value,
+ FIRST(title.value) AS title,
+ FIRST(author.value) AS author,
+ ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
+ ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
+ ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
+ ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn
+ FROM biblio.record_entry r
+ LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
+ LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
+ LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
+ LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
+ LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
+ LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
+ GROUP BY 1,2,3,4,5;
+
+-- Update reporter.materialized_simple_record with normalized ISBN values
+-- This might not get all of them, but most ISBNs will have more than one hyphen
+DELETE FROM reporter.materialized_simple_record WHERE id IN (
+ SELECT record FROM metabib.full_rec WHERE tag = '020' AND subfield IN ('a', 'z') AND value LIKE '%-%-%'
+);
+
+INSERT INTO reporter.materialized_simple_record
+ SELECT DISTINCT rossr.* FROM reporter.old_super_simple_record rossr INNER JOIN metabib.full_rec mfr ON mfr.record = rossr.id
+ WHERE mfr.tag = '020' AND mfr.subfield IN ('a', 'z') AND mfr.value LIKE '%-%-%'
+;
+
+COMMIT;
--- /dev/null
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0486');
+
+ALTER TABLE money.credit_card_payment ADD COLUMN cc_order_number TEXT;
+
+COMMIT;
use Unicode::Normalize;
use OpenILS::Application::AppUtils;
use Data::Dumper;
+use Pod::Usage qw/ pod2usage /;
-=head1
-
-For a given set of records (specified by ID at the command line, or special option --all):
-
-=over
-
-=item * Iterate through the list of fields that are controlled fields
-
-=item * Iterate through the list of subfields that are controlled for
-that given field
-
-=item * Search for a matching authority record for that combination of
-field + subfield(s)
-
-=over
-
-=item * If we find a match, then add a $0 subfield to that field identifying
-the controlling authority record
-
-=item * If we do not find a match, then insert a row into an "uncontrolled"
-table identifying the record ID, field, and subfield(s) that were not controlled
-
-=back
-
-=item * Iterate through the list of floating subdivisions
-
-=over
-
-=item * If we find a match, then add a $0 subfield to that field identifying
-the controlling authority record
-
-=item * If we do not find a match, then insert a row into an "uncontrolled"
-table identifying the record ID, field, and subfield(s) that were not controlled
-
-=back
-
-=item * If we changed the record, update it in the database
-
-=back
-
-=cut
-
-my $all_records;
+my ($start_id, $end_id);
my $bootstrap = '/openils/conf/opensrf_core.xml';
my @records;
+
+my %options;
my $result = GetOptions(
+ \%options,
'configuration=s' => \$bootstrap,
- 'record=s' => \@records,
- 'all' => \$all_records
+ 'record=i' => \@records,
+ 'all', 'help',
+ 'start_id=i' => \$start_id,
+ 'end_id=i' => \$end_id,
);
+if (!$result or $options{help}) {
+ pod2usage(0);
+}
+
OpenSRF::System->bootstrap_client(config_file => $bootstrap);
Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
my $editor = OpenILS::Utils::CStoreEditor->new;
my $undeleted;
-if ($all_records) {
+if ($options{all}) {
# get a list of all non-deleted records from Evergreen
# open-ils.cstore open-ils.cstore.direct.biblio.record_entry.id_list.atomic {"deleted":"f"}
$undeleted = $editor->request(
);
@records = @$undeleted;
}
+
+if ($start_id and $end_id) {
+ @records = ($start_id .. $end_id);
+}
# print Dumper($undeleted, \@records);
# Hash of controlled fields & subfields in bibliographic records, and their
}
$e->commit();
}
+
+__END__
+
+=head1 NAME
+
+authority_control_fields.pl - Controls fields in bibliographic records with authorities in Evergreen
+
+=head1 SYNOPSIS
+
+C<authority_control_fields.pl> [B<--configuration>=I<opensrf_core.conf>]
+[[B<--record>=I<record>[ B<--record>=I<record>]]] | [B<--all>] | [B<--start_id>=I<start-ID> B<--end_id>=I<end-ID>]
+
+=head1 DESCRIPTION
+
+For a given set of records:
+
+=over
+
+=item * Iterate through the list of fields that are controlled fields
+
+=item * Iterate through the list of subfields that are controlled for
+that given field
+
+=item * Search for a matching authority record for that combination of
+field + subfield(s)
+
+=over
+
+=item * If we find a match, then add a $0 subfield to that field identifying
+the controlling authority record
+
+=item * If we do not find a match, then insert a row into an "uncontrolled"
+table identifying the record ID, field, and subfield(s) that were not controlled
+
+=back
+
+=item * Iterate through the list of floating subdivisions
+
+=over
+
+=item * If we find a match, then add a $0 subfield to that field identifying
+the controlling authority record
+
+=item * If we do not find a match, then insert a row into an "uncontrolled"
+table identifying the record ID, field, and subfield(s) that were not controlled
+
+=back
+
+=item * If we changed the record, update it in the database
+
+=back
+
+=head1 OPTIONS
+
+=over
+
+=item * B<-c> I<config-file>, B<--configuration>=I<config-file>
+
+Specifies the OpenSRF configuration file used to connect to the OpenSRF router.
+Defaults to F</openils/conf/opensrf_core.xml>
+
+=item * B<-r> I<record-ID>, B<--record>=I<record-ID>
+
+Specifies the bibliographic record ID (found in the C<biblio.record_entry.id>
+column) of the record to process. This option may be specified more than once
+to process multiple records in a single run.
+
+=item * B<-a>, B<--all>
+
+Specifies that all bibliographic records should be processed. For large
+databases, this may take an extraordinarily long amount of time.
+
+=item * B<-s> I<start-ID>, B<--start_id>=I<start-ID>
+
+Specifies the starting ID of the range of bibliographic records to process.
+This option is ignored unless it is accompanied by the B<-e> or B<--end_id>
+option.
+
+=item * B<-e> I<end-ID>, B<--end_id>=I<end-ID>
+
+Specifies the ending ID of the range of bibliographic records to process.
+This option is ignored unless it is accompanied by the B<-s> or B<--start>
+option.
+
+=back
+
+=head1 EXAMPLES
+
+ authority_control_fields.pl --start_id 1 --end_id 50000
+
+Processes the bibliographic records with IDs between 1 and 50,000 using the
+default OpenSRF configuration file for connection information.
+
+=head1 AUTHOR
+
+Dan Scott <dscott@laurentian.ca>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2010-2011 by Dan Scott
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+=cut
+
# vim:noet:ts=4:
use strict;
use warnings;
+use Test::More tests => 5;
+use Error qw(:try);
-#FIXME: use Test::More or any kind of Test module instead of eval/die if
-
-BEGIN {
- eval "use OpenSRF::Utils::Config;";
- die "Please ensure that /openils/lib/perl5 is in your PERL5LIB environment variable.
- You must run this script as the 'opensrf' user.\n" if ($@);
- eval "use Error qw/:try/;";
- die "Please install Error.pm.\n" if ($@);
- eval "use UNIVERSAL::require;";
- die "Please install the UNIVERSAL::require perl module.\n" if ($@);
- eval "use Getopt::Long;";
- die "Please install the Getopt::Long perl module.\n" if ($@);
- eval "use Net::Domain;";
- die "Please install the Net::Domain perl module.\n" if ($@);
-}
+use_ok( 'OpenSRF::Utils::Config' );
+use_ok( 'UNIVERSAL::require' );
+use_ok( 'Getopt::Long' );
+use_ok( 'Net::Domain' );
my $output = '';
my $perloutput = '';
$output .= test_db_connect($db_name, $db_host, $db_port, $db_user, $db_pw, $osrf_xpath);
}
+print "\nChecking postgresql version\n";
+system ("psql", "--version");
+
print "\nChecking database drivers to ensure <driver> matches <language>\n";
# Check database drivers
# if language eq 'C', driver eq 'pgsql'
$result = "* ERROR: $driver language is $language in $lang_xpath\n";
warn $result;
}
+
+ } elsif ($driver eq "SIP") {
+ $result = "* OK SIP from telephony section. \n";
+ warn $result;
} else {
$result = "* ERROR: Unknown driver $driver in $driver_xpath\n";
warn $result;
Parse::RecDescent
SRU
JSON::XS
+UUID::Tiny
+Business::CreditCard::Object
+Net::Z3950::Simple2ZOOM
.opac-auto-094 { padding: 5px 7px 0px 0px; }
.opac-auto-095 { padding: 5px 7px 0px 0px; white-space: nowrap; }
.opac-auto-096 { padding: 6px }
-.opac-auto-097 { padding: 8px 0px 6px 0px; }
+.opac-auto-097 { padding: 8px 0px 6px 0px; width: 100%; border: 0; }
+.opac-auto-097b { padding: 8px 0px 6px 0px; border: 0; }
.opac-auto-098 { padding-bottom: 10px; }
.opac-auto-099 { padding-bottom: 12px; color: #666; }
.opac-auto-100 { padding-bottom: 16px; }
}
#dash_user {
- font-weight:bold;
- text-transform:capitalize;
-}
-
+ font-weight: bold;
+ text-transform: capitalize;
+ position: relative;
+ top: 10px;
+}
+
+#dash_corner_mid1a {
+ vertical-align: top;
+ background: url('/images/dash-corner-mid1.png') repeat-x;
+ padding-left: 8px;
+}
+#dash_corner_mid1b {
+ background: url('/images/dash-corner-mid1.png') repeat-x;
+ padding: 0px 8px 0px 10px;
+}
+#dash_corner_mid1b img { position: relative; top: -1px; }
+#dash_corner_mid1c {
+ background: url('/images/dash-corner-mid1.png') repeat-x;
+ vertical-align: top;
+}
+#dash_corner_mid2a {
+ vertical-align: top;
+ width: 372px;
+ background: url('/images/dash-corner-mid2.png') repeat-x;
+}
+.dash-pos-out { position: relative; left: 3px; }
+.dash-pos-holds { position: relative; left: 100px; }
+.dash-align-out { text-align: right; width: 86px; }
+.dash-align-holds { text-align: right; width: 62px; }
+.dash-pos-pickup { position: relative; left: 170px; }
+.dash-align-pickup { text-align: right; width: 111px; }
+.dash-pos-fines { position: relative; left: 284px; }
+.dash-align-fines { text-align: right; width: 76px; }
+.pos-rel-top4 { position: relative; top: 4px; }
+#dash_number_row { position: relative; top: 6px; }
+#logout_link { left: 1px; }
+
+#dash_checked { color: #ffcc33; }
+#dash_holds { color: #ffcc33; }
+#dash_pickup { color: #1dd93c; }
+#dash_fines { color: #f41d36; }
#header {
color: #fff;
padding: 26px 0px 26px 0px;
padding-left:0px;
}
+#gold-links-home {
+ margin:auto;
+ width:694px;
+ padding-left:0px;
+}
+
#util-bar {
margin:auto;
width:974px;
font-size: 18px;
}
+#rdetail_image { border: none; }
#rdetail_image_cell {
padding-top: 3px;
padding-right: 10px;
#hp-buttons {
margin: auto;
margin-top: 6px;
- width: 974px;
+ width: 694px; /* 974px; */
}
#hp-welcome {
#hp-banner {
margin: auto;
- width:974px;
+ width: 694px; /* formerly 974px */
height: 213px;
}
border-bottom: 1px solid black;
}
-#main-content {
- /* on devcatalog: width: 974px; margin:auto; padding-left:0px; */
- width: 694px;
- margin: auto;
- padding-left: 17px;
-}
+#main-content-home { width: 694px; margin: auto; padding-left: 17px; }
+#main-content { width: 974px; margin:auto; padding-left: 0px; }
#main-content .login_boxes {
border: 1px solid #dedede;
margin:0;
}
-#results_header_nav1 {
+.results_header_nav1 {
padding: 5px 7px 6px 0px;
border-bottom: 1px dotted #ccc;
}
-#results_header_nav1 .h1 {
+.results_header_nav1 .h1 {
font-size:14px;
font-weight:bold;
color:#074079;
}
-#start_end_links_span {
- font-size: 11px;
-}
-
-#start_end_links_span2 {
+.start_end_links_span {
font-size: 11px;
}
margin-top: 20px;
}
-#result_numbers1 {
- font-size: 11px;
- padding-left:15px;
+.result_numbers {
+ font-size: 11px; padding-left:15px; white-space: nowrap; width: 320px;
}
.result_table_subtable { width: 100%; border-collapse: collapse; border: 0; }
#rdetail_copy_info_table { font-size: 8pt; }
#rdetail_copy_info_table td { padding: 3px; }
.search_page_nav_link { cursor: pointer; }
+#opac.result.sort { width: 160px; }
+.renew-summary { font-size: 125%; font-style: italic; margin: 0.5ex 0; }
+.failure-text { margin-left: 4em; font-style: italic; color: #ff0000; }
<!ENTITY staff.serial.ssub_editor.notes.accesskey "N">
<!ENTITY staff.serial.manage_dists.actions.cmd_add_sstr.label "Add Stream">
<!ENTITY staff.serial.manage_dists.actions.cmd_delete_sstr.label "Delete Stream">
+<!ENTITY staff.serial.manage_items.actions.cmd_edit_items.label "Edit Item Attributes">
+<!ENTITY staff.serial.manage_items.actions.cmd_edit_items.access_key "E">
+<!ENTITY staff.serial.manage_items.actions.cmd_delete_items.label "Delete Items">
+<!ENTITY staff.serial.manage_items.actions.cmd_delete_items.access_key "D">
+<!ENTITY staff.serial.manage_items.actions.cmd_reset_items.label "Reset Items to Expected">
+<!ENTITY staff.serial.manage_items.workarea_showing "Showing: ">
+<!ENTITY staff.serial.manage_items.workarea_current_unit "Current Working Unit: ">
+<!ENTITY staff.serial.manage_items.workarea_recently_received "Recently Received">
+<!ENTITY staff.serial.manage_items.mode "Mode:">
+<!ENTITY staff.serial.manage_items.bind.label "Bind">
+<!ENTITY staff.serial.manage_items.receive.label "Receive">
+<!ENTITY staff.serial.manage_items.show_all.label "Show All">
+<!ENTITY staff.serial.manage_items.receive_move.label "Receive/Move Selected ↓">
+<!ENTITY staff.serial.manage_items.set_current_unit.label "Set Current Unit">
+<!ENTITY staff.serial.manage_items.auto_per_item.label "Auto per Item">
+<!ENTITY staff.serial.manage_items.new_unit.label "New Unit">
+<!ENTITY staff.serial.manage_items.recent.label "Recent">
+<!ENTITY staff.serial.manage_items.other_unit.label "Other...">
<!ENTITY staff.serial.batch_receive "Batch Receive">
<!ENTITY staff.serial.batch_receive.bib_search_term.label "Enter an identifier for a bibliographic record:">
<form action='place_hold' method='POST'>
Choose a pickup Library [% PROCESS build_org_selector name='pickup_lib' value=ctx.default_pickup_lib %]
<input type='Submit'/>
- <input type='hidden' name='hold_target' value='[% CGI.param('hold_target') %]'/>
- <input type='hidden' name='hold_type' value='[% CGI.param('hold_type') %]'/>
- <input type='hidden' name='redirect_to' value='[% ctx.referer %]'/>
+ <input type='hidden' name='hold_target' value='[% CGI.param('hold_target') | html %]'/>
+ <input type='hidden' name='hold_type' value='[% CGI.param('hold_type') | html %]'/>
+ <input type='hidden' name='redirect_to' value='[% ctx.referer | html %]'/>
</form>
[% END %]
</div>
-[% WRAPPER "default/opac/parts/base.tt2";
+[% PROCESS "default/opac/parts/header.tt2";
+ WRAPPER "default/opac/parts/base.tt2";
INCLUDE "default/opac/parts/topnav.tt2";
- ctx.page_title = "Advanced Search" %]
+ ctx.page_title = l("Advanced Search") %]
<div id="search-wrapper">
[% INCLUDE "default/opac/parts/utils.tt2" %]
<div id="adv_search_parent">
<div id="adv_search_tabs">
- <a href="#" alt="Advanced Search" id="adv_search"
- rel="adv_global_search"></a>
- <a href="#" alt="Numeric Search" id="num_search"
- rel="adv_quick_search_sidebar"></a>
- <a href="#" alt="Expert Search" id="expert_search"
- rel="adv_marc_search_sidebar"></a>
+ <a href="#" alt="[% l('Advanced Search') %]" id="adv_search"></a>
+ <a href="#" alt="[% l('Numeric Search') %]" id="num_search"></a>
+ <a href="#" alt="[% l('Expert Search') %]" id="expert_search"></a>
</div>
</div>
</div>
-[% WRAPPER "default/opac/parts/base.tt2";
+[% PROCESS "default/opac/parts/header.tt2";
+ WRAPPER "default/opac/parts/base.tt2";
INCLUDE "default/opac/parts/topnav.tt2";
- ctx.page_title = "Home" %]
+ ctx.page_title = l("Home") %]
<div id="search-wrapper">
[% INCLUDE "default/opac/parts/utils.tt2" %]
[% INCLUDE "default/opac/parts/searchbar.tt2" %]
</div>
<div id="content-wrapper">
- <div id="main-content">
+ <div id="main-content-home">
<div class="common-full-pad"></div>
[% INCLUDE "default/opac/parts/homesearch.tt2" %]
<div class="common-full-pad"></div>
-[% WRAPPER "default/opac/parts/base.tt2";
+[% PROCESS "default/opac/parts/header.tt2";
+ WRAPPER "default/opac/parts/base.tt2";
INCLUDE "default/opac/parts/topnav.tt2";
- ctx.page_title = "Account Login" %]
+ ctx.page_title = l("Account Login") %]
<div id="search-wrapper">
[% INCLUDE "default/opac/parts/utils.tt2" %]
[% INCLUDE "default/opac/parts/searchbar.tt2" %]
<div id="main-content">
[% INCLUDE "default/opac/parts/login/form.tt2" %]
<div class="clear-both very-big-height"></div>
+ <script type="text/javascript">
+ var _onload = window.onload;
+ window.onload = function() {
+ try {
+ document.getElementById("username_field").focus();
+ if (_onload) _onload();
+ } catch (E) {
+ void(0);
+ }
+ };
+ </script>
</div>
</div>
[% END %]
-[% WRAPPER "default/opac/parts/base.tt2" +
+[% PROCESS "default/opac/parts/header.tt2";
+ PROCESS "default/opac/parts/marc_misc.tt2";
+ WRAPPER "default/opac/parts/base.tt2" +
"default/opac/parts/myopac/base.tt2";
myopac_page = "circs" %]
<div id='myopac_checked_div' style="padding:0px;">
- <div id="acct_checked_tabs" style="padding-bottom: 12px;color:#666;">
+ <div id="acct_checked_tabs" style="padding-bottom: 12px;color:#666;" class="hide_me">
<div class="align selected" id="checked_label">
<img src="[% ctx.media_prefix %]/images/sub_checked_out_on.jpg" />
</div>
</div>
<div class="header_middle">
- <span id="acct_checked_header" style="float:left;">
- Current Items Checked Out
- </span>
- <span style="float:right;">
- <a class="hide_me" href="#">Export List</a>
+ <span class="float-left">[% l('Current Items Checked Out') %]</span>
+ <span class="float-right">
+ <a class="hide_me" href="#">[% l('Export List') %]</a>
</span>
</div>
<div class="clear-both"></div>
+ [% IF ctx.success_renewals %]
+ <div class="renew-summary">
+ [% l("Successfully renewed [_1] item(s)", ctx.success_renewals) %]
+ </div>
+ [% END %]
+ [% IF ctx.failed_renewals %]
+ <div class="renew-summary red">
+ [% l("Failed to renew [_1] item(s)", ctx.failed_renewals) %]
+ </div>
+ [% END %]
<div id='checked_main'>
- <table cellpadding='0' cellspacing='0' border='0'
- style="padding:8px 0px 6px 0px;">
+ <form method="POST" id="circ-form"
+ onsubmit="return confirm('[% l("Are you sure you wish to renew the selected item(s)?") %]');">
+ <table cellpadding='0' cellspacing='0' class="opac-auto-097b">
<tr>
<td>
- <a href="#">Renew Selected Titles</a>
+ <select name="action">
+ <option value="renew">[% l('Renew Selected Titles') %]</option>
+ </select>
</td>
<td style="padding-left:9px;">
- <a class="hide_me" href="#"><img
- alt="Save"
- src="[% ctx.media_prefix %]/images/save-btn.png" /></a>
+ <input type="image"
+ alt="[% l('Go') %]" title="[% l('Go') %]"
+ src="[% ctx.media_prefix %]/images/go-btn.png" /></a>
</td>
<td style="padding-left:5px;">
<a href="#"><img alt="Renewing Help"
border='0'>
<tr>
<td width="1%" style="padding-left:10px;">
- <input type="checkbox" id="check_all_checked" />
+ <input type="checkbox" id="check_all_checked"
+ onclick="var inputs=document.getElementsByTagName('input'); for (i = 0; i < inputs.length; i++) { if (inputs[i].name == 'circ' && !inputs[i].disabled) inputs[i].checked = this.checked;}"
+ />
</td>
<td width="40%" style="padding-left:5px;">
<span title="Click to sort" style="cursor:pointer;">
<div id="acct_checked_temp">
<table cellpadding='0' cellspacing='0' border='0'
style="margin-top:5px;">
+ [% FOR circ IN ctx.circs;
+ attrs = {marc_xml => circ.marc_xml};
+ PROCESS get_marc_attrs args=attrs; %]
<tr>
<td width="1%" style="padding-left:10px;" valign="top">
- <input type="checkbox" name="check_all_checked" />
+ <input type="checkbox" name="circ"
+ [% IF circ.circ.renewal_remaining < 1; l('disabled="disabled"'); END %]
+ value="[% circ.circ.id %]" />
</td>
<td width="40%"
style="padding-left:5px;padding-bottom:10px;"
name="author">
- <a href="#" name="title"></a>
+ <a href="[% ctx.opac_root %]/record/[% circ.circ.target_copy.call_number.record.id %]" name="[% l('Catalog record') %]">[% attrs.title %]</a>
+ [% IF attrs.author %] /
+ <a href="[% ctx.opac_root %]/results?query=au:[% attrs.author | replace('[,\.:;]', '') | url %]">[% attrs.author %]</a>
+ [% END %]
+ </td>
+ <td width="8%" name="renewals" align="center">
+ [% circ.circ.renewal_remaining %]
</td>
- <td width="8%" name="renewals" align="center"></td>
<td width="13%" style="padding-left:5px;"
- name="due_date"></td>
- <td width="16%" name="barcode"></td>
- <td width="22%" name="call_number"></td>
+ name="due_date">
+ [% date.format(ctx.parse_datetime(circ.circ.due_date),DATE_FORMAT) %]
+ </td>
+ <td width="16%" name="barcode">
+ [% circ.circ.target_copy.barcode %]
+ </td>
+ <td width="22%" name="call_number">
+ [% circ.circ.target_copy.call_number.label %]
+ </td>
+ </tr>
+ [% IF circ.renewal_response AND
+ circ.renewal_response.textcode != 'SUCCESS' %]
+ <tr>
+ <td colspan="6">[%# XXX colspan="0" does not work in IE %]
+ <span class="failure-text" title="[% circ.renewal_response.textcode %] / [% circ.renewal_response.payload.fail_part %]">
+ [% circ.renewal_response.desc || circ.renewal_response.payload.fail_part || circ.renewal_response.textcode %]
+ </span>
+ </td>
</tr>
+ [% END;
+ END %]
</table>
</div>
</div>
+ </form>
</div>
<div id='checked_hist' class="hide_me" style="padding-top:8px;">
<table id="acct_checked_hist_header" cellpadding='0' cellspacing='0'
</tr>
</tbody>
</table>
- </div>
- <div id='myopac_renew_success' class='hide_me'>[% l("item(s) successfully renewed") %]</div>
- <span class='hide_me' id='myopac_renew_confirm'>[% l("Are you sure you wish to renew the selected item(s)?") %]</span>
<span class='hide_me' id='myopac_renew_fail'>[% l("The system is unable to renew the selected item at this time. This usually means the item is needed to fulfill a hold. Please see a librarian for further help.") %]</span>
<span class='hide_me' id='myopac_renew_fail2'>[% l("Library policy prevents the renewal of this item at this time. Please see a librarian for further details.") %]</span>
</div>
-[% WRAPPER "default/opac/parts/base.tt2" +
+[% PROCESS "default/opac/parts/header.tt2";
+ PROCESS "default/opac/parts/marc_misc.tt2";
+ WRAPPER "default/opac/parts/base.tt2" +
"default/opac/parts/myopac/base.tt2";
myopac_page = "holds" %]
<div id='myopac_holds_div'>
- <div id="acct_holds_tabs" style="padding-bottom: 12px;color:#666;">
+ <div id="acct_holds_tabs" style="padding-bottom: 12px;color:#666;" class="hide_me">
<div class="align selected" id="holds_label">
<img src="[% ctx.media_prefix %]/images/sub_holds_on.jpg" />
</div>
</div>
<div class="header_middle">
<span id="acct_holds_header" style="float:left;">
- Current Items on Hold
+ [% IF CGI.param("available");
+ l("Items Ready for Pickup");
+ ELSE;
+ l("Current Items on Hold");
+ END
+ %]
</span>
<span style="float:right;">
<a class="hide_me" href="#">Export List</a>
</div>
<div class="clear-both"></div>
<div id='holds_main'>
- <table cellpadding='0' cellspacing='0' border='0'
- style="padding:8px 0px 6px 0px;">
+ <form method="POST">
+ <table cellpadding='0' cellspacing='0' class="opac-auto-097">
<tr>
<td width="1">
- <select id="acct_holds_actions">
+ <select name="action" id="acct_holds_actions">
<option id='myopac_holds_actions_none' value=''>
-- [% l("Actions for selected holds") %] --
</option>
- <option value='freeze'>
+ <option value='suspend'>
[% l("Suspend") %]
</option>
- <option value='thaw'>
+ <option value='activate'>
[% l("Activate") %]
</option>
- <option value='thaw_date'>
+ <!-- XXX maybe later <option value='thaw_date'>
[% l("Set Active Date") %]
- </option>
+ </option> -->
<option value='cancel'>
[% l("Cancel") %]
</option>
</select>
</td>
<td width="1" style="padding-left:9px;">
- <a href="#"><img
- alt="Save"
- src="[% ctx.media_prefix %]/images/save-btn.png" /></a>
+ <input type="image"
+ alt="[% l('Go') %]" title="[% l('Go') %]"
+ src="[% ctx.media_prefix %]/images/go-btn.png" />
</td>
<td width="1" style="padding-left:5px;">
<a href="#"><img
src="[% ctx.media_prefix %]/images/question-mark.png" /></a>
</td>
<td align="right">
+ [% l("Show") %]
+ [% IF CGI.param("available") %]
+ <a href="holds">[% l('all') %]</a> |
+ <strong>[% l("only available") %]</strong>
+ [% ELSE %]
+ <strong>[% l("all") %]</strong> |
+ <a href="holds?available=1">[% l("only available") %]</a>
+ [% END %]
+ [% l("holds") %]
<select class="hide_me" id="holds_sort">
<option value="">-- Sort By --</option>
<option value="title">Title</option>
<table cellpadding='0' cellspacing='0' border='0' width="100%">
<tbody id="holds_temp_parent">
+ [% FOR hold IN ctx.holds;
+ attrs = {marc_xml => hold.marc_xml};
+ PROCESS get_marc_attrs args=attrs %]
<tr id="acct_holds_temp" name="acct_holds_temp"
class="acct_holds_temp">
<td width="36" align="center" style="text-align:center;">
- <input type="checkbox" name="check_all_holds" />
+ <input type="checkbox" name="hold_id" value="[% hold.hold.hold.id %]" />
</td>
<td width="138">
<div style="margin-top:10px;margin-bottom:10px;">
- <a href="#" name="myopac_holds_title_link"></a>
+ <a href="[% ctx.opac_root %]/record/[% hold.hold.bre.id %]">[% attrs.title | html %]</a>
</div>
</td>
<td width="123">
- <div style="margin-top:10px;margin-bottom:10px;"
- name="myopac_holds_author"></div>
+ <div style="margin-top:10px;margin-bottom:10px;">
+ <a href="[% ctx.opac_root %]/results?query=au:[% attrs.author | replace('[,\.:;]', '') | url %]">[% attrs.author | html %]</a>
+ </div>
</td>
<td width="64">
- <div style="width:26px;height:23px;margin-top:6px;margin-bottom:6px;"
- name="myopac_holds_formats">
+ <div style="width:26px;height:23px;margin-top:6px;margin-bottom:6px;">
+ [% IF attrs.format_icon %]
+ <img src="[% ctx.media_prefix %]/images/[% attrs.form_icon %]"
+ title="[% attrs.format %]" alt="[% attrs.format %]" />
+ [% ELSE;
+ attrs.format;
+ END %]
</div>
</td>
<td width="136">
- <span name="hold_pickup_lib_span"></span>
- <span name="hold_pickup_lib"></span>
+ [% ctx.find_aou(hold.hold.hold.pickup_lib).name %]
</td>
<td width="104">
- <input
+ <!-- <input
title="Enter a date (e.g. 10/21/2010)"
class="hide_me" style="width:91px;"
- name="activate_box" type="text" />
- <span name="activate_date"></span>
+ name="activate_box" type="text" /> -->
+ [% IF hold.hold.hold.frozen == 't' AND
+ hold.hold.hold.thaw_date;
+ date.format(ctx.parse_datetime(hold.hold.hold.thaw_date), DATE_FORMAT);
+ END %]
</td>
<td width="106">
- <input title="Enter a date (e.g. 10/21/2010)"
+ <!-- <input title="[% l('Enter a date (e.g. 10/21/2010)') %]"
class="hide_me" style="width:91px;"
- name="hold_expires_box" type="text" />
- <span name="hold_expires"></span>
+ name="hold_expires_box" type="text" />-->
+ [% IF hold.hold.hold.expire_time;
+ date.format(ctx.parse_datetime(hold.hold.hold.expire_time), DATE_FORMAT);
+ END %]
</td>
<td width="95">
- <select name="hold_active_sel"
+ <!-- <select name="hold_active_sel"
style="width:90px;" class="hide_me">
<option value="f">Active</option>
<option value="t">Suspended</option>
- </select>
- <span name="hold_active"></span>
+ </select> -->
+ [% l(hold.hold.hold.frozen == 'f' ? 'Active' : 'Suspended') %]
</td>
<td width="110">
<div name="acct_holds_status"
style="margin-top:10px;margin-bottom:10px;">
- <span class="hide_me"
- name="hold_ready_expire"></span>
+ [%
+ IF hold.hold.status == 4;
+ l("Available");
+ ELSIF hold.hold.estimated_wait;
+ l("Estimated wait (days): ");
+ hold.hold.estimated_wait;
+ ELSIF hold.hold.status == 3;
+ l("In Transit");
+ ELSIF hold.hold.status < 3;
+ l("Waiting for copy");
+ END;
+ %]
</div>
</td>
<td width="62" align="right"
class="hide_me">Back</a>
</td>
</tr>
+ [% END %]
</tbody>
</table>
+ </form>
</div>
<div id='holds_hist_table' class="hide_me">testing...</div>
-[% WRAPPER "default/opac/parts/base.tt2" +
+[% PROCESS "default/opac/parts/header.tt2";
+ WRAPPER "default/opac/parts/base.tt2" +
"default/opac/parts/myopac/base.tt2";
myopac_page = "lists" %]
<div
-[% WRAPPER "default/opac/parts/base.tt2" +
+[% PROCESS "default/opac/parts/header.tt2";
+ PROCESS "default/opac/parts/marc_misc.tt2";
+ WRAPPER "default/opac/parts/base.tt2" +
"default/opac/parts/myopac/base.tt2";
myopac_page = "main" %]
<div id='myopac_summary_div' style="padding:0px;">
<img src="[% ctx.media_prefix %]/images/acct_sum_fines_br.png" />
</div>
</div>
- Fines: <span id="myopac_sum_fines_bal">$0.00</span><br />
+ [% l('Fines:') %]
+ <span id="myopac_sum_fines_bal" class='[% ctx.fines.balance_owed ? "red" : ""%]'>
+ [% money(ctx.fines.balance_owed) %]
+ </span><br />
<a class="hide_me" href="#" id="pay_fines_btn1"><img
- alt="Pay Fines"
+ alt="[% l('Pay Fines') %]"
onmouseover="this.src='[% ctx.media_prefix %]/images/pay-fines-btn-hover.png';"
onmouseout="this.src='[% ctx.media_prefix %]/images/pay-fines-btn.png';"
src="[% ctx.media_prefix %]/images/pay-fines-btn.png"
<table width="100%" cellspacing="0" cellpadding="0">
<tr>
<td>
- Items Currently Checked out
+ [% l("Items Currently Checked out") %]
<span id="myopac_sum_checked" class="view_link">
- (0)
+ ([% ctx.user_stats.checkouts.total_out %])
</span>
</td>
<td align="right" class="view_link">
- <a href="circs">View All</a>
+ <a href="[% ctx.opac_root %]/myopac/circs">[% l("View All") %]</a>
</td>
</tr>
</table>
<td>
Items Currently on Hold
<span id="myopac_sum_holds" class="view_link">
- (0)
+ ([% ctx.user_stats.holds.total %])
</span>
</td>
<td align="right" class="view_link">
- <a href="holds">View All</a>
+ <a href="[% ctx.opac_root %]/myopac/holds">View All</a>
</td>
</tr>
</table>
<td>
Items ready for pickup
<span id="myopac_sum_pickup" class="view_link">
- (0)
+ ([% ctx.user_stats.holds.ready %])
</span>
</td>
<td align="right" class="view_link">
- <a href="#">View All</a>
+ <a href="[% ctx.opac_root %]/myopac/holds?available=1">View All</a>
</td>
</tr>
</table>
</tr>
</thead>
<tbody id='myopac_fines_summary_tbody'>
- <tr id='myopac_fines_summary_loading'>
- <td>[% l("Loading...") %]</td>
- </tr>
- <tr id='myopac_fines_summary_row' class='hide_me'>
- <td id='myopac_fines_summary_total' >[% l("\$") %]</td>
- <td id='myopac_fines_summary_paid' >[% l("\$") %]</td>
- <td id='myopac_fines_summary_balance' style='color:red;font-weight: bold;'>[% l("\$") %]</td>
+ <tr id='myopac_fines_summary_row'>
+ <td id='myopac_fines_summary_total'>[% money(ctx.fines.total_owed) %]</td>
+ <td id='myopac_fines_summary_paid'>[% money(ctx.fines.total_paid) %]</td>
+ <td id='myopac_fines_summary_balance' style='color:red;font-weight: bold;'>[% money(ctx.fines.balance_owed) %]</td>
</tr>
</tbody>
</table>
<span>will continue to accrue fines until the checked out item is returned.</span>
</div>
-->
- <!-- Table for circulation transactions only -->
- <div id='myopac_circ_trans_div' class='hide_me'>
+ [% IF ctx.fines.circulation.size > 0 %]
+ <div id='myopac_circ_trans_div'>
<br/><hr/><br/>
<table width='100%' class='data_grid data_grid_center'
id='myopac_circ_trans_table'>
<thead>
- <!--<tr><td colspan='10' style='padding: 6px'><b>[% l("Overdue Materials") %]</b></td></tr>-->
<tr>
<td colspan='10' style='padding: 6px'>
- <b>Fines</b>
+ <strong>[% l("Fines") %]</strong>
</td>
</tr>
<tr>
<td>[% l("Due Date") %]</td>
<td>[% l("Date Returned") %]</td>
<td>[% l("Balance Owed") %]</td>
- <td align="center" nowrap="nowrap"
- style="white-space:nowrap;">
- <label for="pay_fines_box1">Pay Fines</label>
- <br />
+ <td nowrap="nowrap" style="white-space:nowrap;">
<input id="pay_fines_box1" checked="checked"
type="checkbox"
- title="Click to (un)select all fines" />
+ title="[% l('Click to (un)select all fines') %]" />
+ <label for="pay_fines_box1">[% l('Pay Fines') %]</label>
</td>
</tr>
</thead>
<tbody id='myopac_circ_trans_tbody'>
+ [% FOR f IN ctx.fines.circulation;
+ attrs = {marc_xml => f.marc_xml};
+ PROCESS get_marc_attrs args=attrs %]
<tr id='myopac_circ_trans_row'>
<td>
- <a class='classic_link' name='myopac_circ_trans_title'> </a>
+ <a class='classic_link'
+ href="[% ctx.opac_root %]/record/[% f.xact.circulation.target_copy.call_number.record.id %]">[% attrs.title %]</a>
+ </td>
+ <td>
+ <a class="classic_link"
+ href="[% ctx.opac_root %]/results?query=au:[% attrs.author | replace('[,\.:;]', '') | url %]">[% attrs.author %]</a>
+ </td>
+ <td name='myopac_circ_trans_start'>
+ [% date.format(
+ ctx.parse_datetime(
+ f.xact.circulation.xact_start
+ ), DATE_FORMAT
+ ) %]
+ </td>
+ <td name='myopac_circ_trans_due'>
+ [% date.format(
+ ctx.parse_datetime(
+ f.xact.circulation.due_date
+ ), DATE_FORMAT
+ ) %]
</td>
- <td name='myopac_circ_trans_author'> </td>
- <td name='myopac_circ_trans_start'> </td>
- <td name='myopac_circ_trans_due'> </td>
<td name='myopac_circ_trans_finished'>
- <span style='color:red;'>[% l("(fines accruing)") %]</span>
+ [% IF f.xact.circulation.checkin_time;
+ date.format(
+ ctx.parse_datetime(
+ f.xact.circulation.checkin_time
+ ), DATE_FORMAT
+ );
+ ELSE %]
+ <!-- XXX TODO fines aren't really accruing
+ if circ has hit maxfines. more clarity
+ here? -->
+ <span class="red">[% l('(fines accruing)') %]</span>
+ [% END %]
</td>
<td>
- <span style='color: red; font-weight: bold;'
- name='myopac_circ_trans_balance'>[% l("\$") %]</span>
+ <strong class="red">
+ [% money(f.xact.balance_owed) %]
+ </strong>
</td>
- <td align="center">
+ <td>
<input type="checkbox" checked="checked"
- name="selector" title="pay this fine" />
+ name="selector"
+ title="[% l('Pay this fine') %]" />
</td>
</tr>
+ [% END %]
</tbody>
</table>
</div>
+ [% END %]
+ [% IF ctx.fines.grocery.size > 0 %]
<!-- Table for all non-circulation transactions -->
- <div id='myopac_trans_div' class='hide_me'>
+ <div id='myopac_trans_div'>
<br/>
<hr style="border-bottom:none;*height:0px;" color="#dcdbdb" />
<br/>
<td width='16%'>[% l("Billing Type") %]</td>
<td width='4%' align="center" nowrap="nowrap"
style="white-space:nowrap;">
- <label for="pay_fines_box2">Pay Fines</label>
- <br />
<input id="pay_fines_box2" checked="checked"
type="checkbox"
- title="Click to (un)select all fines" />
+ title="[% l('Click to (un)select all fines') %]" />
+ <label for="pay_fines_box2">[% l("Pay Fines") %]</label>
</td>
</tr>
</thead>
<tbody id='myopac_trans_tbody'>
+ [% FOR f IN ctx.fines.grocery %]
<tr id='myopac_trans_row'>
- <td name='myopac_trans_start'> </td>
- <td name='myopac_trans_last_payment'> </td>
- <td name='myopac_trans_init_amount'>
- [% l("\$") %]
+ <td>[% date.format(
+ ctx.parse_datetime(f.xact.xact_start),
+ DATE_FORMAT
+ ) %]</td>
+ <td>
+ [% IF f.xact.last_payment_ts;
+ date.format(
+ ctx.parse_datetime(
+ f.xact.last_payment_ts
+ ), DATE_FORMAT
+ );
+ END %]
</td>
- <td name='myopac_trans_total_paid'>
- [% l("\$") %]
+ <td>[% money(f.xact.total_owed) %]</td>
+ <td>[% money(f.xact.total_paid) %]</td>
+ <td class="red">
+ <strong>
+ [% money(f.xact.balance_owed) %]
+ </strong>
</td>
- <td style='color:red; font-weight: bold;'>
- <span name='myopac_trans_balance_recur'
- class='hide_me'> * </span>
- <span name='myopac_trans_balance'>
- [% l("\$") %]
- </span>
- </td>
- <td name='myopac_trans_bill_type'></td>
- <td align="center">
+ <td>[% f.xact.last_billing_type %]</td>
+ <td>
<input type="checkbox" name='selector'
- title='pay this fine' checked="checked" />
+ title='[% l("Pay this fine") %]'
+ checked="checked" />
</td>
</tr>
+ [% END %]
</tbody>
</table>
</div>
- <a href="#"><img alt="Pay Fines"
+ [% END %]
+ <a href="#"><img alt="[% l('Pay Fines') %]"
onmouseover="this.src='[% ctx.media_prefix %]/images/pay-fines-btn-hover.png';"
src="[% ctx.media_prefix %]/images/pay-fines-btn.png"
style="position:relative;top:5px;" /></a>
-[% WRAPPER "default/opac/parts/base.tt2" +
+[% PROCESS "default/opac/parts/header.tt2";
+ WRAPPER "default/opac/parts/base.tt2" +
"default/opac/parts/myopac/base.tt2";
myopac_page = "prefs" %]
<div id='myopac_prefs_div'>
<tr>
<td align='center'>
[% l("Search Library") %]<br /><br />
- [% INCLUDE "default/opac/parts/libselect.tt2" %]
+ <span id='depth_selector_span'>
+ [% PROCESS "default/opac/parts/org_selector.tt2";
+ PROCESS build_org_selector name='loc' value=loc %]
+ </span>
+ <span id='lib_selector_span'>
+ <a id='lib_selector_link' class='classic_link'
+ href='#'>[% l("Choose a library to search") %]</a>
+ </span>
<br /><br />
<span>[% l("Limit to Available") %]</span>
<input type='checkbox' id='opac.result.limit2avail'/>
<span>[% l("Item Form") %]</span>
</td>
<td align='left' class="hide_me">
- <select multiple='multiple' size='3'
- id='adv_global_item_form'>
+ <select multiple='multiple' size='3' id='adv_global_item_form'>
</select>
</td>
<td align='right'>
<span>[% l("Item Type") %]</span><br />
</td>
<td align='left'>
- <select multiple='multiple' size='3'
- id='adv_global_item_type' class='hide_me'>
+ <select multiple='multiple' size='3' id='adv_global_item_type' class='hide_me'>
</select>
- <select multiple='multiple' size='3'
- id='adv_global_item_type_basic'>
+ <select multiple='multiple' size='3' id='adv_global_item_type_basic'>
<option value=''>[% l("All Formats") %]</option>
+
+ <!-- These will be replaced w/ SVF. Leave them hard-coded for now -->
<option value='a'>Book</option>
<option value='i'>Book on cassette</option>
<option value='n'>Book on CD</option>
</td>
<td align='right' class="hide_me">
<span>[% l("Literary Form") %]</span>
- <a id='adv_global_lit_form_link_adv'
- class='classic_link adv_adv_link'
- href='#'>[% l("Advanced") %]</a>
-
- <a id='adv_global_lit_form_link_basic'
- class='hide_me classic_link adv_adv_link'
- href='#'>[% l("Basic") %]</a>
+ <a class='classic_link adv_adv_link' href='#'>[% l("Advanced") %]</a>
+ <a class='hide_me classic_link adv_adv_link' href='#'>[% l("Basic") %]</a>
</td>
<td align='left' class="hide_me">
- <select multiple='multiple' size='3'
- id='adv_global_lit_form' class='hide_me'>
- </select>
- <select multiple='multiple' size='3'
- id='adv_global_lit_form_basic'>
- <option value='0 '>
- [% l("Non Fiction") %]
- </option>
- <option value='1'>
- [% l("Fiction") %]
- </option>
+ <select multiple='multiple' size='3' id='adv_global_lit_form' class='hide_me'> </select>
+ <select multiple='multiple' size='3' id='adv_global_lit_form_basic'>
+ <option value='0 '>[% l("Non Fiction") %]</option>
+ <option value='1'>[% l("Fiction") %]</option>
</select>
</td>
<td align='right'>
href='#'>[% l("Basic") %]</a>
</td>
<td align='left' class="hide_me">
- <select multiple='multiple' size='3'
- id='adv_global_audience' class='hide_me'>
+ <select multiple='multiple' size='3' id='adv_global_audience' class='hide_me'>
</select>
- <select multiple='multiple' size='3'
- id='adv_global_audience_basic'>
+ <select multiple='multiple' size='3' id='adv_global_audience_basic'>
<option value='e '>[% l("Adult") %]</option>
<option value='abcdj'>[% l("Juvenile") %]</option>
<option value='fg '>[% l("General") %]</option>
<span>[% l("Bib Level") %]</span>
</td>
<td align='left' class="hide_me">
- <select multiple='multiple' size='3'
- id='adv_global_bib_level'>
+ <select multiple='multiple' size='3' id='adv_global_bib_level'>
</select>
</td>
</tr>
</td>
<td align='left'>
<select id='adv_global_pub_date_type'>
- <option value='equals' selected='selected'>
- [% l("Is") %]
- </option>
- <option value='before'>
- [% l("Before") %]
- </option>
- <option value='after'>
- [% l("After") %]
- </option>
- <option value='between'>
- [% l("Between") %]
- </option>
+ <option value='equals' selected='selected'>[% l("Is") %] </option>
+ <option value='before'>[% l("Before") %]</option>
+ <option value='after'>[% l("After") %]</option>
+ <option value='between'>[% l("Between") %]</option>
</select>
<div style='margin-top:5px;'>
- <input id='adv_global_pub_date_1' type='text'
- size='4' maxlength='4'/>
- <span id='adv_global_pub_date_2_span'
- class='hide_me'>
- [% l("and") %] <input
- id='adv_global_pub_date_2' type='text'
- size='4' maxlength='4'/>
+ <input id='adv_global_pub_date_1' type='text' size='4' maxlength='4'/>
+ <span id='adv_global_pub_date_2_span' class='hide_me'>
+ [% l("and") %] <input id='adv_global_pub_date_2' type='text' size='4' maxlength='4'/>
</span>
</div>
</td>
<tr class='border_4_2'>
<td align="left" colspan='2'>
<!-- XXX TODO make a real form, and make this a real submitter -->
- <img src="[% ctx.media_prefix %]/images/search_btn.gif" alt="Search" style="cursor:pointer;" />
+ <img src="[% ctx.media_prefix %]/images/search_btn.gif" alt="[% l('Search') %]" style="cursor:pointer;" />
<!-- XXX TODO make a real form, and make this a real resetter -->
<a href="javascript:;" style="position: relative; top: -9px;">Reset Form</a>
<!-- XXX TODO make a real form, and make a real submitter (quick
submit, FKA advGenericSearch() -->
<img src="[% ctx.media_prefix %]/images/search_btn.gif"
- alt="Search" id="adv_quick_submit" style="cursor:pointer;" />
+ alt="[% l('Search') %]" id="adv_quick_submit" style="cursor:pointer;" />
</div>
</div>
</div>
</tr>
<tr name='crow' class='hide_me'>
<td colspan='4' align='center'>
- <a href='javascript:void(0);'
- class='classic_link'>[% l("close") %]</a>
+ <a href='javascript:void(0);' class='classic_link'>[% l("close") %]</a>
</td>
</tr>
</tbody>
</table>
<div id='adv_marc_submit' class='adv_quick_search_submit'>
<a style='margin-right: 4px; position:relative;top:-10px;'
- class='classic_link'
- href='javascript:advAddMARC();'>[% l("Add Row") %]</a>
+ class='classic_link' href='javascript:advAddMARC();'>[% l("Add Row") %]</a>
<!-- XXX TODO make a real form, and make a real submitter (FKA
advMARCRun()) -->
- <img alt="Search" src="[% ctx.media_prefix %]/images/search_btn.gif"
- style="cursor:pointer;" />
+ <img alt="Search" src="[% ctx.media_prefix %]/images/search_btn.gif" style="cursor:pointer;" />
</div>
</div>
<!-- ****************** end: advanced_global.xml ***************************** -->
<html xmlns='http://www.w3.org/1999/xhtml' lang='[% ctx.locale %]' xml:lang='[% ctx.locale %]'>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+ [% IF ctx.authtime %]
+ <meta http-equiv="refresh" content="[% ctx.authtime %];[% ctx.home_page %]">
+ [% END %]
<link rel="stylesheet" type="text/css"
href="[% ctx.media_prefix %]/css/skin/default/opac/semiauto.css" />
<link rel="stylesheet" type="text/css"
href="[% ctx.media_prefix %]/css/skin/default/opac/style.css" />
- <link rel="stylesheet" type="text/css"
- href="[% ctx.media_prefix %]/css/skin/default/opac/contentslider.css" />
- <title>Catalog - King County Library - [% ctx.page_title %]</title>
- <link rel="unapi-server" type="application/xml" title="unAPI"
- href="/opac/extras/unapi" />
- [% BLOCK html_head; END; # provide a default that can be overridden %]
- [% PROCESS html_head %]
+ <title>[% l('Catalog - King County Library - [_1]', ctx.page_title) %]</title>
+ [%# <!-- is this needed? --><link rel="unapi-server"
+ type="application/xml" title="unAPI" href="/opac/extras/unapi" />%]
+ [% BLOCK html_head; END; # provide a default that can be overridden -%]
+ [%- PROCESS html_head -%]
</head>
<body>
[% content %]
-<!-- ****************** format_selector.xml ***************************** -->
-<select id='format_selector'>
+[% name = name || "item_type";
+ id = id || "format_selector" %]
+<select id='[% id %]' name='[% name %]'>
<option value=''>[% l("All Formats") %]</option>
- <option value='a'>Book</option>
- <option value='i'>Book on cassette</option>
- <option value='n'>Book on CD</option>
- <option value='x'>Download music</option>
- <option value='y'>Download video</option>
- <option value='h'>DVD</option>
- <option value='w'>eBook - Audio</option>
- <option value='v'>eBook - Text</option>
- <option value='e'>Equipment</option>
- <option value='f'>Films</option>
- <option value='o'>Kit</option>
- <option value='q'>Large print</option>
- <option value='b'>Magazine</option>
- <option value='d'>Microform</option>
- <option value='k'>Music cassette</option>
- <option value='j'>Music CD</option>
- <option value='l'>Music LP</option>
- <option value='p'>Newspaper</option>
- <option value='t'>Online</option>
- <option value='u'>Player</option>
- <option value='c'>Printed music</option>
- <option value='2'>Read along with cassette</option>
- <option value='5'>Read along with CD</option>
- <option value='c'>Scores</option>
- <option value='m'>Software</option>
- <option value='g'>Video</option>
- <option value='r'>3-D Object</option>
- <option value='z'>Map</option>
- <option value='s'>Slide set</option>
+[% FOR o IN item_types %]
+ <option value='[% o.code %]'[% value == o.code ? ' selected="selected"' : ''%]>[% o.name %]</option>
+[%- END %]
<!--
<option value='at'>[% l("Books") %]</option>
<option value='at-d'>[% l("Large Print Books") %]</option>
<option value='m'>[% l("Electronic Resources") %]</option>
-->
</select>
-<!-- ****************** end: format_selector.xml ***************************** -->
--- /dev/null
+[%- USE money = format(l('$%.2f'));
+ USE date;
+ USE CGI;
+ USE EGI18N;
+ SET DATE_FORMAT = l('%m/%d/%Y');
+
+ item_types = [ # XXX KCLS-specific
+ {'code' => 'a', 'name' => 'Book', 'image' => 'media_book.jpg'},
+ {'code' => 'i', 'name' => 'Book on cassette', 'image' => 'media_bookoncasset.jpg'},
+ {'code' => 'n', 'name' => 'Book on CD', 'image' => 'media_bookoncd.jpg'},
+ {'code' => 'x', 'name' => 'Download music', 'image' => 'media_downloadmusic.jpg'},
+ {'code' => 'y', 'name' => 'Download video', 'image' => 'media_downloadvideo.jpg'},
+ {'code' => 'h', 'name' => 'DVD', 'image' => 'media_dvd.jpg'},
+ {'code' => 'w', 'name' => 'eBook - Audio', 'image' => 'media_eaudio.jpg'},
+ {'code' => 'v', 'name' => 'eBook - Text', 'image' => 'media_ebooktext.jpg'},
+ {'code' => 'e', 'name' => 'Equipment', 'image' => 'media_equipment.jpg'},
+ {'code' => 'f', 'name' => 'Films', 'image' => 'media_films.jpg'},
+ {'code' => 'o', 'name' => 'Kit', 'image' => 'media_kit.jpg'},
+ {'code' => 'q', 'name' => 'Large print', 'image' => 'media_largeprint.jpg'},
+ {'code' => 'b', 'name' => 'Magazine', 'image' => 'media_magazines.jpg'},
+ {'code' => 'd', 'name' => 'Microform', 'image' => 'media_microform.jpg'},
+ {'code' => 'k', 'name' => 'Music cassette', 'image' => 'media_musiccassette.jpg'},
+ {'code' => 'j', 'name' => 'Music CD', 'image' => 'media_musiccd.jpg'},
+ {'code' => 'l', 'name' => 'Music LP', 'image' => 'media_musicrecord.jpg'},
+ {'code' => 'p', 'name' => 'Newspaper', 'image' => 'media_newspaper.jpg'},
+ {'code' => 't', 'name' => 'Online', 'image' => 'media_online.jpg'},
+ {'code' => 'u', 'name' => 'Player', 'image' => 'media_eaudio.jpg'},
+ {'code' => 'c', 'name' => 'Printed music / scores', 'image' => 'media_printedmusic.jpg'},
+ {'code' => '2', 'name' => 'Read along with cassette', 'image' => 'media_cassettewithbook.jpg'},
+ {'code' => '5', 'name' => 'Read along with CD', 'image' => 'media_cdwithbook.jpg'},
+ {'code' => 'm', 'name' => 'Software', 'image' => 'media_software.jpg'},
+ {'code' => 'g', 'name' => 'Video', 'image' => ''},
+ {'code' => 'r', 'name' => '3-D Object', 'image' => 'media_3dobject.jpg'},
+ {'code' => 'z', 'name' => 'Map', 'image' => 'media_map.jpg'},
+ {'code' => 's', 'name' => 'Slide set', 'image' => 'media_slide.jpg'}
+ ];
+
+ icon_by_mattype = {};
+ FOR o IN item_types;
+ code = o.code;
+ icon_by_mattype.$code = o.image;
+ END;
+
+-%]
+++ /dev/null
-[% # XXX TODO probably put this BLOCK somewhere else so it can be used widely.
- # Org Unit Selector Widget :
- # PROCESS build_org_selector id='selector-id' name='selector-name'
- BLOCK build_org_selector;
- first_run = 0;
- IF !org_unit;
- org_unit = ctx.aou_tree;
- first_run = 1;
-%]
- <select id='[% id %]' name='[% name %]'>
- [% END %]
- <option value='[% org_unit.id %]' [% IF org_unit.id == value %] selected='selected' [% END %]>
- [%
- pad = org_unit.ou_type.depth * 2;
- FOR idx IN [0..pad]; ' '; END;
- org_unit.name;
- %]
- </option>
- [% FOR child IN org_unit.children; PROCESS build_org_selector org_unit = child; END %]
- [% IF first_run %]
- </select>
- [% END %]
-[% END %]
-
-<!-- ****************** libselect.xml ***************************** -->
- <span id='depth_selector_span'>
- [% PROCESS build_org_selector name='loc' value=loc %]
- </span>
- <span id='lib_selector_span'>
- <a id='lib_selector_link' class='classic_link'
- href='#'>[% l("Choose a library to search") %]</a>
- </span>
-<!-- ****************** end: libselect.xml ***************************** -->
-<!-- ****************** login.xml ***************************** -->
+<!-- TODO: MOVE INTO SEPARATE FORGOT-PASSWORD PAGE
+
<div class="hide_me">
<div class='login_text color_1' style='padding: 4px; text-align: center;'>
<span>[% l("Login") %]</span>
</tbody>
</table>
+
<span id='pw_no_match' class='hide_me'>[% l("Passwords do not match") %]</span>
<span id='pw_update_successful' class='hide_me'>[% l("Password successfully updated") %]</span>
<span id='pw_not_strong' class='hide_me'>
[% l("The password provided is not strong enough.") %]
- [% l("The password must be at least 7 characters in length,
- contain at least one letter (a-z/A-Z),
- and contain at least one number.") %]
+ [% l("The password must be at least 7 characters in length, contain at least one letter (a-z/A-Z), and contain at least one number.") %]
</span>
-<span id='patron_card_inactive_alert' class='hide_me'>[% l("The barcode used to login is marked as inactive. Please contact your local library.") %]</span>
-<span id='patron_inactive_alert' class='hide_me'>[% l("This account has been deactivated. Please contact your local library.") %]</span>
-<span id='patron_login_failed' class='hide_me'>[% l("Login failed. The username or password provided was not valid. Ensure Caps-Lock is off and try again or contact your local library.") %]</span>
-<div id="login_box">
+ ^== TODO: MOVE INTO SEPARATE FORGOT-PASSWORD PAGE -->
+
+[% IF ctx.login_failed_event %]
+<div id='login-failed-message'>
+[%
+ IF ctx.login_failed_event.textcode == 'PATRON_CARD_INACTIVE';
+ l("The barcode used to login is marked as inactive. Please contact your local library.");
+ ELSIF ctx.login_failed_event.textcode == 'PATRON_INACTIVE';
+ l("This account has been deactivated. Please contact your local library.");
+ ELSE;
+ l("Login failed. The username or password provided was not valid.
+ Ensure Caps-Lock is off and try again or contact your local library.");
+ END;
+%]
+</div>
+[% END %]
+
+<div>
<div style="height:20px;"></div>
- <form id='login_form' method='POST'>
+ <form method='POST'>
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td valign="top" width="676" class="login_boxes left_brain">
width="100%">
<tr>
<td colspan="2" style="padding-bottom: 10px;">
- <h1>Log in to Your Account</h1>
- Please enter the following information:
+ <h1>[% l('Log in to Your Account') %]</h1>
+ [% l('Please enter the following information:') %]
<br /><br />
</td>
</tr>
<tr>
<td width="42%" class="lbl1">
- Library Card Number or Username<br />
+ [% l('Library Card Number or Username') %]
+ <br />
<span class="lbl2">
- Please include leading zeros and no spaces.
- <br /> Example: 0026626051</span>
+ [% l('Please include leading zeros and no spaces.') %]
+ <br/>
+ [% l('Example: 0026626051') %]
+ </span>
<br /><br />
</td>
<td width="58%" valign="top">
<div class="input_bg">
- <input type="text" name="username"
- id="login_username" />
+ <input type="text" id="username_field" name="username"/>
</div>
</td>
</tr>
</tr>
<tr>
<td valign="top" class="lbl1">
- PIN Number or Password<br />
- <span class="lbl2">If this is your first time
- logging in, please enter<br />
- the last 4 digits of your phone number.<br />
- Example: 0926</span>
+ [% l('PIN Number or Password') %]<br />
+ <span class="lbl2">
+ [% | l('<br/>', '<br/>') %]
+ If this is your first time logging in, please enter [_1] the last 4 digits of your phone number. [_2] Example: 0926
+ [% END %]
+ </span>
</td>
<td valign="top">
<div class="input_bg">
- <input name="password" type="password" id="login_password" />
+ <input name="password" type="password" />
</div>
<div style="padding-top:7px;">
- <input type='hidden'
- name='redirect_to'
- value='[% CGI.param('redirect_to') || ctx.referer | replace('^http:', 'https:') %]' />
- <input class="hide_me" type="checkbox"
- id="remember_me" name="remember_me" />
- <label class="hide_me"
- style="position:relative;top:-2px;"
- for="remember_me">Remember Me?</label>
+ [%
+ # If no redirect is offered or it's leading us back to the
+ # login form, redirect the user to My Account
+ redirect = CGI.param('redirect_to') || ctx.referer;
+ IF !redirect OR redirect.match(ctx.path_info _ '$');
+ redirect = CGI.url('-full' => 1) _ '/opac/myopac/main';
+ END;
+ redirect = redirect | replace('^http:', 'https:');
+ %]
+ <input type='hidden' name='redirect_to' value='[% redirect %]'/>
+ <input type="checkbox" name="persist" /> [% l('Remember Me?') %]
</div>
<div style="padding-top:14px;">
- <a href="#" id="login_button"><img
- alt="Log in" src="[% ctx.media_prefix %]/images/login-btn2.png" /></a>
- <input class="hide_me"
- id="login_form_submit" type="submit" />
- <a href="#"
+ <input type='image' alt="[% l('Log in') %]" src="[% ctx.media_prefix %]/images/login-btn2.png" />
+ <!-- TODO
+ <a href="reset_password"
style="position:relative;top:-13px;left:2px;font-size:10px;">Forgot your PIN?</a>
+ -->
</div>
</td>
</tr>
<br /><br />
</td>
<td><div style="width:10px;"></div></td>
- <td class="login_boxes right_brain" align="center"
- valign="top" width="291">
- <a href="http://www.kcls.org/about/contact/"><img
- src="[% ctx.media_prefix %]/images/questions.png"
- alt="Questions?" style="margin-top:29px;" /></a>
+ <td class="login_boxes right_brain" align="center" valign="top" width="291">
+
+ <a href="http://www.kcls.org/about/contact/"><img
+ src="[% ctx.media_prefix %]/images/questions.png" alt="[% l('Questions?') %]" style="margin-top:29px;" /></a>
+
<div style="width:182px;color:black;padding:5px 25px;">
- Visit our FAQs section for answers to common questions
- about how to use your account.
+ [% l('Visit our FAQs section for answers to common questions about how to use your account.') %]
</div>
+
<a href="http://www.kcls.org/usingthelibrary/catalog_help/index.cfm#FAQs"><img
- alt="FAQs" src="[% ctx.media_prefix %]/images/faqs-btn.png"
- style="margin-top:13px;" /></a>
+ alt="[% l('FAQs') %]" src="[% ctx.media_prefix %]/images/faqs-btn.png" style="margin-top:13px;" /></a>
</td>
</tr>
</table>
</form>
<div class="clear-both"></div>
</div>
-<!-- ****************** end: login.xml ***************************** -->
args.author = xml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
args.publisher = xml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
args.pubdate = xml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
+ args.edition = xml.findnodes('//*[@tag="250"]/*[@code="a"]').textContent ||
+ xml.findnodes('//*[@tag="534"]/*[@code="b"]').textContent ||
+ xml.findnodes('//*[@tag="775"]/*[@code="b"]').textContent;
+ phys = xml.findnodes(
+ '//*[@tag="300"]/*[@code="a" or @code="b" or @code="c" or @code="e"]'
+ );
+ phys_content = [];
+ FOR p IN phys; phys_content.push(p.textContent); END;
+ args.phys_desc = phys_content.join("");
+
# clean up the ISBN
args.isbn_clean = args.isbn.replace('\ .*', '');
# KCLS-specific stuff; needs to change
args.mattype = xml.findnodes('//*[@tag="998"]/*[@code="d"]').textContent;
args.kcls_cn = xml.findnodes('//*[@tag="092" or @tag="099"]/*').textContent;
-
+ mattype = attrs.mattype;
+ args.format = ctx.find_citm(mattype).value;
+ args.format_icon = icon_by_mattype.$mattype;
END;
-
- icon_by_mattype = { # XXX KCLS-specific
- "a" => "media_book.jpg",
- "b" => "media_magazines.jpg",
- "c" => "media_printedmusic.jpg",
- "d" => "media_microform.jpg",
- "e" => "media_equipment.jpg",
- "f" => "media_films.jpg",
- "g" => "",
- "h" => "media_dvd.jpg",
- "i" => "media_bookoncassette.jpg",
- "j" => "media_musiccd.jpg",
- "k" => "media_musiccassette.jpg",
- "l" => "media_musicrecord.jpg",
- "m" => "media_software.jpg",
- "n" => "media_bookoncd.jpg",
- "o" => "media_kit.jpg",
- "p" => "media_newspaper.jpg",
- "q" => "media_largeprint.jpg",
- "r" => "media_3dobject.jpg",
- "s" => "media_slide.jpg",
- "t" => "media_online.jpg",
- "u" => "media_eaudio.jpg",
- "v" => "media_ebooktext.jpg",
- "w" => "media_eaudio.jpg",
- "x" => "media_downloadmusic.jpg",
- "y" => "media_downloadvideo.jpg",
- "z" => "media_map.jpg",
- "2" => "media_cassettewithbook.jpg",
- "5" => "media_cdwithbook.jpg"
- };
%]
</div>
</div>
<div id="main-content">
- <div class="common-full-pad"></div>
[% content %]
- <div class="clear-both"></div>
+ <div class="common-full-pad"></div>
</div>
</div>
--- /dev/null
+[%
+ BLOCK build_org_selector_options; %]
+ <option value='[% walker.id %]' [% IF walker.id == value %] selected='selected' [% END %]>
+ [%
+ pad = walker.ou_type.depth * 2;
+ FOR idx IN [0..pad]; ' '; END;
+ walker.name;
+ %]
+ </option>
+ [% FOR child IN walker.children;
+ PROCESS build_org_selector_options walker=child value=value;
+ END;
+ END;
+
+ # XXX TODO probably put this BLOCK somewhere else so it can be used widely.
+ # Org Unit Selector Widget :
+ # PROCESS build_org_selector id='selector-id' name='selector-name'
+ BLOCK build_org_selector;
+%]
+ <select id='[% id %]' name='[% name %]'>
+ [% PROCESS build_org_selector_options walker=(org_unit || ctx.aou_tree) value=value %]
+ </select>
+[% END %]
+[% USE CGI;
+ PROCESS "default/opac/parts/marc_misc.tt2";
+ attrs = {marc_xml => ctx.marc_xml};
+ PROCESS get_marc_attrs args=attrs;
+%]
<div>
<div id='xulholds_box' class='hide_me canvas' style='margin-top: 6px;'>
+ <!-- XXX TODO staff will need this to work ("advanced" hold placement)
+ later -->
<center>
<table class='data_grid' style='margin-top: 20px;'>
<tbody>
<input type='text' id='xul_recipient_barcode' />
</td>
<td>
- <input type='submit' value='[% l("Submit") %]' id='xul_recipient_barcode_submit' />
+ <input type='submit' value='[% l("Submit") %]'
+ id='xul_recipient_barcode_submit' />
</td>
<td>
<input type='submit' value='[% l("Cancel") %]' />
</td>
<td>
- <input type='submit' value='[% l("Place hold for my account") %]' id='xul_recipient_me' />
+ <input type='submit'
+ value='[% l("Place hold for my account") %]'
+ id='xul_recipient_me' />
</td>
</tr>
</tbody>
<b>[% l("Checking for possibility of hold fulfillment...") %]</b>
</div>
<div id='holds_box' class='canvas' style='margin-top: 6px;'>
- <br/>
- <h1>Place Hold</h1>
- <p>
- You would like to place a hold on
- <strong>"<span id="holds_title"></span>"</strong>
- to be picked up at
- <strong>"<span id="holds_pickup_lib"></span>"</strong>.<br />
- If this is correct, press <strong>SUBMIT</strong>.</p>
- <p>
- If you would like to change the library pick up location, select
- from the
- <select style="width:200px;" id="holds_org_selector"></select><br />
- Location dropdown menu.
- </p>
- <p>If you use the Traveling Library Center (TLC) and ABC Express
- services, please select "Outreach" to have the item delivered
- during your scheduled visit.</p>
- <a href="#" id="holds_submit"><img
- alt="Submit" src="[% ctx.media_prefix %]/images/btnSubmit.png" /></a>
-
- <a href="#" id="holds_cancel"><img
- alt="Cancel" src="[% ctx.media_prefix %]/images/btnCancel.png" /></a>
+ [% IF ctx.hold_success %]
+ <div><big><strong>[% l("Hold was successfully placed"); %]</strong></big></div>
+ [% ELSIF ctx.hold_failed %]
+ <div><big><strong>[% l("Hold was not successfully placed"); %]</strong></big></div>
+ [% IF ctx.hold_failed_event %]
+ <div>
+ <strong>[% l('Problem:') %]</strong>
+ <span title="[% ctx.hold_failed_event.textcode %]">
+ <em>[% ctx.hold_failed_event.desc ||
+ ctx.hold_failed_event.payload.fail_part ||
+ ctx.hold_failed_event.textcode %]</em>
+ </span>
+ </div>
+ [% END;
+ ELSE %]
+ <form method="POST">
+ <br/>
+ <input type="hidden" name="hold_target"
+ value="[% CGI.param('hold_target') | html %]" />
+ <input type="hidden" name="hold_type"
+ value="[% CGI.param('hold_type') | html %]" />
+ <input type="hidden" name="redirect_to"
+ value="[% ctx.referer | replace('^http:', 'https:') | html %]" />
+ <h1>Place Hold</h1>
+ <p>
+ [% | l(attrs.title, ctx.find_aou(ctx.default_pickup_lib).name) %]
+ You would like to place a hold on <strong><q>[_1]</q></strong> to be picked up at [_2].
+ If this is correct, press <strong>SUBMIT</strong>.
+ [% END %]
+ </p>
+ <p>
+ [% l('If you would like to change the library pick up location, select from the location dropdown menu.') %]
+ <br class="clear-both" />
+ [% PROCESS "default/opac/parts/org_selector.tt2";
+ PROCESS build_org_selector name='pickup_lib' value=ctx.default_pickup_lib %]
+ </p>
+ <p>
+ [% |l %]If you use the Traveling Library Center (TLC) and ABC Express
+ services, please select "Outreach" to have the item delivered
+ during your scheduled visit.[% END %]
+ </p>
+ <input type="image" name="submit" value="submit" title="[% l('Submit') %]"
+ alt="[% l('Submit') %]" src="[% ctx.media_prefix %]/images/btnSubmit.png" />
+
+ <a href="javascript:history.go(-1);" id="holds_cancel"><img
+ alt="[% l('Cancel') %]" src="[% ctx.media_prefix %]/images/btnCancel.png" /></a>
+ </form>
<br /><br />
- <p>* If you need your item today, and it is checked in at your
+ <p>
+ [% |l %]* If you need your item today, and it is checked in at your
library, please place your hold and then call your library to set it
aside. Placing a hold without calling the library will increase your
- wait time.<br /><a href="#">Library phone numbers.</a>
+ wait time.[% END %]
+ <br /><a href="#">[% l('Library phone numbers.') %]</a>
</p>
- <p>* For best possible service, we recommend keeping a printed copy of
- your most recent holds list.</p>
+ <p>
+ [% |l %]* For best possible service, we recommend keeping
+ a printed copy of your most recent holds list.[% END %]
+ </p>
+ [% END %] <!-- ctx.hold_success -->
<table width='90%' border="1" class="hide_me">
<tbody>
<tr>
<td class='holds_cell'>[% l("Contact email address") %]:</td>
<td class='holds_cell' id='holds_email'>
<span class='hide_me' id='holds.no_email'>
- ([% l("Patron has no configured email address)" %])<br/>
+ ([% l("Patron has no configured email address") %])<br/>
([% l("See") %] <a class='classic_link' id='holds.no_email.my_account'>[% l("My Account") %]</a> [% l("for setting your email address") %])
</span>
<span class='hide_me' id='holds.no_email.xul'>
[% l("The phone number does not have the correct format. The expected format is XXX-YYY-ZZZZ") %]
</span>
<span class='hide_me' id='hold_not_allowed'>
- [% l("No items were found that could fulfill the requested holds. It's possible that choosing a different format will result in a successful hold. It is also possible that you have exceeded the number of allowable holds. For further information, please consult your local librarian.") %]
+ [% |l %]No items were found that could fulfill the requested holds.
+ It's possible that choosing a different format will result in a successful hold.
+ It is also possible that you have exceeded the number of allowable holds.
+ For further information, please consult your local librarian.[% END %]
</span>
</div>
<div id="anonListTable" class="hide_me" style="margin-top: 6px;">
- <select id="holdsCacheSel" class="hide_me"></select><br />
- <a href="#">Place hold on selected</a><br />
- <a href="#">Remove selected</a>
-
- <table id="temp_list_holds" cellpadding='0' cellspacing='0' border='0'
- style="margin-top:10px;">
- <tr>
- <td width="1%" style="padding-left:10px;">
- <input type='checkbox' title='Select All'
- id='anon_selector' />
- </td>
- <td width="1%">
- </td>
- <td width="98%" style="padding-left:40px;">
- <strong>Title</strong>
- </td>
- </tr>
- </table>
- <table width='100%' style="margin-left:7px;margin-bottom:10px;">
- <thead>
- <tr><td width='20'></td><td width='30'></td><td></td></tr>
- </thead>
- <tbody id="anonListParent">
- <tr id="anonListTemp">
- <td><input type='checkbox' name='anon_selector' /></td>
- <td name="curr_row"></td>
- <td name="title"></td>
+ <select id="holdsCacheSel" class="hide_me"></select><br />
+ <a href="#">Place hold on selected</a><br />
+ <a href="#">Remove selected</a>
+
+ <table id="temp_list_holds" cellpadding='0' cellspacing='0' border='0'
+ style="margin-top:10px;">
+ <tr>
+ <td width="1%" style="padding-left:10px;">
+ <input type='checkbox' title='Select All'
+ id='anon_selector' />
+ </td>
+ <td width="1%">
+ </td>
+ <td width="98%" style="padding-left:40px;">
+ <strong>Title</strong>
+ </td>
</tr>
- </tbody>
- </table>
- <a href="#">Back to search results</a>
+ </table>
+ <table width='100%' style="margin-left:7px;margin-bottom:10px;">
+ <thead>
+ <tr><td width='20'></td><td width='30'></td><td></td></tr>
+ </thead>
+ <tbody id="anonListParent">
+ <tr id="anonListTemp">
+ <td><input type='checkbox' name='anon_selector' /></td>
+ <td name="curr_row"></td>
+ <td name="title"></td>
+ </tr>
+ </tbody>
+ </table>
+ <a href="#">Back to search results</a>
</div>
<span class='hide_me' id='format_words'>
<span name='m'>[% l("Electronic Resources") %]</span>
</span>
- <span class='hide_me' id='holds_explain_adv'>[% l("If you wish to broaden the scope of your hold to include other versions of this title, select the formats that would be acceptable. The first available copy will be sent to you.") %]</span>
+ <span class='hide_me' id='holds_explain_adv'>
+ [% |l %]If you wish to broaden the scope of your hold to include other versions of this title,
+ select the formats that would be acceptable. The first available copy will be sent to you.[% END %]
+ </span>
+
<span class='hide_me' id='holds_pick_good_org'>[% l("Please select a physical location where your hold can be delivered.") %]</span>
<span class='hide_me' id='hold_dup_exists'>[% l("A hold already exists on the requested item.") %]</span>
<span class='hide_me' id='hold_dup_exists_override'>[% l("A hold already exists on the requested item. Would you like to create the hold anyway?") %]</span>
- <span id='hold_failed_patron_barred' class='hide_me'>[% l("PATRON BARRED. Please see any notes in the \"Staff Notes\" section of your \"My Account\" page or contact your local library.") %]</span>
- <span id='invalid_hold' class='hide_me'>[% l("This hold is no longer valid. It's likely that the target for the hold was deleted from the system. Please cancel this hold and place a new one.") %]</span>
- <span id='holds_invalid_recipient' class='hide_me'>[% l("The patron barcode entered as the hold recipient is invalid.") %]</span>
+ <span id='hold_failed_patron_barred' class='hide_me'>
+ [% |l %]PATRON BARRED. Please see any notes in the "Staff Notes" section of your
+ "My Account" page or contact your local library.[% END %]
+ </span>
+
+ <span id='invalid_hold' class='hide_me'>
+ [% |l %]This hold is no longer valid. It's likely that the target for the hold was
+ deleted from the system. Please cancel this hold and place a new one.[% END %]
+ </span>
+ <span id='holds_invalid_recipient' class='hide_me'>[% l("The patron barcode entered as the hold recipient is invalid.") %]</span>
</div>
<!-- ****************** page_rdetail.xml ***************************** -->
+[% record = ctx.record;
+ attrs = {marc_xml => ctx.marc_xml};
+ PROCESS "default/opac/parts/marc_misc.tt2";
+ PROCESS get_marc_attrs args=attrs %]
<div id='canvas_main' class='canvas'>
- <div id="rdetail_header">
+ <div id="rdetail_header" class="hide_me">[%#
+ XXX Does it make sense for now to even have this section? Why
+ should the record detail page be aware of the ongoing search?
+ The user can use the back button to go back to their search results
+ like on any other website. %]
<div style="float:left;">
Search Results
<span id="rdetail_result_count" class="hide_me">
<td width='33%'>[% l("Barcode") %]</td>
<td>[% l("Status") %]</td>
<td>[% l("Location") %]</td>
- <td name='age_protect_label' class='hide_me'>[% l("Age Hold Protection") %]</td>
- <td name='create_date_label' class='hide_me'>[% l("Create Date") %]</td>
- <td name='holdable_label' class='hide_me'>[% l("Holdable") %]</td>
- <td name='due_date_label' class='hide_me'>[% l("Due Date") %]</td>
+ [% IF ctx.is_staff %]
+ <td>[% l("Age Hold Protection") %]</td>
+ <td>[% l("Create Date") %]</td>
+ <td>[% l("Holdable") %]</td>
+ [% END %]
+ <td>[% l("Due Date") %]</td>
</tr>
</thead>
<tbody name='copies_tbody' class='copy_details_table' width='100%'>
+ [% FOR copy_info IN ctx.copies %]
<tr name='copies_row'>
+ <td>[% copy_info.barcode %]</td>
+ <td>[% copy_info.copy_status %]</td>
+ <td>[% copy_info.copy_location %]</td>
+ [% IF ctx.is_staff %]
+ <td>[% copy_info.age_protect %]</td>
+ <td>[% date.format(ctx.parse_datetime(copy_info.create_date), DATE_FORMAT) %]</td>
+ <td>
+ [% IF copy_info.holdable == 't' AND
+ copy_info.location_holdable == 't' AND
+ copy_info.status_holdable == 't';
+ l('Yes');
+ ELSE;
+ l('No');
+ END;
+ %]
+ </td>
+ [% END %]
+ <td>[% date.format(ctx.parse_datetime(copy_info.create_date), DATE_FORMAT) %]</td>
+ </tr>
+ [% END %]
+
+ <!-- XXX keeping for now for reference...
<td>
<span name='barcode'> </span>
<a class='hide_me classic_link copy_more_info'
<span name='copy_due_date'> </span>
</td>
</tr>
+ -->
<tr name='copy_extras_row' class='hide_me'>
<td colspan='10'>
<tbody id="rdetail_details_tbody">
<tr>
<td width="90" valign="top" id="rdetail_image_cell">
- <a id='rdetail_img_link' href='${ident.large}'><img
- alt="Image of item" style='border: none;' id='rdetail_image'
- src='${ident.small}' /></a>
+ [% ident = attrs.isbn_clean || attrs.upc; IF ident; %]
+ <a id='rdetail_img_link' href='[% ctx.media_prefix %]/opac/extras/ac/jacket/large/[% ident %]'><img
+ alt="Image of item" id='rdetail_image'
+ src='[% ctx.media_prefix %]/opac/extras/ac/jacket/small/[% ident %]' /></a>
+ [% END %]
<br />
<div class='jacket_attrib hide_me' id='rdetail.jacket_attrib_div'>
<div>[% l("Image provided by") %]</div>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top">
- <span class="rdetail_item" id='rdetail_title'></span><br />
- <span style="color:#545454;">[% l("Author") %]: </span>
- <em><a title='[% l("Perform an author search") %]' id='rdetail_author'></a></em>
+ <span id='rdetail_title'>[% attrs.title %]</span><br />
+ [% IF attrs.author %]
+ <span style="color:#545454;">[% l("Author") %]:</span>
+ <em><a title='[% l("Perform an author search") %]'
+ id='rdetail_author'
+ href="[% ctx.opac_root %]/results?query=author%3a[% attrs.author | replace('[,\.:;]', '') | uri %]&loc=[% loc %]">[% attrs.author %]</a>
+ </em>
+ [% END %]
</td>
<td align="right" valign="top" nowrap="nowrap" style="white-space:nowrap;">
<div style="width:230px;text-align:left;margin-top:3px;">
<div style="float:right;">
<div style="border-bottom:1px dotted #ccc;padding-top:10px;"
class="rdetail_aux_utils">
- <a href="place_hold" id="rdetail_place_hold"><img
+ <a href="[% ctx.opac_root %]/place_hold?hold_target=[% record.id %]&hold_type=T" id="rdetail_place_hold"><img
src="[% ctx.media_prefix %]/images/green_check.png" alt="place hold" />
<span style="position:relative;top:-3px;left:3px;">Place Hold</span></a>
</div>
<table border="0" cellpadding="0" width="100%">
<tr>
<td nowrap='nowrap' valign="top">
- <strong id="rdetail_isbn_lbl" class="">[% l("ISBN") %]</strong>
+ [% IF attrs.isbn %]<strong id="rdetail_isbn_lbl">[% l("ISBN") %]</strong>[% END %]
</td>
- <td valign="top" id='rdetail_isbn'></td>
+ <td valign="top" id='rdetail_isbn'>[% attrs.isbn %]</td>
<td nowrap='nowrap' valign="top">
- <strong id="rdetail_phys_lbl" class="">[% l("Physical Description") %]</strong>
+ [% IF attrs.phys_desc %]<strong id="rdetail_phys_lbl">[% l("Physical Description") %]</strong>[% END %]
</td>
- <td valign="top" id='rdetail_physical_desc'></td>
+ <td valign="top" id='rdetail_physical_desc'>[% attrs.phys_desc %]</td>
</tr>
<tr>
<td nowrap='nowrap' valign="top">
- <strong id="rdetail_ed_lbl" class="">[% l("Edition") %]</strong>
+ <strong id="rdetail_ed_lbl">[% IF attrs.edition; l("Edition"); END %]</strong>
</td>
- <td valign="top" id='rdetail_edition'></td>
+ <td valign="top" id='rdetail_edition'>[% attrs.edition %]</td>
<td nowrap='nowrap' valign="top">
- <strong id="rdetail_form_lbl" class="">[% l("Format") %]</strong>
+ <strong id="rdetail_form_lbl">[% IF attrs.format; l("Format"); END %]</strong>
</td>
<td valign="top">
- <img alt="Format" id='' class='tor_pic hide_me' />
- <span id='rdetail_tor'></span>
+ [% IF attrs.format %]
+ <img alt="Format" class='tor_pic'
+ title="[% attrs.format %]"
+ src="[% ctx.media_prefix _ '/images/' _ attrs.format_icon %]" />
+ [% END %]
</td>
</tr>
<tr>
<td nowrap='nowrap' valign="top">
- <strong id="rdetail_pubdate_lbl" class="">[% l("Publication Date") %]</strong>
+ <strong id="rdetail_pubdate_lbl">[% IF attrs.pubdate; l("Publication Date"); END %]</strong>
</td>
- <td valign="top" id='rdetail_pubdate'></td>
+ <td valign="top" id='rdetail_pubdate'>[% attrs.pubdate %]</td>
<td nowrap='nowrap' valign="top">
- <strong id="rdetail_sum_lbl" class="">Summary</strong>
+ <strong id="rdetail_sum_lbl">Summary</strong>
</td>
<td valign="top" id='rdetail_abstract'></td>
</tr>
<tr>
<td nowrap='nowrap' valign="top">
- <strong id="rdetail_pub_lbl" class="">[% l("Publisher") %]</strong>
+ <strong id="rdetail_pub_lbl">[% IF attrs.publisher; l("Publisher"); END %]</strong>
</td>
- <td valign="top" id='rdetail_publisher'></td>
+ <td valign="top" id='rdetail_publisher'>[% attrs.publisher %]</td>
<td nowrap='nowrap' valign="top">
- <strong id="rdetail_sub_lbl" class="">[% l("Subjects") %]</strong>
+ [%# XXX TODO see kcls' drawMarcSubjects() in rdetail.js %]
+ <strong id="rdetail_sub_lbl">[% l("Subjects") %]</strong>
</td>
<td valign="top"></td>
</tr>
</div>
</td>
<td width="1" valign="top" align="right" style="white-space:nowrap;">
- <a href="place_hold" id=''><img alt="Place Hold"
+ <a href="[% ctx.opac_root %]/place_hold?hold_target=[% record.id %]&hold_type=T"><img alt="Place Hold"
src="[% ctx.media_prefix %]/images/place_hold.gif" /></a>
<a href="#" id="rd_reviews_and_more" target="_blank"><img
alt="Reviews and More" src="[% ctx.media_prefix %]/images/reviews.gif" /></a>
--- /dev/null
+<!-- ****************** filtersort.xml ***************************** -->
+ <select class="results_header_sel" id='opac.result.sort' onchange='searchBarSubmit(true);'>
+ <option selected='selected' value=''>[% l("Sort Results by Relevance") %]</option>
+ <optgroup label='[% l("Sort Results by Title") %]'>
+ <option id='opac.result.title.a2z' label='&common.a2z.titla;' value='title.asc'>[% l("Title: A to Z") %]</option>
+ <option id='opac.result.title.z2a' label='&common.z2a.titla;' value='title.desc'>[% l("Title: Z to A") %]</option>
+ </optgroup>
+ <optgroup label='[% l("Sort Results by Author") %]'>
+ <option id='opac.result.author.a2z' label='[% l("Author: A to Z") %]' value='author.asc'>[% l("Author: A to Z") %]</option>
+ <option id='opac.result.author.z2a' label='[% l("Author: Z to A") %]' value='author.desc'>[% l("Author: Z to A") %]</option>
+ </optgroup>
+ <optgroup label='[% l("Sort Results by Publication Date") %]'>
+ <option id='opac.result.pubdate.new2old' label='[% l("Date: Newest to Oldest") %]'
+ value='pubdate.desc'>[% l("Date: Newest to Oldest") %]</option>
+ <option id='opac.result.pubdate.old2new' label='[% l("Date: Oldest to Newest") %]'
+ value='pubdate.asc'>[% l("Date: Oldest to Newest") %]</option>
+ </optgroup>
+ </select>
+<!-- ****************** end: filtersort.xml ***************************** -->
IF ctx.result_stop > ctx.hit_count; ctx.result_stop = ctx.hit_count; END;
%]
<div style="height: 10px;"></div>
-<div id="results_header_nav1">
+[% BLOCK results_count_header %]
+<div class="results_header_nav1">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td class="h1" width="116">Search Results</td>
- <td valign="bottom" nowrap="nowrap" width="320"
- style="white-space:nowrap;" id="result_numbers1">
+ <td valign="bottom" nowrap="nowrap" class="result_number">
[% l("Results") %]
<strong>[% ctx.result_start %]</strong>
-
</span>
</td>
<td align="right" valign="bottom">
- <span id='start_end_links_span'>
+ <span class='start_end_links_span'>
[% IF page > 0 %]
- <a class='search_page_nav_link' id='prev_link'
+ <a class='search_page_nav_link'
href="[% np_link _ '&page=' _ (page - 1) %]"
title='[% l("Previous page") %]'>
<span class="nav_arrow_fix">◄</span> Previous
</a>
[% END %]
- <span class='hide_me' id='result_info_div'
+ <span class='hide_me'
style='padding-left: 11px; padding-right:11px;'>
- <span id="nav_pages"></span>
+ <span></span>
</span>
[% IF (page + 1) < page_count %]
- <a class='search_page_nav_link' id='next_link'
+ <a class='search_page_nav_link'
href="[% np_link _ '&page=' _ (page + 1) %]"
title='[% l("Next page") %]'>
Next <span class="nav_arrow_fix">►</span>
</tr>
</table>
</div>
+[% END %]
+[% ctx.results_count_header = PROCESS results_count_header;
+ ctx.results_count_header %]