Merge branch 'opac-tt-poc' of git+ssh://yeti.esilibrary.com/home/evergreen/evergreen...
authorberick <berick@esilibrary.com>
Wed, 16 Feb 2011 16:26:56 +0000 (11:26 -0500)
committerberick <berick@esilibrary.com>
Wed, 16 Feb 2011 16:26:56 +0000 (11:26 -0500)
80 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/c-apps/oils_auth.c
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/authority.pm
Open-ILS/src/perlmods/lib/OpenILS/Utils/CStoreEditor.pm
Open-ILS/src/perlmods/lib/OpenILS/Utils/MFHD/test/mfhd.t
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb/I18NFilter.pm [new file with mode: 0644]
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/040.schema.asset.sql
Open-ILS/src/sql/Pg/080.schema.money.sql
Open-ILS/src/sql/Pg/100.circ_matrix.sql
Open-ILS/src/sql/Pg/110.hold_matrix.sql
Open-ILS/src/sql/Pg/reporter-schema.sql
Open-ILS/src/sql/Pg/upgrade/0482.schema.fix_matchpoint_unique.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0483.dynamic_weights_fix.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0484.sql.pg-90-compat.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0485.schema.reporter_strip_isbns.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0486.schema.mccp-order-number.sql [new file with mode: 0644]
Open-ILS/src/support-scripts/authority_control_fields.pl [changed mode: 0644->0755]
Open-ILS/src/support-scripts/settings-tester.pl
Open-ILS/web/css/skin/default/opac/semiauto.css
Open-ILS/web/css/skin/default/opac/style.css
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/web/templates/default/opac-poc/place_hold.tt2
Open-ILS/web/templates/default/opac/advanced.tt2
Open-ILS/web/templates/default/opac/home.tt2
Open-ILS/web/templates/default/opac/login.tt2
Open-ILS/web/templates/default/opac/myopac/circs.tt2
Open-ILS/web/templates/default/opac/myopac/holds.tt2
Open-ILS/web/templates/default/opac/myopac/lists.tt2
Open-ILS/web/templates/default/opac/myopac/main.tt2
Open-ILS/web/templates/default/opac/myopac/prefs.tt2
Open-ILS/web/templates/default/opac/parts/advanced/search.tt2
Open-ILS/web/templates/default/opac/parts/base.tt2
Open-ILS/web/templates/default/opac/parts/format_selector.tt2
Open-ILS/web/templates/default/opac/parts/header.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/parts/libselect.tt2 [deleted file]
Open-ILS/web/templates/default/opac/parts/login/form.tt2
Open-ILS/web/templates/default/opac/parts/marc_misc.tt2
Open-ILS/web/templates/default/opac/parts/myopac/base.tt2
Open-ILS/web/templates/default/opac/parts/org_selector.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/parts/place_hold.tt2
Open-ILS/web/templates/default/opac/parts/record/body.tt2
Open-ILS/web/templates/default/opac/parts/record/cn_details.tt2
Open-ILS/web/templates/default/opac/parts/record/summary.tt2
Open-ILS/web/templates/default/opac/parts/result/filtersort.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/parts/result/header.tt2
Open-ILS/web/templates/default/opac/parts/result/table.tt2
Open-ILS/web/templates/default/opac/parts/searchbar.tt2
Open-ILS/web/templates/default/opac/parts/topnav.tt2
Open-ILS/web/templates/default/opac/parts/utils.tt2
Open-ILS/web/templates/default/opac/place_hold.tt2
Open-ILS/web/templates/default/opac/record.tt2
Open-ILS/web/templates/default/opac/results.tt2
Open-ILS/xul/staff_client/Makefile.am
Open-ILS/xul/staff_client/external/developers.js
Open-ILS/xul/staff_client/server/cat/marcedit.js
Open-ILS/xul/staff_client/server/serial/editor_base.js
Open-ILS/xul/staff_client/server/serial/manage_items.xul
Open-ILS/xul/staff_client/server/serial/sbsum_editor.js
Open-ILS/xul/staff_client/server/serial/scap_editor.js
Open-ILS/xul/staff_client/server/serial/sdist_editor.js
Open-ILS/xul/staff_client/server/serial/siss_editor.js
Open-ILS/xul/staff_client/server/serial/siss_editor.xul
Open-ILS/xul/staff_client/server/serial/sisum_editor.js
Open-ILS/xul/staff_client/server/serial/sitem_editor.js
Open-ILS/xul/staff_client/server/serial/sssum_editor.js
Open-ILS/xul/staff_client/server/serial/sstr_editor.js
Open-ILS/xul/staff_client/server/serial/ssub_editor.js
Open-ILS/xul/staff_client/server/skin/global.css

index c974308..0f13006 100644 (file)
@@ -1571,6 +1571,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <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" />
@@ -3674,11 +3675,11 @@ SELECT  usr,
 
        <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"/>
@@ -3701,11 +3702,11 @@ SELECT  usr,
 
        <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"/>
@@ -3728,11 +3729,11 @@ SELECT  usr,
 
        <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"/>
index eb20fb7..4e7b20b 100644 (file)
@@ -807,8 +807,17 @@ int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
 
 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;
 
@@ -828,7 +837,10 @@ int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
                        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
index 6b3e7be..3a7efd0 100644 (file)
@@ -34,7 +34,7 @@ use OpenILS::Application::Actor::Stage;
 
 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/;
 
@@ -1592,6 +1592,8 @@ sub user_opac_vitals {
         ->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,
index 52e76bc..67b2dc1 100644 (file)
@@ -1138,9 +1138,6 @@ sub mark_item {
         ($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;
 
@@ -1150,9 +1147,6 @@ sub mark_item {
         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;
@@ -1173,6 +1167,8 @@ sub mark_item {
                $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');
@@ -1190,6 +1186,12 @@ sub mark_item {
 
        $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;
 
index d26df13..49d925f 100644 (file)
@@ -3260,6 +3260,7 @@ __PACKAGE__->register_method(
     }
 );
 
+# XXX Need to add type I (and, soon, type P) holds to these counts
 sub rec_hold_count {
     my($self, $conn, $target_id) = @_;
 
index 66de1ab..d472820 100644 (file)
@@ -143,7 +143,7 @@ sub make_payments {
     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;
 
@@ -302,6 +302,7 @@ sub make_payments {
                 $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 {
@@ -346,6 +347,7 @@ sub make_payments {
         }
 
         $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'};
index 96ca5d2..4406125 100644 (file)
@@ -2095,6 +2095,8 @@ __PACKAGE__->register_method(
 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) };
 }
@@ -2109,6 +2111,8 @@ sub biblio_search_isbn_batch {
        $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 )
                } ) {
index f21530f..58cbc01 100644 (file)
@@ -1,3 +1,6 @@
+use strict;
+use warnings;
+
 package OpenILS::Application::Storage::Publisher::authority;
 use base qw/OpenILS::Application::Storage::Publisher/;
 use vars qw/$VERSION/;
@@ -36,8 +39,6 @@ sub validate_tag {
                        my $sf = $$search{subfield};
                        my $term = naco_normalize($$search{term}, $sf);
 
-                       $tag = [$tag] if (!ref($tag));
-
                        push @values, $t, $sf, $term;
 
                        push @selects,
index ff298f4..03237ca 100644 (file)
@@ -115,11 +115,19 @@ sub log {
 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 {
@@ -177,6 +185,12 @@ sub authtoken {
        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;
index 35d86cd..e6dcf44 100644 (file)
@@ -4,6 +4,7 @@ use strict;
 use warnings;
 use Data::Dumper;
 use Test::More 'no_plan';
+use File::Basename qw(dirname);
 
 use MARC::Record;
 use OpenILS::Utils::MFHD;
@@ -30,7 +31,8 @@ sub right_answer {
 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);
index 4c27e6a..a7fc0a7 100644 (file)
@@ -14,6 +14,13 @@ use OpenILS::Application::AppUtils;
 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 {
@@ -61,156 +68,67 @@ sub cgi {
 }
 
 
-# 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;
 
@@ -218,14 +136,19 @@ sub load_common {
     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', 
@@ -240,39 +163,50 @@ sub load_common {
     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/;
@@ -285,7 +219,7 @@ sub load_login {
                 -path => '/',
                 -secure => 1,
                 -value => $response->{payload}->{authtoken},
-                -expires => CORE::time + $response->{payload}->{authtime}
+                -expires => ($persist) ? CORE::time + $response->{payload}->{authtime} : undef
             )
         )
     );
@@ -293,14 +227,15 @@ sub load_login {
     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 => '/',
@@ -313,633 +248,5 @@ sub load_logout {
     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;
+
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
new file mode 100644 (file)
index 0000000..5042641
--- /dev/null
@@ -0,0 +1,510 @@
+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
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
new file mode 100644 (file)
index 0000000..71c7bd4
--- /dev/null
@@ -0,0 +1,126 @@
+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;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm
new file mode 100644 (file)
index 0000000..ad9526a
--- /dev/null
@@ -0,0 +1,103 @@
+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;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
new file mode 100644 (file)
index 0000000..14456a7
--- /dev/null
@@ -0,0 +1,118 @@
+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;
index 8f19a07..63836f8 100644 (file)
@@ -50,7 +50,8 @@ sub handler {
     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)})) {
@@ -75,7 +76,8 @@ sub set_text_handler {
         $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(@_); };
 }
 
 
@@ -246,6 +248,8 @@ sub load_locale_handlers {
     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 {}
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb/I18NFilter.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb/I18NFilter.pm
new file mode 100644 (file)
index 0000000..cc931fa
--- /dev/null
@@ -0,0 +1,19 @@
+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;
+
index b0c37b8..857d1a1 100644 (file)
@@ -70,7 +70,7 @@ CREATE TABLE config.upgrade_log (
     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,
index 623b7f1..cb73896 100644 (file)
@@ -390,12 +390,12 @@ CREATE VIEW stats.fleshed_call_number AS
         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
@@ -407,7 +407,7 @@ BEGIN
                 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;
 
@@ -421,12 +421,12 @@ BEGIN
 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
@@ -438,7 +438,7 @@ BEGIN
                 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;
 
@@ -452,12 +452,12 @@ BEGIN
 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
@@ -470,7 +470,7 @@ BEGIN
           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
@@ -483,12 +483,12 @@ BEGIN
 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
@@ -501,7 +501,7 @@ BEGIN
           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
@@ -514,19 +514,19 @@ BEGIN
 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;
 
@@ -534,12 +534,12 @@ BEGIN
 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
@@ -551,7 +551,7 @@ BEGIN
                 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;
@@ -566,12 +566,12 @@ BEGIN
 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
@@ -583,7 +583,7 @@ BEGIN
                 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;
@@ -598,12 +598,12 @@ BEGIN
 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
@@ -616,7 +616,7 @@ BEGIN
           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;
 
@@ -630,12 +630,12 @@ BEGIN
 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
@@ -648,7 +648,7 @@ BEGIN
           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;
 
@@ -662,19 +662,19 @@ BEGIN
 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;
 
index e227a18..805da20 100644 (file)
@@ -630,6 +630,7 @@ CREATE TABLE money.credit_card_payment (
     cc_processor TEXT,
     cc_first_name TEXT,
     cc_last_name TEXT,
+    cc_order_number TEXT,
        expire_month    INT,
        expire_year     INT,
        approval_code   TEXT
index c16adaf..0ed8e67 100644 (file)
@@ -79,6 +79,7 @@ INSERT INTO config.videorecording_format_map VALUES ('z','Other');
 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,
@@ -93,6 +94,7 @@ CREATE TABLE config.circ_matrix_matchpoint (
     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,
@@ -100,14 +102,11 @@ CREATE TABLE config.circ_matrix_matchpoint (
     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 (
@@ -132,7 +131,7 @@ DECLARE
     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;
@@ -155,20 +154,20 @@ BEGIN
 
     -- 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
@@ -212,24 +211,24 @@ BEGIN
             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;
index 84c6b11..1821b1d 100644 (file)
@@ -32,6 +32,7 @@ CREATE TABLE config.hold_matrix_matchpoint (
     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"
@@ -45,16 +46,19 @@ CREATE TABLE config.hold_matrix_matchpoint (
     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$
@@ -66,7 +70,7 @@ DECLARE
     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;
@@ -102,19 +106,19 @@ BEGIN
 
     -- 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
@@ -171,22 +175,22 @@ BEGIN
             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;
index d8f614e..85b5c54 100644 (file)
@@ -126,8 +126,8 @@ SELECT      r.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,
@@ -158,8 +158,8 @@ SELECT  r.id,
     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')
diff --git a/Open-ILS/src/sql/Pg/upgrade/0482.schema.fix_matchpoint_unique.sql b/Open-ILS/src/sql/Pg/upgrade/0482.schema.fix_matchpoint_unique.sql
new file mode 100644 (file)
index 0000000..7382605
--- /dev/null
@@ -0,0 +1,82 @@
+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;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0483.dynamic_weights_fix.sql b/Open-ILS/src/sql/Pg/upgrade/0483.dynamic_weights_fix.sql
new file mode 100644 (file)
index 0000000..0e6827c
--- /dev/null
@@ -0,0 +1,262 @@
+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;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0484.sql.pg-90-compat.sql b/Open-ILS/src/sql/Pg/upgrade/0484.sql.pg-90-compat.sql
new file mode 100644 (file)
index 0000000..6e4a6a1
--- /dev/null
@@ -0,0 +1,308 @@
+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;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0485.schema.reporter_strip_isbns.sql b/Open-ILS/src/sql/Pg/upgrade/0485.schema.reporter_strip_isbns.sql
new file mode 100644 (file)
index 0000000..b1ac0e3
--- /dev/null
@@ -0,0 +1,74 @@
+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;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0486.schema.mccp-order-number.sql b/Open-ILS/src/sql/Pg/upgrade/0486.schema.mccp-order-number.sql
new file mode 100644 (file)
index 0000000..7035953
--- /dev/null
@@ -0,0 +1,7 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0486');
+
+ALTER TABLE money.credit_card_payment ADD COLUMN cc_order_number TEXT;
+
+COMMIT;
old mode 100644 (file)
new mode 100755 (executable)
index 3609ffd..f7c3f55
@@ -26,58 +26,26 @@ use Encode;
 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"));
 
@@ -87,7 +55,7 @@ OpenILS::Utils::CStoreEditor::init();
 
 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( 
@@ -96,6 +64,10 @@ if ($all_records) {
     );
     @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
@@ -444,3 +416,120 @@ foreach my $rec_id (@records) {
     }
     $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
+
index 640b47d..a59dca9 100755 (executable)
@@ -2,22 +2,13 @@
 # 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 = '';
@@ -139,6 +130,9 @@ foreach my $database (@databases) {
        $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'
@@ -180,6 +174,10 @@ foreach my $driver_node (@drivers) {
                        $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;
@@ -430,3 +428,6 @@ Tie::IxHash
 Parse::RecDescent
 SRU
 JSON::XS
+UUID::Tiny
+Business::CreditCard::Object
+Net::Z3950::Simple2ZOOM
index 61355d6..6902837 100644 (file)
@@ -85,7 +85,8 @@
 .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; }
index 1dc531f..faa3f57 100644 (file)
@@ -102,10 +102,47 @@ div.select-wrapper:hover {
 }
 
 #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;
@@ -169,6 +206,12 @@ div.select-wrapper:hover {
        padding-left:0px;
 }
 
+#gold-links-home {
+       margin:auto;
+       width:694px;
+       padding-left:0px;
+}
+
 #util-bar {
        margin:auto;
        width:974px;
@@ -340,6 +383,7 @@ div.select-wrapper:hover {
        font-size: 18px;
 }
 
+#rdetail_image { border: none; }
 #rdetail_image_cell {
        padding-top: 3px;
        padding-right: 10px;
@@ -441,7 +485,7 @@ div.select-wrapper:hover {
 #hp-buttons {
        margin: auto;
        margin-top: 6px;
-       width: 974px;
+       width: 694px; /* 974px; */
 }
 
 #hp-welcome {
@@ -467,7 +511,7 @@ div.select-wrapper:hover {
 
 #hp-banner {
        margin: auto;
-       width:974px;
+       width: 694px; /* formerly 974px */
        height: 213px;
 }
 
@@ -518,12 +562,8 @@ div.select-wrapper:hover {
        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;
@@ -623,22 +663,18 @@ div.select-wrapper:hover {
        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;
 }
 
@@ -652,9 +688,8 @@ div.select-wrapper:hover {
        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; }
@@ -892,3 +927,6 @@ div.select-wrapper:hover {
 #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; }
index f68ed14..a723b5f 100644 (file)
 <!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 &#8595;">
+<!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:">
index 91996ad..bfbee07 100644 (file)
@@ -23,9 +23,9 @@
     <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>
index 6479a53..adb1807 100644 (file)
@@ -1,16 +1,14 @@
-[%  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>
index f6c4ea5..3436a46 100644 (file)
@@ -1,12 +1,13 @@
-[%  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>        
index 3b2e392..4888ce2 100644 (file)
@@ -1,6 +1,7 @@
-[%  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" %]
@@ -9,6 +10,17 @@
         <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 %]
index 8e6cc43..2e1d3a4 100644 (file)
@@ -1,8 +1,10 @@
-[%  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"
@@ -52,7 +65,9 @@
             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>
index c239066..bdda327 100644 (file)
@@ -1,8 +1,10 @@
-[%  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") %] &nbsp; &nbsp;
+                    [% 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 %] &nbsp; &nbsp;
+                    [% 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>
 
index 2123f4b..c93fac5 100644 (file)
@@ -1,4 +1,5 @@
-[%  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
index 1dfaf71..2d7b375 100644 (file)
@@ -1,4 +1,6 @@
-[%  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>
index ea6fb15..0545872 100644 (file)
@@ -1,4 +1,5 @@
-[%  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'>
index 44fb6b3..9872b85 100644 (file)
     <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;" />
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
         <!-- 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 ***************************** -->
index 881b46e..c83cb88 100644 (file)
@@ -2,17 +2,18 @@
 <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 %] 
index 5c3904c..7ebd072 100644 (file)
@@ -1,35 +1,10 @@
-<!-- ****************** 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>
@@ -39,4 +14,3 @@
        <option value='m'>[% l("Electronic Resources") %]</option>
 -->
 </select>
-<!-- ****************** end: format_selector.xml ***************************** -->
diff --git a/Open-ILS/web/templates/default/opac/parts/header.tt2 b/Open-ILS/web/templates/default/opac/parts/header.tt2
new file mode 100644 (file)
index 0000000..c4a9ce1
--- /dev/null
@@ -0,0 +1,44 @@
+[%- 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;
+
+-%]
diff --git a/Open-ILS/web/templates/default/opac/parts/libselect.tt2 b/Open-ILS/web/templates/default/opac/parts/libselect.tt2
deleted file mode 100644 (file)
index 59cdfa6..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-[%  # 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]; '&nbsp;'; 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 ***************************** -->
index e140bcb..c0624ae 100644 (file)
@@ -1,4 +1,5 @@
-<!-- ****************** 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 ***************************** -->
index 4b5708e..8f56196 100644 (file)
         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"
-    };
 %]
index 1eb24d3..d41caa1 100644 (file)
@@ -28,8 +28,7 @@
             </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>
diff --git a/Open-ILS/web/templates/default/opac/parts/org_selector.tt2 b/Open-ILS/web/templates/default/opac/parts/org_selector.tt2
new file mode 100644 (file)
index 0000000..aa99e91
--- /dev/null
@@ -0,0 +1,23 @@
+[%
+    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]; '&nbsp;'; 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 %]
index 7fc9f31..efe3092 100644 (file)
@@ -1,5 +1,12 @@
+[%  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>&quot;<span id="holds_title"></span>&quot;</strong>
-        to be picked up at
-        <strong>&quot;<span id="holds_pickup_lib"></span>&quot;</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 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-            <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>
-        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-        <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" />
+            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+            <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>
index 719df27..93d2bf7 100644 (file)
@@ -1,6 +1,14 @@
 <!-- ****************** 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&nbsp;&nbsp;&nbsp;
             <span id="rdetail_result_count" class="hide_me">
index 6d44776..2a5167e 100644 (file)
                                 <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'
@@ -39,6 +64,7 @@
                                     <span name='copy_due_date'> </span>
                                 </td>
                             </tr>
+                                -->
 
                             <tr name='copy_extras_row' class='hide_me'>
                                 <td colspan='10'>
index e1d5e35..712042e 100644 (file)
@@ -6,9 +6,11 @@
     <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>
diff --git a/Open-ILS/web/templates/default/opac/parts/result/filtersort.tt2 b/Open-ILS/web/templates/default/opac/parts/result/filtersort.tt2
new file mode 100644 (file)
index 0000000..cee0f82
--- /dev/null
@@ -0,0 +1,19 @@
+<!-- ****************** 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 ***************************** -->
index c7e2f58..67f6193 100644 (file)
     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>
                 &nbsp;-
                 </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">&#9668;</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">&#9658;</span>
@@ -55,3 +55,6 @@
         </tr>
     </table>
 </div>
+[% END %]
+[% ctx.results_count_header = PROCESS results_count_header;
+    ctx.results_count_header %]