[open-ils-commits] [GIT] Evergreen ILS branch master updated. d7376fdf678deeb4932388fb78ec4066de16b338

Evergreen Git git at git.evergreen-ils.org
Fri Jun 7 15:02:25 EDT 2013


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  d7376fdf678deeb4932388fb78ec4066de16b338 (commit)
       via  e047b415d433b41b42348f7093e2f9afab89f050 (commit)
       via  3bb8ff29f2c1d9c1e536035903b47d0d8a2bb53a (commit)
       via  20fd7bb9d558ae75a6bd1ccde77865fc3e318fce (commit)
       via  91d11f40825fc18f743f950294feeac276eec570 (commit)
       via  a422aa3f6b5837c7730b513780524ffe2571c0ac (commit)
       via  3c656e65e787a5b1d2569a69c8aff5354f9f5223 (commit)
       via  9dff75bd1173f2f9806087752f75cae3b5108a91 (commit)
       via  437aa283e1f7dd121268ee362db87ebd05a58235 (commit)
       via  59cc4b9524bfcf9a56f54c5ed5a3d56a7d1b86e6 (commit)
       via  446284353bb7f6072198af09a0ff8b9379f2a481 (commit)
       via  832e9b0bc783d12babf6dd0f310a7e7fb8f78fc9 (commit)
      from  418bbfa33aecb0bdbc118d2162ae6b7e3c2b8d8b (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit d7376fdf678deeb4932388fb78ec4066de16b338
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Fri Jun 7 14:57:48 2013 -0400

    Stamping upgrade script for batch z39.50 search, match, overlay
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index d49e249..769ad25 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -91,7 +91,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0794', :eg_version); -- ktomita/bshum
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0796', :eg_version); -- berick/dbwells
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql b/Open-ILS/src/sql/Pg/upgrade/0795.schema.z39-batch-fetch-overlay.sql
similarity index 97%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
rename to Open-ILS/src/sql/Pg/upgrade/0795.schema.z39-batch-fetch-overlay.sql
index 44fff4a..00d3104 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0795.schema.z39-batch-fetch-overlay.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
--- TODO version check
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0795', :eg_version); -- berick/dbwells
 
 CREATE OR REPLACE FUNCTION 
     evergreen.z3950_attr_name_is_valid(TEXT) RETURNS BOOLEAN AS $func$
diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql b/Open-ILS/src/sql/Pg/upgrade/0796.schema.vandelay_bucket_match.sql
similarity index 97%
rename from Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql
rename to Open-ILS/src/sql/Pg/upgrade/0796.schema.vandelay_bucket_match.sql
index ba2f79f..f63e5d2 100644
--- a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0796.schema.vandelay_bucket_match.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
--- TODO version check
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0796', :eg_version); -- berick/dbwells
 
 ALTER TABLE vandelay.bib_queue ADD COLUMN match_bucket
    INTEGER REFERENCES container.biblio_record_entry_bucket(id)

commit e047b415d433b41b42348f7093e2f9afab89f050
Author: Bill Erickson <berick at esilibrary.com>
Date:   Tue Mar 5 09:20:43 2013 -0500

    Z39.50 Batch Search/Overlay : SQL / IDL
    
    Copied some missing pieces from the upgrade scripts into the seed schema
    / data files.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 190ecfc..d49e249 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -1036,4 +1036,39 @@ ALTER TABLE config.best_hold_order ADD CHECK ((
     rtime IS NOT NULL
 ));
 
+CREATE OR REPLACE FUNCTION 
+    evergreen.z3950_attr_name_is_valid(TEXT) RETURNS BOOLEAN AS $func$
+    SELECT EXISTS (SELECT 1 FROM config.z3950_attr WHERE name = $1);
+$func$ LANGUAGE SQL STRICT IMMUTABLE;
+
+COMMENT ON FUNCTION evergreen.z3950_attr_name_is_valid(TEXT) IS $$
+Results in TRUE if there exists at least one config.z3950_attr
+with the provided name.  Used by config.z3950_index_field_map
+to verify z3950_attr_type maps.
+$$;
+
+-- drop these in down here since they reference config.metabib_field
+-- and config.record_attr_definition
+CREATE TABLE config.z3950_index_field_map (
+    id              SERIAL  PRIMARY KEY,
+    label           TEXT    NOT NULL, -- i18n
+    metabib_field   INTEGER REFERENCES config.metabib_field(id),
+    record_attr     TEXT    REFERENCES config.record_attr_definition(name),
+    z3950_attr      INTEGER REFERENCES config.z3950_attr(id),
+    z3950_attr_type TEXT,-- REFERENCES config.z3950_attr(name)
+    CONSTRAINT metabib_field_or_record_attr CHECK (
+        metabib_field IS NOT NULL OR 
+        record_attr IS NOT NULL
+    ),
+    CONSTRAINT attr_or_attr_type CHECK (
+        z3950_attr IS NOT NULL OR 
+        z3950_attr_type IS NOT NULL
+    ),
+    -- ensure the selected z3950_attr_type refers to a valid attr name
+    CONSTRAINT valid_z3950_attr_type CHECK (
+        z3950_attr_type IS NULL OR 
+            evergreen.z3950_attr_name_is_valid(z3950_attr_type)
+    )
+);
+
 COMMIT;
diff --git a/Open-ILS/src/sql/Pg/012.schema.vandelay.sql b/Open-ILS/src/sql/Pg/012.schema.vandelay.sql
index 34249d8..72fd275 100644
--- a/Open-ILS/src/sql/Pg/012.schema.vandelay.sql
+++ b/Open-ILS/src/sql/Pg/012.schema.vandelay.sql
@@ -118,6 +118,7 @@ CREATE TYPE vandelay.bib_queue_queue_type AS ENUM ('bib', 'acq');
 CREATE TABLE vandelay.bib_queue (
 	queue_type	    vandelay.bib_queue_queue_type	NOT NULL DEFAULT 'bib',
 	item_attr_def	BIGINT REFERENCES vandelay.import_item_attr_definition (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+    match_bucket    INTEGER, -- REFERENCES container.biblio_record_entry_bucket(id);
 	CONSTRAINT vand_bib_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
 ) INHERITS (vandelay.queue);
 ALTER TABLE vandelay.bib_queue ADD PRIMARY KEY (id);
diff --git a/Open-ILS/src/sql/Pg/800.fkeys.sql b/Open-ILS/src/sql/Pg/800.fkeys.sql
index cf55a30..efb37c5 100644
--- a/Open-ILS/src/sql/Pg/800.fkeys.sql
+++ b/Open-ILS/src/sql/Pg/800.fkeys.sql
@@ -91,6 +91,7 @@ ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (cre
 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
 
 ALTER TABLE vandelay.import_item ADD CONSTRAINT imported_as_fkey FOREIGN KEY (imported_as) REFERENCES asset.copy (id) DEFERRABLE INITIALLY DEFERRED;
+ALTER TABLE vandelay.bib_queue ADD CONSTRAINT match_bucket_fkey FOREIGN KEY (match_bucket) REFERENCES container.biblio_record_entry_bucket(id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
 
 ALTER TABLE asset.copy_note ADD CONSTRAINT asset_copy_note_copy_fkey FOREIGN KEY (owning_copy) REFERENCES asset.copy (id) DEFERRABLE INITIALLY DEFERRED;
 ALTER TABLE asset.copy_note ADD CONSTRAINT asset_copy_note_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
index 7bfaac3..44fff4a 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
@@ -3,10 +3,15 @@ BEGIN;
 -- TODO version check
 
 CREATE OR REPLACE FUNCTION 
-    evergreen.z3950_name_is_valid(TEXT) RETURNS BOOLEAN AS $func$
+    evergreen.z3950_attr_name_is_valid(TEXT) RETURNS BOOLEAN AS $func$
     SELECT EXISTS (SELECT 1 FROM config.z3950_attr WHERE name = $1);
 $func$ LANGUAGE SQL STRICT IMMUTABLE;
 
+COMMENT ON FUNCTION evergreen.z3950_attr_name_is_valid(TEXT) IS $$
+Results in TRUE if there exists at least one config.z3950_attr
+with the provided name.  Used by config.z3950_index_field_map
+to verify z3950_attr_type maps.
+$$;
 
 CREATE TABLE config.z3950_index_field_map (
     id              SERIAL  PRIMARY KEY,
@@ -26,7 +31,7 @@ CREATE TABLE config.z3950_index_field_map (
     -- ensure the selected z3950_attr_type refers to a valid attr name
     CONSTRAINT valid_z3950_attr_type CHECK (
         z3950_attr_type IS NULL OR 
-            evergreen.z3950_name_is_valid(z3950_attr_type)
+            evergreen.z3950_attr_name_is_valid(z3950_attr_type)
     )
 );
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql
index b4e3caa..ba2f79f 100644
--- a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql
@@ -3,7 +3,8 @@ BEGIN;
 -- TODO version check
 
 ALTER TABLE vandelay.bib_queue ADD COLUMN match_bucket
-   INTEGER REFERENCES container.biblio_record_entry_bucket(id);
+   INTEGER REFERENCES container.biblio_record_entry_bucket(id)
+   ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;  
 
 CREATE OR REPLACE FUNCTION vandelay.match_bib_record() RETURNS TRIGGER AS $func$
 DECLARE

commit 3bb8ff29f2c1d9c1e536035903b47d0d8a2bb53a
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Feb 25 11:37:28 2013 -0500

    Z39.50 Batch Search/Overlay Release Notes
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/docs/RELEASE_NOTES_NEXT/z39_batch_search_queue.txt b/docs/RELEASE_NOTES_NEXT/z39_batch_search_queue.txt
new file mode 100644
index 0000000..dceb6ac
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/z39_batch_search_queue.txt
@@ -0,0 +1,89 @@
+Z39.50 Batch Search and Queue
+=============================
+
+Staff Work Flow
+---------------
+
+ * Staff add records to bib record buckets
+ * Staff select the new "Locate Z39.50 Matches" action for a selected bucket.
+ * Staff choose which Z30.50 sources and indexes to search and the destination 
+   queue.
+ * Submitting the search fires a series of parallel Z39.50 searches across
+   all selected Z39.50 sources.
+ * Matches are added to the selected (Vandelay) queue.
+ * Matched records may be manually or automatically overlaid to existing 
+   catalog records using the existing Vandelay import/merge/overlay features.
+
+Vandelay Limit to Bucket
+------------------------
+
+As a side effect of this feature, Vandelay now has a new option in the 
+interface which allows staff to limit which catalog records to which an 
+inbound record matches to bib records within a record bucket.  When a record
+bucket and match set are chosen, only the records in the bucket can act as
+merge/overlay targets for the inbound Vandelay records.
+
+Configuration
+-------------
+
+Z39.50 Index Field Maps
+~~~~~~~~~~~~~~~~~~~~~~~
+
+These map bib record indexes (Metabib Fields and Record Attributes) to Z39.50
+search attributes.  The purpose of the mapping is to allow the server to
+determine which values to use for the automated Z39.50 searches.  For example,
+if the Z39.50 "title" attribute is mapped to the "Uniform Title" Metabib Field,
+the extracted value for "Uniform Title" for each record in the bucket will be
+used as the "title" value in the Z39.50 search.
+
+Mappings can be applied to specific Z39.50 attributes, which define an attribute
+type and a Z39.50 source, or to generic attribute types (e.g. "title").  When
+a specific attribute is used, the mapping will only be applied to searches 
+directed at the Z39.50 source linked to the attribute.
+
+The management interface can be found in the staff client under 
+
+Admin => Server Administration => Z39.50 Index Field Maps
+
+Metabib Field Additions
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Stock config.metabib_field entries for author include additional author-
+related data, like dates.  For example, a value for Personal Author might 
+look like this:
+
+'girdlestone cuthbert morton 1895 1975 creator'
+
+In the context of searching Z39.50 servers, the extraneous data can be 
+detrimental.  Creating a separate field definition without the extra data
+is recommended.
+
+[source,sql]
+--------------------------------------------
+INSERT INTO config.metabib_field 
+    (field_class, name, label, format, xpath, search_field)
+    VALUES (
+        'author', 
+        'personal - trimmed', 
+        'Personal Author (trimmed)', 
+        'mods32',
+        '//mods32:mods/mods32:name[@type=''personal'' and mods32:role/mods32:roleTerm[text()=''creator'']]/mods32:namePart[not (@type)]',
+        FALSE
+    );
+
+-- FULL BIB (OR INDEX-TARGETED) RE-INGEST REQUIRED
+--------------------------------------------
+
+
+Org Unit Settings
+~~~~~~~~~~~~~~~~~
+
+ * cat.z3950.batch.max_parallel 
+  ** Maximum Parallel Z39.50 Batch Searches
+  ** The maximum number of Z39.50 searches that can be in-flight at any given 
+     time when performing batch Z39.50 searches
+ * cat.z3950.batch.max_results
+  ** Maximum Z39.50 Batch Search Results
+  ** The maximum number of search results to retrieve and queue for each 
+     record + Z39 source during batch Z39.50 searches
+    

commit 20fd7bb9d558ae75a6bd1ccde77865fc3e318fce
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Feb 25 13:17:59 2013 -0500

    Z39.50 Batch Search/Overlay TPAC my-list entry point
    
    Similar to the staff client bucket UI, this adds a button which allows
    staff to access the batch Z39 search-and-queue operation directly from
    the TPAC my-lists UI.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
index ec7bb96..ca917a9 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
@@ -23,6 +23,10 @@ sub prepare_extended_user_info {
     my $local_xact = !$e->{xact_id}; 
     $e->xact_begin if $local_xact;
 
+    # keep the original user object so we can restore
+    # login-specific data (e.g. workstation)
+    my $usr = $self->ctx->{user};
+
     $self->ctx->{user} = $self->editor->retrieve_actor_user([
         $self->ctx->{user}->id,
         {
@@ -36,6 +40,9 @@ sub prepare_extended_user_info {
 
     $e->rollback if $local_xact;
 
+    $self->ctx->{user}->wsid($usr->wsid);
+    $self->ctx->{user}->ws_ou($usr->ws_ou);
+
     # discard replaced (negative-id) addresses.
     $self->ctx->{user}->addresses([
         grep {$_->id > 0} @{$self->ctx->{user}->addresses} ]);
diff --git a/Open-ILS/src/templates/opac/myopac/lists.tt2 b/Open-ILS/src/templates/opac/myopac/lists.tt2
index 0b2977d..ba78f76 100644
--- a/Open-ILS/src/templates/opac/myopac/lists.tt2
+++ b/Open-ILS/src/templates/opac/myopac/lists.tt2
@@ -151,6 +151,27 @@
                     [% END %]
                 </div>
             </form>
+            [% IF ctx.is_staff %]
+            <div class="bookbag-controls">
+                <input 
+                    type="submit" 
+                    onclick='
+                       var path = 
+                          "oils://remote/xul/server/cat/bucketz39_dialog.xul";
+                        window.openDialog(
+                            xulG.url_prefix(path),
+                            "bucketz39_dialog",
+                            "width=800,height=500",
+                            "[% ctx.user.id %]",
+                            "[% ctx.authtoken %]",
+                            "[% ctx.user.ws_ou %]",
+                            "[% bbag.id %]",
+                            xulG
+                        )'
+                    value="[% l('Locate Z39.59 Matches') %]" 
+                />
+            </div>
+            [% END %]
             <div class="bookbag-controls">
                 [% IF bbag.pub == 't'; %]
                 <a target='_blank' href='/opac/extras/feed/bookbag/rss2-full/[% bbag.id %]'><img

commit 91d11f40825fc18f743f950294feeac276eec570
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Feb 25 10:37:11 2013 -0500

    Z39.50 Batch Search/Overlay Z-Index Admin UI
    
    Admin -> Server Admin -> Z-Index Field Maps
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/src/templates/conify/global/config/z3950_index_field_map.tt2 b/Open-ILS/src/templates/conify/global/config/z3950_index_field_map.tt2
new file mode 100644
index 0000000..4c603ad
--- /dev/null
+++ b/Open-ILS/src/templates/conify/global/config/z3950_index_field_map.tt2
@@ -0,0 +1,118 @@
+[% WRAPPER base.tt2 %]
+[% ctx.page_title = l('Z39.50 Index Field Maps') %]
+
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+
+    <div dojoType="dijit.layout.ContentPane" 
+            layoutAlign="top" class='oils-header-panel'>
+
+        <div>[% l('Z39.50 Index Field Maps') %]</div>
+        <div>
+            <button dojoType='dijit.form.Button' 
+                onClick='zGrid.showCreateDialog()'>
+                [% l('New') %]
+            </button>
+            <button dojoType='dijit.form.Button' 
+                onClick='zGrid.deleteSelected()'>
+                [% l('Delete Selected') %]
+            </button>
+        </div>
+    </div>
+
+    <br/>
+
+    <table
+        id="zGrid"
+        jsid="zGrid"
+        dojoType="openils.widget.FlattenerGrid"
+        columnPersistKey='"conify.config.z3950_index_field_map"'
+        autoHeight="true"
+        editOnEnter="true"
+        editStyle="pane"
+        showLoadFilter="true"
+        fmClass="'czifm'"
+        defaultSort="['label']"
+        query="{'id': {'!=' : null}}">
+        <thead>
+            <tr>
+                <th field="label" fpath="label" ffilter="true"/>
+                <th field="metabib_field_label" fpath="metabib_field.label" 
+                    ffilter="true" name="[% l('Metabib Field') %]"/>
+                <th field="record_attr_label" fpath="record_attr.label" 
+                    ffilter="true" name="[% l('Record Attribute') %]"/>
+                <th field="z3950_attr_label" fpath="z3950_attr.label" 
+                    ffilter="true" name="[% l('Z39.50 Attribute') %]"/>
+                <th field="z3950_attr_type" fpath="z3950_attr_type" 
+                    ffilter="true"/>
+            </tr>
+        </thead>
+    </table>
+
+    <p><i>
+[% | l %]Map Metabib Fields OR Bib Record Attributes to specific Z39.50 
+Attributes OR generic Z39.50 Attribute types (by name).[% END %]
+    </i></p>
+
+</div>
+
+<script>
+    dojo.require('dijit.form.Button');
+    dojo.require('dojo.data.ItemFileReadStore');
+    dojo.require('dijit.form.FilteringSelect');
+    dojo.require('openils.widget.FlattenerGrid');
+    dojo.require('openils.PermaCrud');
+    dojo.require('openils.Util');
+
+    dojo.addOnLoad(function() {
+
+        // display the z39 attr field label and source
+        zGrid.overrideWidgetArgs.z3950_attr = {
+            labelFormat : ['${0} : ${1}', 'source', 'label']
+        }
+
+        // fetch all of the z39 attrs and create an attr type
+        // selector from the distinct set of z39 attr names
+        new openils.PermaCrud().retrieveAll('cza', {
+            oncomplete : function(r) {
+                var attrs = openils.Util.readResponse(r);
+                var names = [];
+                var seen = {};
+
+                dojo.forEach(attrs, function(attr) {
+                    var name = attr.name();
+                    if (seen[name]) return;
+                    names.push({name : name});
+                    seen[name] = 1;
+                });
+
+                var store = new dojo.data.ItemFileReadStore({ 
+                    data : {
+                        identifier : 'name', 
+                        label : 'name', 
+                        items : names
+                    }
+                });  
+
+                zGrid.overrideEditWidgets.z3950_attr_type = 
+                    new dijit.form.FilteringSelect({
+                        searchAttr : 'name',
+                        valueAttr : 'name',
+                        labelAttr : 'name',
+                        store : store
+                    });
+
+                // when the edit pane is rendered, update the value of our
+                // custom widget to match that of the row being edited
+                zGrid.onEditPane = function(pane) {
+                    zGrid.overrideEditWidgets.z3950_attr_type.attr(
+                        'value', pane.fmObject.z3950_attr_type() || ''
+                    );
+                }
+            }
+        });
+    });
+
+</script>
+
+[% END %]
+
diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd
index a0ba7bf..ccf0a95 100644
--- a/Open-ILS/web/opac/locale/en-US/lang.dtd
+++ b/Open-ILS/web/opac/locale/en-US/lang.dtd
@@ -795,6 +795,7 @@
 <!ENTITY staff.main.menu.admin.server_admin.conify.sms_carrier.label "SMS Carriers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.z3950_source.label "Z39.50 Servers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.org_unit_proximity_adjustment.label "Org Unit Proximity Adjustments">
+<!ENTITY staff.main.menu.admin.server_admin.conify.z3950_index_field_map.label "Z39.50 Index Field Maps">
 <!ENTITY staff.main.menu.admin.server_admin.conify.circulation_modifier.label "Circulation Modifiers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.org_unit_setting_type "Organization Unit Setting Types">
 <!ENTITY staff.main.menu.admin.server_admin.conify.import_match_set "Import Match Sets">
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index aab9000..cc7a91a 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -1032,6 +1032,10 @@ main.menu.prototype = {
             'cmd_server_admin_org_unit_proximity_adjustment' : [
                 ['oncommand'],
                 function(event) { open_eg_web_page('conify/global/config/org_unit_proximity_adjustment', null, event); }
+			],
+            'cmd_server_admin_z39_index_field_map' : [
+                ['oncommand'],
+                function(event) { open_eg_web_page('conify/global/config/z3950_index_field_map', null, event); }
             ],
             'cmd_server_admin_circ_mod' : [
                 ['oncommand'],
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
index 63bc923..647ad70 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
@@ -258,6 +258,9 @@
              perm="ADMIN_Z3950_SOURCE"
              />
     <command id="cmd_server_admin_org_unit_proximity_adjustment" />
+    <command id="cmd_server_admin_z39_index_field_map" 
+             perm="ADMIN_Z3950_SOURCE"
+             />
     <command id="cmd_server_admin_circ_mod" 
              perm="CREATE_CIRC_MOD DELETE_CIRC_MOD UPDATE_CIRC_MOD ADMIN_CIRC_MOD"
              />
@@ -624,6 +627,7 @@
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.billing_type.label;" command="cmd_server_admin_billing_type"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.sms_carrier.label;" command="cmd_server_admin_sms_carrier"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.z3950_source.label;" command="cmd_server_admin_z39_source"/>
+                <menuitem label="&staff.main.menu.admin.server_admin.conify.z3950_index_field_map.label;" command="cmd_server_admin_z39_index_field_map"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.circulation_modifier.label;" command="cmd_server_admin_circ_mod"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.circulation_limit_group.label;" command="cmd_server_admin_circ_limit_group"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.global_flag.label;" command="cmd_server_admin_global_flag"/>

commit a422aa3f6b5837c7730b513780524ffe2571c0ac
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Feb 25 10:36:54 2013 -0500

    Z39.50 Batch Search/Overlay minor IDL repairs
    
    Added some missing reporter:selector attributes and repaired an invalid
    link class.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 837e5e6..ec6525e 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -947,7 +947,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
     <class id="cza" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::z3950_attr" oils_persist:tablename="config.z3950_attr" reporter:label="Z39.50 Attribute">
         <fields oils_persist:primary="id" oils_persist:sequence="config.z3950_attr_id_seq">
-            <field reporter:label="Z39.50 Attribute ID" name="id" reporter:datatype="id"/>
+            <field reporter:label="Z39.50 Attribute ID" name="id" reporter:datatype="id" reporter:selector="label"/>
             <field reporter:label="Z39.50 Source" name="source" reporter:datatype="link"/>
             <field reporter:label="Name" name="name" reporter:datatype="text"/>
             <field reporter:label="Label" name="label" reporter:datatype="text" oils_persist:i18n="true"/>
@@ -982,7 +982,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
         </fields>
         <links>
             <link field="metabib_field" reltype="has_a" key="id" map="" class="cmf"/>
-            <link field="record_attr" reltype="has_a" key="id" map="" class="crad"/>
+            <link field="record_attr" reltype="has_a" key="name" map="" class="crad"/>
             <link field="z3950_attr" reltype="has_a" key="id" map="" class="cza"/>
         </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
@@ -2398,7 +2398,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 	<class id="cmf" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::metabib_field" oils_persist:tablename="config.metabib_field" reporter:label="Metabib Field" oils_persist:field_safe="true">
 		<fields oils_persist:primary="id" oils_persist:sequence="config.metabib_field_id_seq">
 			<field reporter:label="Class" name="field_class" reporter:datatype="link"/>
-			<field reporter:label="ID" name="id" reporter:datatype="id" />
+			<field reporter:label="ID" name="id" reporter:datatype="id" reporter:selector="label"/>
 			<field reporter:label="Name" name="name" reporter:datatype="text"/>
 			<field reporter:label="Label" name="label" reporter:datatype="text" oils_persist:i18n="true"/>
 			<field reporter:label="XPath" name="xpath" reporter:datatype="text"/>

commit 3c656e65e787a5b1d2569a69c8aff5354f9f5223
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Feb 18 10:41:01 2013 -0500

    Z39.50 Batch Search/Overlay Z-Search UI
    
    Adds a new "Find Z39.50 Matches" option to Cataloging -> Manage Record
    Buckets.  When selected, the user chooses the Z39 fields, the Z39
    sources, the destination queue, and the queue match set.  The user then
    submits the search.  Basic progress info is reported to the user.
    Once complete, the user can open the destination queue, from which
    regular vandelay import, etc. actions may be performed on the newly
    found records.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd
index 508d6b2..a0ba7bf 100644
--- a/Open-ILS/web/opac/locale/en-US/lang.dtd
+++ b/Open-ILS/web/opac/locale/en-US/lang.dtd
@@ -3876,3 +3876,15 @@
 <!ENTITY staff.search_prefs.clear.label "Clear All">
 <!ENTITY staff.search_prefs.clear.accesskey "C">
 <!ENTITY staff.search_prefs.cleared_message "Preferences cleared">
+<!ENTITY staff.bucketz39_search.dialog_label "Locate Z39.59 Matches">
+<!ENTITY staff.bucketz39_search.servers "Z39.50 Servers:">
+<!ENTITY staff.bucketz39_search.indexes "Z39.50 Search Indexes:">
+<!ENTITY staff.bucketz39_search.queue "Add Results to Queue:">
+<!ENTITY staff.bucketz39_search.match_set "Match Set:">
+<!ENTITY staff.bucketz39_search.match_set_select "-- Select Match Set --">
+<!ENTITY staff.bucketz39_search.perform_search "Perform Search">  
+<!ENTITY staff.bucketz39_search.bib_count "Bib Records to Search:">
+<!ENTITY staff.bucketz39_search.progress "Search Progress:">
+<!ENTITY staff.bucketz39_search.found_matches "Matches Found:">
+<!ENTITY staff.bucketz39_search.close "Close">
+<!ENTITY staff.bucketz39_search.open_queue "Open Queue">
diff --git a/Open-ILS/xul/staff_client/server/cat/bucketz39_dialog.js b/Open-ILS/xul/staff_client/server/cat/bucketz39_dialog.js
new file mode 100644
index 0000000..0ede94f
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/cat/bucketz39_dialog.js
@@ -0,0 +1,236 @@
+var dialog;
+
+function Bucketz39Dialog() {
+
+    /**
+     * builds the  Z39 sources and Z39 search indexes grid
+    */
+    this._build_options_grid = function(list, key, id_attr, label_attr) {
+
+        // determine the number of columns per row dynamically
+        var grid = dojo.byId(key).parentNode;
+        var colcount = grid.getElementsByTagName('column').length;
+
+        var row;
+        dojo.forEach(list, function(obj, idx) {
+
+            if (idx % colcount == 0) {
+                row = dojo.create('row');
+                dojo.byId(key).appendChild(row);
+            }
+
+            var attrs = {
+                value : obj[id_attr](),
+                label : obj[label_attr]()
+            };
+            attrs[key] = 1;
+
+            row.appendChild(dojo.create('checkbox', attrs));
+        });
+    }
+
+    /**
+     * Fetches needed data
+     */
+    this.init = function() {
+        var self = this;
+        var pcrud = new OpenSRF.ClientSession('open-ils.pcrud');
+
+        // vandelay queues
+        pcrud.request({
+            method : 'open-ils.pcrud.search.vbq.atomic',
+            params : [
+                this.authtoken, 
+                {owner : this.user_id, queue_type : 'bib'}, 
+                {order_by : {vbq : 'name'}}
+            ],
+            oncomplete : function(r) {
+                if (resp = r.recv()) {
+                    var qlist = resp.content();
+                    dojo.forEach(qlist, function(q) {
+                        var attrs = {label : q.name()};
+                        var item = dojo.create('menuitem', attrs);
+                        dojo.byId('queue_selector').appendChild(item);
+                    });
+                }
+            }
+        }).send();
+
+        // z39 index field maps
+        pcrud.request({
+            method : 'open-ils.pcrud.search.czifm.atomic',
+            params : [
+                this.authtoken, 
+                {id : {'!=' : null}}, 
+                {order_by : {czifm : 'label'}}
+            ],
+            oncomplete : function(r) {
+                self._build_options_grid(
+                    r.recv().content(), 
+                    'index_selector', 'id', 'label');
+            }
+        }).send();
+
+        // z39 sources
+        pcrud.request({
+            method : 'open-ils.pcrud.search.czs.atomic',
+            params : [
+                this.authtoken, 
+                {name : {'!=' : null}},
+                {order_by : {czs : 'name'}}
+            ],
+            oncomplete : function(r) {
+                self._build_options_grid(
+                    r.recv().content(), 
+                    'source_selector', 'name', 'label');
+            }
+        }).send();
+
+        pcrud.request({
+            method : 'open-ils.pcrud.search.vms.atomic',
+            params : [this.authtoken, {
+                owner : this._ws_ancestors(),
+                mtype : 'biblio'
+            }],
+            oncomplete : function(r) {
+                var sets = r.recv().content();
+                dojo.forEach(sets, function(set) {
+                    var attrs = {label : set.name(), value : set.id() };
+                    var item = dojo.create('menuitem', attrs);
+                    dojo.byId('match_set_selector').appendChild(item);
+                });
+            }
+        }).send();
+
+    }
+
+    /* my workstation org unit plus ancestors as a flat list */
+    this._ws_ancestors = function() {
+        JSAN.use('OpenILS.data');
+        var data = new OpenILS.data(); 
+        data.stash_retrieve();
+        var org = data.hash.aou[ this.ws_ou ]
+        var org_list = [];
+
+        while (org) {
+            org_list.push(org.id());
+            org = data.hash.aou[org.parent_ou()];
+        }
+        return org_list;
+    }
+
+    /**
+     * extracts params from UI form elements
+     */
+    this._collect_params = function() {
+
+        // request params
+        var params = [this.authtoken, this.bucket_id];
+
+        // Z39 sources
+        params.push(dojo.query('[source_selector]').filter(
+            function(cbox) { return cbox.checked }).map(
+                function(cbox) { return cbox.getAttribute('value') }));
+
+        // indexes
+        params.push(dojo.query('[index_selector]').filter(
+            function(cbox) { return cbox.checked }).map(
+                function(cbox) { return cbox.getAttribute('value') }));
+
+        params.push({
+            // queue name (editable menulist)
+            queue_name : dojo.byId('queue_selector').parentNode.value,
+            // match set ID
+            match_set : dojo.byId('match_set_selector').parentNode.value
+        });
+
+        return params;
+    }
+
+    this.submit = function() {
+        var self = this;
+        
+        // progress labels
+        this.search_bib_count = dojo.byId('search-bib-count');
+        this.search_queue_count = dojo.byId('search-queue-count');
+        this.search_progress = dojo.byId('search-progress');
+
+        // hide submit row
+        dojo.addClass(dojo.byId('search-submit-row'), 'hideme');
+
+        // show progress rows
+        dojo.forEach(
+            dojo.query('.search_result_row'),
+            function(row) { dojo.removeClass(row, 'hideme') }
+        );
+
+        var params = this._collect_params();
+        dump('Submitting z39 search with: ' + js2JSON(params) + '\n');
+
+        var ses = new OpenSRF.ClientSession('open-ils.search');
+        ses.request({
+            method : 'open-ils.search.z3950.bucket_search_queue',
+            params : params,
+            onresponse : function(r) {
+                var resp = r.recv();
+                if (!resp) return;
+                var stat = resp.content();
+
+                dojo.attr(self.search_bib_count, 'value', ''+stat.bre_count);
+                dojo.attr(self.search_queue_count, 'value', ''+stat.queue_count);
+
+                var scount = Number(stat.search_count);
+                if (scount) {
+                    dojo.attr(self.search_progress, 'value', ''+Math.floor(
+                        (Number(stat.search_complete) / scount) * 100
+                    ));
+                }
+
+                // queue object is returned in the final response
+                self.queue = stat.queue;
+            },
+            oncomplete : function() {
+                dojo.removeClass(dojo.byId('final-actions-row'), 'hideme');
+            }
+        }).send();
+    }
+
+    // Open a new XUL tab focused on the Vandelay queue containing the results.
+    this.open_queue = function() {
+        /*
+        labelKey = labelKey || 'menu.cmd_open_conify.tab';
+        var label = offlineStrings.getString(labelKey);
+        */
+        var label = 'MARC Import/Export'; // TODO
+       
+        // URL
+        /*
+        var url_prefix = this.xulG.url_prefix || window.url_prefix;
+        */
+        var urls = this.xulG.urls || window.urls;
+        var loc = urls.XUL_BROWSER + '?url=' + 
+            window.escape(
+                this.xulG.url_prefix('EG_WEB_BASE/') +
+                'vandelay/vandelay?qtype=bib&qid=' + this.queue.id()
+            );
+        
+        var content_params = {
+            'no_xulG': false,
+            'show_print_button': true,
+            'show_nav_buttons': true 
+        };  
+       
+        this.xulG.new_tab(loc, {tab_name: label}, content_params);
+        window.close();
+    }
+}
+
+function my_init() {
+    dialog = new Bucketz39Dialog();
+    dialog.user_id   = window.arguments[0];
+    dialog.authtoken = window.arguments[1];
+    dialog.ws_ou     = window.arguments[2];
+    dialog.bucket_id = window.arguments[3];
+    dialog.xulG      = window.arguments[4];
+    dialog.init();
+}
diff --git a/Open-ILS/xul/staff_client/server/cat/bucketz39_dialog.xul b/Open-ILS/xul/staff_client/server/cat/bucketz39_dialog.xul
new file mode 100644
index 0000000..552c45b
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/cat/bucketz39_dialog.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
+<!-- borrow from the serials css -->
+<?xml-stylesheet href="/xul/server/skin/serial.css" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/bucketz39.css" type="text/css"?>
+<!DOCTYPE window PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
+<?xul-overlay href="/xul/server/cat/bucketz39_dialog_overlay.xul"?>
+
+<window id="bucketz39_dialog_win"
+    title="Bucket Z39.50 Search Dialog"
+    onload="try{my_init();font_helper();persist_helper();}catch(E){alert(E);}"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <script type="text/javascript">
+        var myPackageDir = "open_ils_staff_client";
+        var IAMXUL = true;
+        var g = {};
+    </script>
+
+    <scripts id="openils_util_scripts" />
+
+    <!-- JSAN is still needed for font_helper stuff, but I'm going to try
+        not to use it otherwise.  -->
+    <script type="text/javascript" src="/xul/server/main/JSAN.js" />
+
+    <messagecatalog id="catStrings"
+        src="/xul/server/locale/<!--#echo var='locale'-->/cat.properties" />
+
+    <commandset />
+    <box id="bucketz39_dialog_main" />
+</window>
diff --git a/Open-ILS/xul/staff_client/server/cat/bucketz39_dialog_overlay.xul b/Open-ILS/xul/staff_client/server/cat/bucketz39_dialog_overlay.xul
new file mode 100644
index 0000000..08e550f
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/cat/bucketz39_dialog_overlay.xul
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE overlay PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+<overlay id="bucketz39_dialog_overlay"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <script type="text/javascript" src="/xul/server/cat/bucketz39_dialog.js" />
+
+    <box orient="vertical" id="bucketz39_dialog_main" flex="1">
+        <caption class="top" label="&staff.bucketz39_search.dialog_label;"/>
+        <vbox flex="1">
+            <grid>                                                     
+                <columns><column /><column /></columns>
+                <rows>      
+                    <row class='search_row'>
+                        <label class='header' 
+                            value="&staff.bucketz39_search.servers;"/>
+                        <grid>
+                            <columns>
+                                <!-- display 2 Z-sources per row -->
+                                <column />
+                                <column />
+                            </columns>
+                            <rows id='source_selector'>
+                            </rows>
+                        </grid>
+                    </row>
+                    <row class='search_row'>
+                        <label class='header' 
+                            value="&staff.bucketz39_search.indexes;"/>
+                        <grid>
+                            <columns>
+                                <!-- display 3 Z-index options per row -->
+                                <column />
+                                <column />
+                                <column />
+                            </columns>
+                            <rows id='index_selector'>
+                            </rows>
+                        </grid>
+                    </row>
+
+                    <row class='search_row'>
+                        <label class='header' 
+                            value="&staff.bucketz39_search.queue;"/>
+                        <menulist editable='true'>
+                            <menupopup id='queue_selector'>
+                            </menupopup>
+                        </menulist>
+                    </row>
+                    <row class='search_row'>
+                        <label class='header' 
+                            value="&staff.bucketz39_search.match_set;"/>
+                        <menulist>
+                            <menupopup id='match_set_selector'>
+                                <menuitem value='' 
+                                    label="&staff.bucketz39_search.match_set_select;"/>
+                            </menupopup>
+                        </menulist>
+                    </row>
+                    <row id='search-submit-row' class='search_row'>
+                        <button oncommand="window.close();" 
+                            icon="remove" accesskey="C" 
+                            label="&common.cancel;" />  
+                        <button oncommand="dialog.submit();" 
+                            icon="accept" accesskey="P" 
+                            label="&staff.bucketz39_search.perform_search;"/>
+                    </row>
+                    <row class='hideme search_result_row search_row'>
+                        <label class='header' 
+                            value='&staff.bucketz39_search.bib_count;'/>
+                        <label id='search-bib-count' value='0'/>
+                    </row>
+                    <row class='hideme search_result_row search_row'>
+                        <label class='header' 
+                            value='&staff.bucketz39_search.found_matches;'/>
+                        <label id='search-queue-count' value='0'/>
+                    </row>
+                    <row class='hideme search_result_row search_row'>
+                        <label class='header' 
+                            value='&staff.bucketz39_search.progress;'/>
+                        <progressmeter id='search-progress' mode='determined'/>
+                    </row>
+                    <row id='final-actions-row' class='hideme search_row'>
+                        <button oncommand="window.close();" 
+                            icon="remove" accesskey="C" 
+                            label="&staff.bucketz39_search.close;"/>
+                        <button oncommand="dialog.open_queue();" 
+                            icon="accept" accesskey="O" 
+                            label="&staff.bucketz39_search.open_queue;"/>
+                    </row>
+                </rows>
+            </grid>
+        </vbox>
+    </box>
+</overlay>
diff --git a/Open-ILS/xul/staff_client/server/cat/record_buckets.js b/Open-ILS/xul/staff_client/server/cat/record_buckets.js
index 393d55a..b6aacd5 100644
--- a/Open-ILS/xul/staff_client/server/cat/record_buckets.js
+++ b/Open-ILS/xul/staff_client/server/cat/record_buckets.js
@@ -331,6 +331,7 @@ cat.record_buckets.prototype = {
                                     if (x) x.setAttribute('label','');
                                     obj.controller.view.cmd_record_buckets_delete_bucket.setAttribute('disabled','true');
                                     obj.controller.view.cmd_record_buckets_refresh.setAttribute('disabled','true');
+                                    obj.controller.view.cmd_record_buckets_zsearch.setAttribute('disabled','true');
                                     obj.controller.view.record_buckets_export_records.disabled = true;
                                     obj.controller.view.cmd_merge_records.setAttribute('disabled','true');
                                     obj.controller.view.cmd_delete_records.setAttribute('disabled','true');
@@ -353,6 +354,7 @@ cat.record_buckets.prototype = {
                                     try {
                                         obj.controller.view.cmd_record_buckets_delete_bucket.setAttribute('disabled','false');
                                         obj.controller.view.cmd_record_buckets_refresh.setAttribute('disabled','false');
+                                        obj.controller.view.cmd_record_buckets_zsearch.setAttribute('disabled','false');
                                         obj.controller.view.record_buckets_export_records.disabled = false;
                                         obj.controller.view.cmd_merge_records.setAttribute('disabled','false');
                                         obj.controller.view.cmd_delete_records.setAttribute('disabled','false');
@@ -544,6 +546,7 @@ cat.record_buckets.prototype = {
                                 x.setAttribute('hidden','true');
                                 obj.controller.view.cmd_record_buckets_delete_bucket.setAttribute('disabled','true');
                                 obj.controller.view.cmd_record_buckets_refresh.setAttribute('disabled','true');
+                                obj.controller.view.cmd_record_buckets_zsearch.setAttribute('disabled','true');
                                 obj.controller.view.record_buckets_export_records.disabled = true;
                                 obj.controller.view.cmd_merge_records.setAttribute('disabled','true');
                                 obj.controller.view.cmd_delete_records.setAttribute('disabled','true');
@@ -832,6 +835,28 @@ cat.record_buckets.prototype = {
                             }
                         }
                     ],
+                    'cmd_record_buckets_zsearch' : [
+                        ['command'],
+                        function() {
+                            try {
+                                var bucket_id = obj.controller.view.bucket_menulist.value;
+
+                                window.openDialog(
+                                    // TODO: constants.js
+                                    xulG.url_prefix("oils://remote/xul/server/cat/bucketz39_dialog.xul"),
+                                    "bucketz39_dialog",
+                                    "width=800,height=500",
+                                    obj.data.list.au[0].id(),
+                                    ses(), 
+                                    ses('ws_ou'),
+                                    bucket_id,
+                                    xulG
+                                );
+                            } catch(E) {
+                                alert('Error in record_buckets.js, cmd_record_buckets_zsearch: ' + E);
+                            }
+                        }
+                    ],
 
                     'record_buckets_export_records' : [ ['render'], function(){} ],
                     'record_buckets_list_actions' : [ ['render'], function(){} ]
diff --git a/Open-ILS/xul/staff_client/server/cat/record_buckets_overlay.xul b/Open-ILS/xul/staff_client/server/cat/record_buckets_overlay.xul
index 2ea4cdb..0f9215e 100644
--- a/Open-ILS/xul/staff_client/server/cat/record_buckets_overlay.xul
+++ b/Open-ILS/xul/staff_client/server/cat/record_buckets_overlay.xul
@@ -29,6 +29,7 @@
     <command id="cmd_record_buckets_new_bucket" />
     <command id="cmd_record_buckets_delete_bucket" disabled="true"/>
     <command id="cmd_record_buckets_refresh" disabled="true"/>
+    <command id="cmd_record_buckets_zsearch" disabled="true"/>
 
     <command id="cmd_record_buckets_delete_item" />
     <command id="cmd_record_buckets_to_pending_buckets" />
@@ -104,6 +105,8 @@
                                 <menuitem command="cmd_record_buckets_new_bucket" label="&staff.cat.record_buckets_overlay.new_bucket.label;"/>
                                 <menuitem command="cmd_record_buckets_delete_bucket" label="&staff.cat.record_buckets_overlay.delete_bucket.label;"/>
                                 <menuitem command="cmd_record_buckets_refresh" label="&staff.cat.record_buckets_overlay.refresh_bucket.label;"/>
+                                <!-- TODO: label -->
+                                <menuitem command="cmd_record_buckets_zsearch" label="Locate Z39.50 Matches"/>
                             </menupopup>
                         </button>
                         <label id="bucket_item_count" />
diff --git a/Open-ILS/xul/staff_client/server/skin/bucketz39.css b/Open-ILS/xul/staff_client/server/skin/bucketz39.css
new file mode 100644
index 0000000..401ab0e
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/skin/bucketz39.css
@@ -0,0 +1,5 @@
+.search_row {
+    border-bottom:1px solid black; 
+    padding:5px;  
+}
+

commit 9dff75bd1173f2f9806087752f75cae3b5108a91
Author: Bill Erickson <berick at esilibrary.com>
Date:   Wed Feb 13 14:07:29 2013 -0500

    Z39.50 Batch Search/Overlay Seed Data
    
    Org unit setting seed data:
    * cat.z3950.batch.max_parallel
    * cat.z3950.batch.max_parallel
    
    Stock / sample seed data entries for config.z3950_index_field_map, with
    room added for future stock data.
    
    Vandelay bib attribute seed data for Z39.50 source (901z), which is
    stamped during the Z39.50 batch search-and-queue operation.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Z3950.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Z3950.pm
index e01460a..f9c88df 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Z3950.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Z3950.pm
@@ -686,8 +686,13 @@ sub stamp_and_queue_results {
 sub send_and_queue_bucket_searches {
     my ($conn, $e, $queue, $z_searches) = @_;
 
-    my $max_parallel = 5; # TODO org setting
-    my $search_limit = 5; # TODO org setting
+    my $max_parallel = $U->ou_ancestor_setting(
+        $e->requestor->ws_ou,
+        'cat.z3950.batch.max_parallel') || 5;
+
+    my $search_limit = $U->ou_ancestor_setting(
+        $e->requestor->ws_ou,
+        'cat.z3950.batch.max_results') || 5;
 
     my $response = {
         bre_count => 0,
diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index 7f401cd..125a4b2 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -4840,6 +4840,7 @@ INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES
 INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, remove ) VALUES (13, 'pubdate',oils_i18n_gettext(13, 'Publication Date', 'vqbrad', 'description'),'//*[@tag="260"]/*[@code="c"][1]',$r$\D$r$);
 INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (14, 'edition',oils_i18n_gettext(14, 'Edition', 'vqbrad', 'description'),'//*[@tag="250"]/*[@code="a"][1]');
 INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (15, 'item_barcode',oils_i18n_gettext(15, 'Item Barcode', 'vqbrad', 'description'),'//*[@tag="852"]/*[@code="p"][1]');
+INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (16, 'zsource', oils_i18n_gettext(16, 'Z39.50 Source', 'vqbrad', 'description'), '//*[@tag="901"]/*[@code="z"]');
 SELECT SETVAL('vandelay.bib_attr_definition_id_seq'::TEXT, 100);
 
 INSERT INTO vandelay.import_item_attr_definition (
@@ -12620,3 +12621,60 @@ VALUES (
     'bool'
 );
 
+INSERT INTO config.z3950_index_field_map 
+    (id, label, metabib_field, z3950_attr_type) VALUES 
+(1, oils_i18n_gettext(1, 'Title',   'czifm', 'label'), 5,  'title'),
+(2, oils_i18n_gettext(2, 'Author',  'czifm', 'label'), 8,  'author'),
+(3, oils_i18n_gettext(3, 'ISBN',    'czifm', 'label'), 18, 'isbn'),
+(4, oils_i18n_gettext(4, 'ISSN',    'czifm', 'label'), 19, 'issn'),
+(5, oils_i18n_gettext(5, 'LCCN',    'czifm', 'label'), 30, 'lccn');
+
+INSERT INTO config.z3950_index_field_map 
+    (id, label, record_attr, z3950_attr_type) VALUES 
+(6, oils_i18n_gettext(6, 'Pubdate',  'czifm', 'label'),'pubdate', 'pubdate'),
+(7, oils_i18n_gettext(7, 'Item Type', 'czifm', 'label'),'item_type', 'item_type');
+
+
+-- let's leave room for more stock mappings
+SELECT SETVAL('config.z3950_index_field_map_id_seq'::TEXT, 1000);
+
+INSERT INTO config.org_unit_setting_type
+    (name, grp, label, description, datatype)
+    VALUES (
+        'cat.z3950.batch.max_parallel',
+        'cat',
+        oils_i18n_gettext(
+            'cat.z3950.batch.max_parallel',
+            'Maximum Parallel Z39.50 Batch Searches',
+            'coust',
+            'label'
+        ),
+        oils_i18n_gettext(
+            'cat.z3950.batch.max_parallel',
+            'The maximum number of Z39.50 searches that can be in-flight at any given time when performing batch Z39.50 searches',
+            'coust',
+            'description'
+        ),
+        'integer'
+    );
+
+INSERT INTO config.org_unit_setting_type
+    (name, grp, label, description, datatype)
+    VALUES (
+        'cat.z3950.batch.max_results',
+        'cat',
+        oils_i18n_gettext(
+            'cat.z3950.batch.max_results',
+            'Maximum Z39.50 Batch Search Results',
+            'coust',
+            'label'
+        ),
+        oils_i18n_gettext(
+            'cat.z3950.batch.max_results',
+            'The maximum number of search results to retrieve and queue for each record + Z39 source during batch Z39.50 searches',
+            'coust',
+            'description'
+        ),
+        'integer'
+    );
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
index 8edf298..7bfaac3 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
@@ -49,4 +49,54 @@ INSERT INTO config.z3950_index_field_map
 -- let's leave room for more stock mappings
 SELECT SETVAL('config.z3950_index_field_map_id_seq'::TEXT, 1000);
 
+INSERT INTO config.org_unit_setting_type
+    (name, grp, label, description, datatype)
+    VALUES (
+        'cat.z3950.batch.max_parallel',
+        'cat',
+        oils_i18n_gettext(
+            'cat.z3950.batch.max_parallel',
+            'Maximum Parallel Z39.50 Batch Searches',
+            'coust',
+            'label'
+        ),
+        oils_i18n_gettext(
+            'cat.z3950.batch.max_parallel',
+            'The maximum number of Z39.50 searches that can be in-flight at any given time when performing batch Z39.50 searches',
+            'coust',
+            'description'
+        ),
+        'integer'
+    );
+
+INSERT INTO config.org_unit_setting_type
+    (name, grp, label, description, datatype)
+    VALUES (
+        'cat.z3950.batch.max_results',
+        'cat',
+        oils_i18n_gettext(
+            'cat.z3950.batch.max_results',
+            'Maximum Z39.50 Batch Search Results',
+            'coust',
+            'label'
+        ),
+        oils_i18n_gettext(
+            'cat.z3950.batch.max_results',
+            'The maximum number of search results to retrieve and queue for each record + Z39 source during batch Z39.50 searches',
+            'coust',
+            'description'
+        ),
+        'integer'
+    );
+
+INSERT INTO vandelay.bib_attr_definition (id, code, description, xpath) 
+    VALUES (
+        16, 
+        'zsource',
+        oils_i18n_gettext(16, 'Z39.50 Source', 'vqbrad', 'description'),
+        '//*[@tag="901"]/*[@code="z"]'
+    );
+
+
+
 COMMIT;

commit 437aa283e1f7dd121268ee362db87ebd05a58235
Author: Bill Erickson <berick at esilibrary.com>
Date:   Wed Feb 13 12:16:24 2013 -0500

    Vandelay : sort queued records by matches
    
    Sort queued records by their matches (based on the first match) so that
    like records are grouped in the queue interface.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
index 778ab8b..ad92caf 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
@@ -82,7 +82,7 @@ sub create_bib_queue {
     $queue->queue_type( $type ) if ($type);
     $queue->item_attr_def( $import_def ) if ($import_def);
     $queue->match_set($match_set) if $match_set;
-    $queue->match_bucket($match_bucket);
+    $queue->match_bucket($match_bucket) if $match_bucket;
 
     my $new_q = $e->create_vandelay_bib_queue( $queue );
     return $e->die_event unless ($new_q);
@@ -469,12 +469,19 @@ sub retrieve_queued_records {
     return $evt if ($evt);
 
     my $class = ($type eq 'bib') ? 'vqbr' : 'vqar';
+    my $mclass = $type eq 'bib' ? 'vbm' : 'vam';
     my $query = {
-        select => {$class => ['id']},
+        select => {
+            $class => ['id'],
+            $mclass => [{
+                column => 'eg_record', 
+                transform => 'min',
+                aggregate => 1
+            }]
+        },
         from => $class,
         where => {queue => $queue_id},
         distinct => 1,
-        order_by => {$class => ['id']}, 
         limit => $limit,
         offset => $offset,
     };
@@ -507,9 +514,17 @@ sub retrieve_queued_records {
 
     if($self->api_name =~ /matches/) {
         # find only records that have matches
-        my $mclass = $type eq 'bib' ? 'vbm' : 'vam';
         $query->{from} = {$class => {$mclass => {type => 'right'}}};
-    } 
+    } else {
+        # join to mclass for sorting (see below)
+        $query->{from} = {$class => {$mclass => {type => 'left'}}};
+    }
+
+    # order by the matched bib records to group like queued records
+    $query->{order_by} = [
+        {class => $mclass, field => 'eg_record', transform => 'min'},
+        {class => $class, field => 'id'} 
+    ];
 
     my $record_ids = $e->json_query($query);
 

commit 59cc4b9524bfcf9a56f54c5ed5a3d56a7d1b86e6
Author: Bill Erickson <berick at esilibrary.com>
Date:   Thu Jan 31 12:37:44 2013 -0500

    Vandelay record bucket-limited matching
    
    Provides the option to link record buckets to vandelay queues.  When
    linked, vandelay imports where a match set is specified will limit
    matches to records within the selected bucket.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 8e69bbd..837e5e6 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -370,11 +370,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			<field reporter:label="Type" name="queue_type" reporter:datatype="text"/>
 			<field reporter:label="Match Set" name="match_set" reporter:datatype="link"/>
 			<field reporter:label="Item Import Attribute Definition" name="item_attr_def" reporter:datatype="link"/>
+			<field reporter:label="Match Bucket" name="match_bucket" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
 			<link field="item_attr_def" reltype="has_a" key="id" map="" class="viiad"/>
 			<link field="match_set" reltype="has_a" key="id" map="" class="vms"/>
+			<link field="match_bucket" reltype="has_a" key="id" map="" class="cbreb"/>
 		</links>
 		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
 			<actions>
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
index 6796447..778ab8b 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
@@ -63,6 +63,7 @@ sub create_bib_queue {
     my $type = shift;
     my $match_set = shift;
     my $import_def = shift;
+    my $match_bucket = shift;
 
     my $e = new_editor(authtoken => $auth, xact => 1);
 
@@ -81,6 +82,7 @@ sub create_bib_queue {
     $queue->queue_type( $type ) if ($type);
     $queue->item_attr_def( $import_def ) if ($import_def);
     $queue->match_set($match_set) if $match_set;
+    $queue->match_bucket($match_bucket);
 
     my $new_q = $e->create_vandelay_bib_queue( $queue );
     return $e->die_event unless ($new_q);
diff --git a/Open-ILS/src/sql/Pg/012.schema.vandelay.sql b/Open-ILS/src/sql/Pg/012.schema.vandelay.sql
index 4122444..34249d8 100644
--- a/Open-ILS/src/sql/Pg/012.schema.vandelay.sql
+++ b/Open-ILS/src/sql/Pg/012.schema.vandelay.sql
@@ -496,7 +496,7 @@ $_$ LANGUAGE SQL;
 CREATE TYPE vandelay.match_set_test_result AS (record BIGINT, quality INTEGER);
 
 CREATE OR REPLACE FUNCTION vandelay.match_set_test_marcxml(
-    match_set_id INTEGER, record_xml TEXT
+    match_set_id INTEGER, record_xml TEXT, bucket_id INTEGER 
 ) RETURNS SETOF vandelay.match_set_test_result AS $$
 DECLARE
     tags_rstore HSTORE;
@@ -534,7 +534,16 @@ BEGIN
         FROM _vandelay_tmp_jrows;
 
     -- add those joins and the where clause to our query.
-    query_ := query_ || joins || E'\n' || 'JOIN biblio.record_entry bre ON (bre.id = record) ' || 'WHERE ' || wq || ' AND not bre.deleted';
+    query_ := query_ || joins || E'\n';
+
+    -- join the record bucket
+    IF bucket_id IS NOT NULL THEN
+        query_ := query_ || 'JOIN container.biblio_record_entry_bucket_item ' ||
+            'brebi ON (brebi.target_biblio_record_entry = record ' ||
+            'AND brebi.bucket = ' || bucket_id || E')\n';
+    END IF;
+
+    query_ := query_ || 'JOIN biblio.record_entry bre ON (bre.id = record) ' || 'WHERE ' || wq || ' AND not bre.deleted';
 
     -- this will return rows of record,quality
     FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
@@ -545,9 +554,9 @@ BEGIN
     DROP TABLE _vandelay_tmp_jrows;
     RETURN;
 END;
-
 $$ LANGUAGE PLPGSQL;
 
+
 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
     record_xml TEXT
 ) RETURNS HSTORE AS $func$
@@ -774,6 +783,7 @@ DECLARE
     test_result             vandelay.match_set_test_result%ROWTYPE;
     tmp_rec                 BIGINT;
     match_set               INT;
+    match_bucket            INT;
 BEGIN
     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
         RETURN NEW;
@@ -810,8 +820,10 @@ BEGIN
         RETURN NEW;
     END IF;
 
+    SELECT q.match_bucket INTO match_bucket FROM vandelay.bib_queue q WHERE q.id = NEW.queue;
+
     FOR test_result IN SELECT * FROM
-        vandelay.match_set_test_marcxml(match_set, NEW.marc) LOOP
+        vandelay.match_set_test_marcxml(match_set, NEW.marc, match_bucket) LOOP
 
         INSERT INTO vandelay.bib_match ( queued_record, eg_record, match_score, quality )
             SELECT  
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
index 5e65faf..8edf298 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
@@ -30,7 +30,6 @@ CREATE TABLE config.z3950_index_field_map (
     )
 );
 
-
 -- seed data
 
 INSERT INTO config.z3950_index_field_map 
diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql
new file mode 100644
index 0000000..b4e3caa
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay_bucket_match.sql
@@ -0,0 +1,135 @@
+BEGIN;
+
+-- TODO version check
+
+ALTER TABLE vandelay.bib_queue ADD COLUMN match_bucket
+   INTEGER REFERENCES container.biblio_record_entry_bucket(id);
+
+CREATE OR REPLACE FUNCTION vandelay.match_bib_record() RETURNS TRIGGER AS $func$
+DECLARE
+    incoming_existing_id    TEXT;
+    test_result             vandelay.match_set_test_result%ROWTYPE;
+    tmp_rec                 BIGINT;
+    match_set               INT;
+    match_bucket            INT;
+BEGIN
+    IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
+        RETURN NEW;
+    END IF;
+
+    DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
+
+    SELECT q.match_set INTO match_set FROM vandelay.bib_queue q WHERE q.id = NEW.queue;
+
+    IF match_set IS NOT NULL THEN
+        NEW.quality := vandelay.measure_record_quality( NEW.marc, match_set );
+    END IF;
+
+    -- Perfect matches on 901$c exit early with a match with high quality.
+    incoming_existing_id :=
+        oils_xpath_string('//*[@tag="901"]/*[@code="c"][1]', NEW.marc);
+
+    IF incoming_existing_id IS NOT NULL AND incoming_existing_id != '' THEN
+        SELECT id INTO tmp_rec FROM biblio.record_entry WHERE id = incoming_existing_id::bigint;
+        IF tmp_rec IS NOT NULL THEN
+            INSERT INTO vandelay.bib_match (queued_record, eg_record, match_score, quality) 
+                SELECT
+                    NEW.id, 
+                    b.id,
+                    9999,
+                    -- note: no match_set means quality==0
+                    vandelay.measure_record_quality( b.marc, match_set )
+                FROM biblio.record_entry b
+                WHERE id = incoming_existing_id::bigint;
+        END IF;
+    END IF;
+
+    IF match_set IS NULL THEN
+        RETURN NEW;
+    END IF;
+
+    SELECT q.match_bucket INTO match_bucket FROM vandelay.bib_queue q WHERE q.id = NEW.queue;
+
+    FOR test_result IN SELECT * FROM
+        vandelay.match_set_test_marcxml(match_set, NEW.marc, match_bucket) LOOP
+
+        INSERT INTO vandelay.bib_match ( queued_record, eg_record, match_score, quality )
+            SELECT  
+                NEW.id,
+                test_result.record,
+                test_result.quality,
+                vandelay.measure_record_quality( b.marc, match_set )
+	        FROM  biblio.record_entry b
+	        WHERE id = test_result.record;
+
+    END LOOP;
+
+    RETURN NEW;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+
+DROP FUNCTION IF EXISTS vandelay.match_set_test_marcxml(INTEGER, TEXT);
+
+CREATE OR REPLACE FUNCTION vandelay.match_set_test_marcxml(
+    match_set_id INTEGER, record_xml TEXT, bucket_id INTEGER 
+) RETURNS SETOF vandelay.match_set_test_result AS $$
+DECLARE
+    tags_rstore HSTORE;
+    svf_rstore  HSTORE;
+    coal        TEXT;
+    joins       TEXT;
+    query_      TEXT;
+    wq          TEXT;
+    qvalue      INTEGER;
+    rec         RECORD;
+BEGIN
+    tags_rstore := vandelay.flatten_marc_hstore(record_xml);
+    svf_rstore := vandelay.extract_rec_attrs(record_xml);
+
+    CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
+    CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
+
+    -- generate the where clause and return that directly (into wq), and as
+    -- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
+    wq := vandelay.get_expr_from_match_set(match_set_id, tags_rstore);
+
+    query_ := 'SELECT DISTINCT(record), ';
+
+    -- qrows table is for the quality bits we add to the SELECT clause
+    SELECT ARRAY_TO_STRING(
+        ARRAY_ACCUM('COALESCE(n' || q::TEXT || '.quality, 0)'), ' + '
+    ) INTO coal FROM _vandelay_tmp_qrows;
+
+    -- our query string so far is the SELECT clause and the inital FROM.
+    -- no JOINs yet nor the WHERE clause
+    query_ := query_ || coal || ' AS quality ' || E'\n';
+
+    -- jrows table is for the joins we must make (and the real text conditions)
+    SELECT ARRAY_TO_STRING(ARRAY_ACCUM(j), E'\n') INTO joins
+        FROM _vandelay_tmp_jrows;
+
+    -- add those joins and the where clause to our query.
+    query_ := query_ || joins || E'\n';
+
+    -- join the record bucket
+    IF bucket_id IS NOT NULL THEN
+        query_ := query_ || 'JOIN container.biblio_record_entry_bucket_item ' ||
+            'brebi ON (brebi.target_biblio_record_entry = record ' ||
+            'AND brebi.bucket = ' || bucket_id || E')\n';
+    END IF;
+
+    query_ := query_ || 'JOIN biblio.record_entry bre ON (bre.id = record) ' || 'WHERE ' || wq || ' AND not bre.deleted';
+
+    -- this will return rows of record,quality
+    FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
+        RETURN NEXT rec;
+    END LOOP;
+
+    DROP TABLE _vandelay_tmp_qrows;
+    DROP TABLE _vandelay_tmp_jrows;
+    RETURN;
+END;
+$$ LANGUAGE PLPGSQL;
+
+COMMIT;
diff --git a/Open-ILS/src/templates/vandelay/inc/upload.tt2 b/Open-ILS/src/templates/vandelay/inc/upload.tt2
index 0203306..bca673e 100644
--- a/Open-ILS/src/templates/vandelay/inc/upload.tt2
+++ b/Open-ILS/src/templates/vandelay/inc/upload.tt2
@@ -30,6 +30,11 @@
                 <input jsId='vlUploadQueueMatchSet'
                     dojoType='dijit.form.FilteringSelect' labelAttr='name' searchAttr='name'/>
             </td>
+            <td>[% l('Limit matches to bucket') %]</td>
+            <td>
+                <input jsId='vlUploadQueueMatchBucket'
+                    dojoType='dijit.form.FilteringSelect' labelAttr='name' searchAttr='name'/>
+            </td>
         </tr>
         <tr>
             <td>[% l('Holdings Import Profile') %]</td>
diff --git a/Open-ILS/web/js/ui/default/vandelay/vandelay.js b/Open-ILS/web/js/ui/default/vandelay/vandelay.js
index bc82bca..00f78e1 100644
--- a/Open-ILS/web/js/ui/default/vandelay/vandelay.js
+++ b/Open-ILS/web/js/ui/default/vandelay/vandelay.js
@@ -89,6 +89,7 @@ var vlQueueGridColumePicker = {};
 var vlBibSources = [];
 var importItemDefs = [];
 var matchSets = {biblio : [], authority : []};
+var matchBuckets = {};
 var mergeProfiles = [];
 var copyStatusCache = {};
 var copyLocationCache = {};
@@ -208,6 +209,19 @@ function vlInit() {
         }
     );
 
+    fieldmapper.standardRequest(
+        ['open-ils.actor', 'open-ils.actor.container.retrieve_by_class'],
+        {   async : true,
+            params : [authtoken, new openils.User().user.id(), 'biblio'],
+            oncomplete : function(r) {
+                var buckets = openils.Util.readResponse(r);
+                // only bib buckets are supported
+                matchBuckets.biblio = buckets;
+                checkInitDone();
+            }
+        }
+    );
+
     new openils.PermaCrud().retrieveAll('ccs',
         {   async: true,
             oncomplete: function(r) {
@@ -417,7 +431,9 @@ function uploadMARC(onload){
 /**
   * Creates a new vandelay queue
   */
-function createQueue(queueName, type, onload, importDefId, matchSet) {
+function createQueue(
+        queueName, type, onload, importDefId, matchSet, matchBucket) {
+
     var name = (type=='bib') ? 'bib' : 'authority';
     var method = 'open-ils.vandelay.'+ name +'_queue.create'
 
@@ -431,7 +447,10 @@ function createQueue(queueName, type, onload, importDefId, matchSet) {
     fieldmapper.standardRequest(
         ['open-ils.vandelay', method],
         {   async: true,
-            params: [authtoken, queueName, null, qType, matchSet, importDefId],
+            params: [
+                authtoken, queueName, null, 
+                qType, matchSet, importDefId, matchBucket
+            ],
             oncomplete : function(r) {
                 var queue = r.recv().content();
                 if(e = openils.Event.parse(queue)) 
@@ -1421,7 +1440,8 @@ function batchUpload() {
     } else {
         createQueue(queueName, currentType, handleCreateQueue, 
             vlUploadQueueHoldingsImportProfile.attr('value'),
-            vlUploadQueueMatchSet.attr('value')
+            vlUploadQueueMatchSet.attr('value'),
+            vlUploadQueueMatchBucket.attr('value')
         );
     }
 }
@@ -1467,11 +1487,13 @@ function vlFleshQueueSelect(selector, type) {
             vlUploadQueueHoldingsImportProfile.attr('disabled', true);
             vlUploadQueueMatchSet.attr('value', queue.match_set() || '');
             vlUploadQueueMatchSet.attr('disabled', true);
+            vlUploadQueueMatchBucket.attr('value', queue.match_bucket() || '');
+            vlUploadQueueMatchBucket.attr('disabled', true);
         } else {
             vlUploadQueueHoldingsImportProfile.attr('value', '');
             vlUploadQueueHoldingsImportProfile.attr('disabled', false);
-            vlUploadQueueMatchSet.attr('value', '');
-            vlUploadQueueMatchSet.attr('disabled', false);
+            vlUploadQueueMatchBucket.attr('value', '');
+            vlUploadQueueMatchBucket.attr('disabled', false);
         }
         dojo.disconnect(qInput._onchange);
         qInput.attr('value', '');
@@ -1483,6 +1505,7 @@ function vlFleshQueueSelect(selector, type) {
         // user entered a new queue name. clear the selector 
         vlUploadQueueHoldingsImportProfile.attr('disabled', false);
         vlUploadQueueMatchSet.attr('disabled', false);
+        vlUploadQueueMatchBucket.attr('disabled', false);
         dojo.disconnect(selector._onchange);
         selector.attr('value', '');
         selector._onchange = dojo.connect(selector, 'onChange', selChange);
@@ -1505,6 +1528,19 @@ function vlUpdateMatchSetSelector(type) {
     }
 }
 
+function vlUpdateMatchBucketSelector(type) {
+    type = (type.match(/bib/)) ? 'biblio' : 'authority';
+    if (type == 'authority') {
+        vlUploadQueueMatchBucket.attr('value', '');
+        vlUploadQueueMatchBucket.attr('disabled', true);
+    } else {
+        vlUploadQueueMatchBucket.attr('disabled', false);
+        vlUploadQueueMatchBucket.store = 
+            new dojo.data.ItemFileReadStore(
+                {data:cbreb.toStoreData(matchBuckets[type])});
+    }
+}
+
 function vlShowUploadForm() {
     displayGlobalDiv('vl-marc-upload-div');
     vlFleshQueueSelect(vlUploadQueueSelector, vlUploadRecordType.getValue());
@@ -1514,6 +1550,7 @@ function vlShowUploadForm() {
     vlUploadQueueHoldingsImportProfile.store = 
         new dojo.data.ItemFileReadStore({data:viiad.toStoreData(importItemDefs)});
     vlUpdateMatchSetSelector(vlUploadRecordType.getValue());
+    vlUpdateMatchBucketSelector(vlUploadRecordType.getValue());
 
     if (vlUploadRecordType.attr('value').match(/auth/) || trashGroups.length == 0) {
         openils.Util.hide('vl-trash-groups-row');

commit 446284353bb7f6072198af09a0ff8b9379f2a481
Author: Bill Erickson <berick at esilibrary.com>
Date:   Wed Jan 30 17:44:30 2013 -0500

    Z39.50 Batch Search/Overlay API
    
    New API for performing Z39.50 queries for a set of bib records across a
    set of Z39 sources.  Searches are performed in parallel (up to a
    configurable maximum number of active searches and maximum results) and
    results are stored in a vandelay MARC Import/Export queue.
    
    Search fields and values are determined by Z39 index field maps.  At
    search time, the caller provides a set of field maps to use for the
    search. Search values for each bib record are found by following the
    index maps to the indexed values in metabib.*_field_entry or
    metabib.record_attr.
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Z3950.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Z3950.pm
index 1f17a0c..e01460a 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Z3950.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Z3950.pm
@@ -11,11 +11,14 @@ use XML::LibXML;
 
 use OpenILS::Event;
 use OpenSRF::EX qw(:try);
+use OpenSRF::MultiSession;
 use OpenILS::Utils::ModsParser;
 use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::JSON;
 use OpenILS::Application::AppUtils;
 use OpenSRF::Utils::Logger qw/$logger/;
 use OpenILS::Utils::CStoreEditor q/:funcs/;
+use OpenILS::Utils::Normalize qw/clean_marc/;                                  
 
 MARC::Charset->assume_unicode(1);
 MARC::Charset->ignore_errors(1);
@@ -437,7 +440,7 @@ sub process_results {
     my $res = {};
     my $count = $$res{count} = $results->size;
 
-    $logger->info("z3950: search returned $count hits");
+    $logger->info("z3950: '$service' search returned $count hits");
 
     my $tend = $limit + $offset;
 
@@ -525,5 +528,375 @@ sub compile_query {
     return $str;
 }
 
+
+__PACKAGE__->register_method(
+    method    => 'bucket_search_queue',
+    api_name  => 'open-ils.search.z3950.bucket_search_queue',
+    stream    => 1,
+    # disable opensrf chunking so the caller can receive timely responses
+    max_chunk_size => 0,
+    signature => {
+        desc => q/
+            Performs a Z39.50 search for every record in a bucket, using the
+            provided Z39.50 fields.  Add all search results to the specified
+            Vandelay queue.  If no source records or search results are found,
+            no queue is created.
+        /,
+        params => [
+            {desc => q/Authentication token/, type => 'string'},
+            {desc => q/Bucket ID/, type => 'number'},
+            {desc => q/Z39 Sources.  List of czs.name/, type => 'array'},
+            {desc => q/Z39 Index Maps.  List of czifm.id/, type => 'array'},
+            {   desc => q/Vandelay arguments
+                    queue_name -- required
+                    match_set
+                    ...
+                    /, 
+                type => 'object'
+            }
+        ],
+        return => {
+            q/Object containing status information about the on-going search
+            and queue operation. 
+            {
+                bre_count    : $num, -- number of bibs to search against
+                search_count : $num,
+                search_complete  : $num,
+                queue_count  : $num
+                queue        : $queue_obj
+            }
+            This object will be streamed back with each milestone (search
+            result or complete).
+            Event object returned on failure
+            /
+        }
+    }
+);
+
+sub bucket_search_queue {
+    my $self = shift;
+    my $conn = shift;
+    my $auth = shift;
+    my $bucket_id = shift;
+    my $z_sources = shift;
+    my $z_indexes = shift;
+    my $vandelay = shift;
+
+    my $e = new_editor(authtoken => $auth);
+    return $e->event unless 
+        $e->checkauth and
+        $e->allowed('REMOTE_Z3950_QUERY') and
+        $e->allowed('CREATE_BIB_IMPORT_QUEUE');
+    
+    # find the source bib records
+
+    my $bre_ids = $e->json_query({
+        select => {cbrebi => ['target_biblio_record_entry']},
+        from => 'cbrebi',
+        where => {bucket => $bucket_id},
+        distinct => 1
+    });
+
+    # empty bucket
+    return {bre_count => 0} unless @$bre_ids;
+
+    $bre_ids = [ map {$_->{target_biblio_record_entry}} @$bre_ids ];
+
+    $z_indexes = $e->search_config_z3950_index_field_map({id => $z_indexes});
+
+    return OpenILS::Event->new('BAD_PARAMS', 
+        note => q/No z_indexes/) unless @$z_indexes;
+
+    # build the Z39 queries for the source bib records
+
+    my $z_searches = compile_bucket_zsearch(
+        $e, $bre_ids, $z_sources, $z_indexes);
+
+    return $e->event unless $z_searches;
+    return {bre_count => 0} unless @$z_searches;
+
+    my $queue = create_z39_bucket_queue($e, $bucket_id, $vandelay);
+    return $e->event unless $queue;
+
+    send_and_queue_bucket_searches($conn, $e, $queue, $z_searches);
+
+    return undef;
+}
+
+ # create the queue for storing search results
+sub create_z39_bucket_queue {
+    my ($e, $bucket_id, $vandelay) = @_;
+
+    my $existing = $e->search_vandelay_bib_queue({
+        name => $vandelay->{queue_name},
+        owner => $e->requestor->id
+    })->[0];
+
+    return $existing if $existing;
+
+    my $queue = Fieldmapper::vandelay::bib_queue->new;
+    $queue->match_bucket($bucket_id);
+    $queue->owner($e->requestor->id);
+    $queue->name($vandelay->{queue_name});
+    $queue->match_set($vandelay->{match_set});
+
+    $e->xact_begin;
+    unless ($e->create_vandelay_bib_queue($queue)) {
+        $e->rollback;
+        return undef;
+    }
+    $e->commit;
+
+    return $queue;
+}
+
+# sets the 901c value to the Z39 service and 
+# adds the record to the growing vandelay queue
+# returns the number of successfully queued records
+sub stamp_and_queue_results {
+    my ($e, $queue, $service, $bre_id, $result) = @_;
+    my $qcount = 0;
+
+    for my $rec (@{$result->{records}}) {
+        # insert z39 service as the 901z
+        my $marc = MARC::Record->new_from_xml(
+            $rec->{marcxml}, 'UTF-8', 'USMARC');
+
+        $marc->insert_fields_ordered(
+            MARC::Field->new('901', '', '', z => $service));
+
+        # put the record into the queue
+        my $qrec = Fieldmapper::vandelay::queued_bib_record->new;
+        $qrec->marc(clean_marc($marc));
+        $qrec->queue($queue->id);
+
+        $e->xact_begin;
+        if ($e->create_vandelay_queued_bib_record($qrec)) {
+            $e->commit;
+            $qcount++;
+        } else {
+            my $evt = $e->die_event;
+            $logger->error("z39: unable to queue record: $evt");
+        }
+    }
+
+    return $qcount;
+}
+
+sub send_and_queue_bucket_searches {
+    my ($conn, $e, $queue, $z_searches) = @_;
+
+    my $max_parallel = 5; # TODO org setting
+    my $search_limit = 5; # TODO org setting
+
+    my $response = {
+        bre_count => 0,
+        search_count => 0,
+        search_complete => 0,
+        queue_count => 0
+    };
+
+    # searches are about to be in flight
+    # let the caller know we're still alive
+    $conn->respond($response);
+
+    my $handle_search_result = sub {
+        my ($self, $req) = @_;
+        my $bre_id = $req->{req}->{_bre_id};
+
+        my @p = $req->{req}->payload->params;
+        $logger->debug("z39: multi-search response for request [$bre_id]". 
+            OpenSRF::Utils::JSON->perl2JSON(\@p));
+
+        for my $resp (@{$req->{response}}) {
+            $response->{search_complete}++;
+            my $result = $resp->content or next;
+            my $service = $result->{service};
+            $response->{queue_count} += 
+                stamp_and_queue_results($e, $queue, $service, $bre_id, $result);
+        }
+
+        $conn->respond($response);
+    };
+
+    my $multi_ses = OpenSRF::MultiSession->new(
+        app             => 'open-ils.search',
+        cap             => $max_parallel,
+        timeout         => 120,
+        success_handler => $handle_search_result
+    );
+
+    # note: mult-session blocks new requests when it hits max 
+    # parallel, so we need to cacluate summary values up front.
+    my %bre_uniq;
+    $bre_uniq{$_->{bre_id}} = 1 for @$z_searches;
+    $response->{bre_count} = scalar(keys %bre_uniq);
+    $response->{search_count} += scalar(@$z_searches);
+
+    # let the caller know searches are on their way out
+    $conn->respond($response);
+
+    for my $search (@$z_searches) {
+
+        my $bre_id = delete $search->{bre_id};
+        $search->{limit} = $search_limit;
+
+        # toss it onto the multi-pile
+        my $req = $multi_ses->request(
+            'open-ils.search.z3950.search_class', $e->authtoken, $search);
+
+        $req->{_bre_id} = $bre_id;
+    }
+
+    $multi_ses->session_wait(1);
+    $response->{queue} = $queue;
+    $conn->respond($response);
+}
+
+
+# creates a series of Z39.50 searchs based on the 
+# in-bucket records and the selected sources and indexes
+sub compile_bucket_zsearch {
+    my ($e, $bre_ids, $z_sources, $z_indexes) = @_;
+
+    # pre-load the metabib_field's we'll need for this batch
+
+    my %mb_fields;
+    my @mb_fields = grep { $_->metabib_field } @$z_indexes;
+    if (@mb_fields) {
+        @mb_fields = map { $_->metabib_field } @mb_fields;
+        my $field_objs = $e->search_config_metabib_field({id => \@mb_fields});
+        %mb_fields = map {$_->id => $_} @$field_objs;
+    }
+
+    # pre-load the z3950_attrs we'll need for this batch
+
+    my %z3950_attrs;
+    my @z3950_attrs = grep { $_->z3950_attr } @$z_indexes;
+    if (@z3950_attrs) {
+        @z3950_attrs = map { $_->z3950_attr } @z3950_attrs;
+        my $attr_objs = $e->search_config_z3950_attr({id => \@z3950_attrs});
+        %z3950_attrs = map {$_->id => $_} @$attr_objs;
+    }
+
+    # indexes with specific z3950_attr's take precedence
+    my @z_index_attrs = grep { $_->z3950_attr } @$z_indexes;
+    my @z_index_types = grep { !$_->z3950_attr } @$z_indexes;
+
+    # for each bib record, extract the indexed value for the selected indexes.  
+    my %z_searches;
+
+    for my $bre_id (@$bre_ids) {
+
+        $z_searches{$bre_id} = {};
+
+        for my $z_index (@z_index_attrs, @z_index_types) {
+
+            my $bre_val;
+            if ($z_index->record_attr) {
+
+                my $attrs = $U->get_bre_attrs($bre_id, $e);
+                $bre_val = $attrs->{$bre_id}{$z_index->record_attr}{code};
+
+            } else { # metabib_field
+                my $fid = $z_index->metabib_field;
+
+                # the value for each field will be in the 
+                # index class-specific table
+                my $entry_query = sprintf(
+                    'search_metabib_%s_field_entry', 
+                    $mb_fields{$fid}->field_class);
+
+                my $entry = $e->$entry_query(
+                    {field => $fid, source => $bre_id})->[0];
+
+                $bre_val = $entry->value if $entry;
+            }
+
+            # no value means no search
+            next unless $bre_val;
+
+            # determine which z3950 source to send this search field to 
+
+            my $z_source = [];
+            my $z_index_name;
+            if ($z_index->z3950_attr) {
+
+                # a specific z3950_attr means this search index
+                # only applies to the z_source linked to the attr
+
+                $z_index_name = $z3950_attrs{$z_index->z3950_attr}->name;
+                my $src = $z3950_attrs{$z_index->z3950_attr}->source;
+
+                if (grep { $_ eq $src } @$z_sources) {
+                    $z_searches{$bre_id}{$src} ||= {
+                        service => [$src],
+                        search => {}
+                    };
+                    $z_searches{$bre_id}{$src}{search}{$z_index_name} = $bre_val;
+
+                } else {
+                    $logger->warn("z39: z3950_attr '$z_index_name' for '$src'".
+                        " selected, but $src is not in the search list.  Skipping...");
+                }
+
+            } else {
+
+                # when a generic attr type is used, it applies to all 
+                # z-sources, except those for which a more specific
+                # z3950_attr has already been applied
+
+                $z_index_name = $z_index->z3950_attr_type;
+
+                my @excluded;
+                for my $attr (values %z3950_attrs) {
+                    push(@excluded, $attr->source)
+                        if $attr->name eq $z_index_name;
+                }
+
+                for my $src (@$z_sources) {
+                    next if grep {$_ eq $src} @excluded;
+                    $z_searches{$bre_id}{$src} ||= {
+                        service => [$src],
+                        search => {}
+                    };
+                    $z_searches{$bre_id}{$src}{search}{$z_index_name} = $bre_val;
+                }
+            }
+        }
+    }
+
+    # NOTE: ISBNs are sent through the translate_isbn1013 normalize
+    # before entring metabib.identifier_field_entry.  As such, there
+    # will always be at minimum 2 ISBNs per record w/ ISBN and the
+    # data will be pre-sanitized.  The first ISBN in the list is the
+    # ISBN from the record.  Use that for these searches.
+    for my $bre_id (keys %z_searches) {
+        for my $src (keys %{$z_searches{$bre_id}}) {
+            my $blob = $z_searches{$bre_id}{$src};
+
+            # Sanitized ISBNs are space-separated.
+            # kill everything past the first space
+            $blob->{search}{isbn} =~ s/\s.*//g if $blob->{search}{isbn};
+        }
+    }
+
+    # let's turn this into something slightly more digestable
+    my @searches;
+    for my $bre_id (keys %z_searches) {
+        for my $blobset (values %{$z_searches{$bre_id}}) {
+            $blobset = [$blobset] unless ref $blobset eq 'ARRAY';
+            for my $blob (@$blobset) {
+                $blob->{bre_id} = $bre_id;
+                push(@searches, $blob);
+            }
+        }
+    }
+
+    return \@searches;
+}
+
+
+
 1;
 # vim:et:ts=4:sw=4:

commit 832e9b0bc783d12babf6dd0f310a7e7fb8f78fc9
Author: Bill Erickson <berick at esilibrary.com>
Date:   Wed Jan 30 15:08:47 2013 -0500

    Z39.50 Batch Search/Overlay : SQL / IDL
    
     * DB table for Z39.50 index field maps, which link Z39 search fields to
       indexed bib data via metabib fields and record attributes (svf)
    
     * Seed data for Z39.50 batch search org unit settings
      ** cat.z3950.batch.max_parallel
      ** cat.z3950.batch.max_results
    
     * Stock seed data for config.z3950_index_field_map entries
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 122e044..8e69bbd 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -966,6 +966,34 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
         </permacrud>
     </class>
 
+    <class id="czifm" controller="open-ils.cstore open-ils.pcrud" 
+			oils_obj:fieldmapper="config::z3950_index_field_map" 
+			oils_persist:tablename="config.z3950_index_field_map" 
+			reporter:label="Z39.50 Index Field Map">
+        <fields oils_persist:primary="id" oils_persist:sequence="config.z3950_index_field_map_id_seq">
+            <field reporter:label="Map ID" name="id" reporter:datatype="id"/>
+            <field reporter:label="Label" name="label" reporter:datatype="text" oils_persist:i18n="true"/>
+            <field reporter:label="Metabib Field" name="metabib_field" reporter:datatype="link"/>
+            <field reporter:label="Record Attribute" name="record_attr" reporter:datatype="link"/>
+            <field reporter:label="Z39.50 Attribute" name="z3950_attr"  reporter:datatype="link"/>
+            <field reporter:label="Z39.50 Attribute Type" name="z3950_attr_type" reporter:datatype="text"/>
+        </fields>
+        <links>
+            <link field="metabib_field" reltype="has_a" key="id" map="" class="cmf"/>
+            <link field="record_attr" reltype="has_a" key="id" map="" class="crad"/>
+            <link field="z3950_attr" reltype="has_a" key="id" map="" class="cza"/>
+        </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="ADMIN_Z3950_SOURCE" global_required="true"/>
+                <retrieve/>
+                <update permission="ADMIN_Z3950_SOURCE" global_required="true"/>
+                <delete permission="ADMIN_Z3950_SOURCE" global_required="true"/>
+            </actions>
+        </permacrud>
+    </class>
+
+
 	<class id="ateo" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action_trigger::event_output" oils_persist:tablename="action_trigger.event_output" reporter:label="Event Output">
 		<fields oils_persist:primary="id" oils_persist:sequence="action_trigger.event_output_id_seq">
 			<field reporter:label="Output ID" name="id" reporter:datatype="id"/>
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
new file mode 100644
index 0000000..5e65faf
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.z39-batch-fetch-overlay.sql
@@ -0,0 +1,53 @@
+BEGIN;
+
+-- TODO version check
+
+CREATE OR REPLACE FUNCTION 
+    evergreen.z3950_name_is_valid(TEXT) RETURNS BOOLEAN AS $func$
+    SELECT EXISTS (SELECT 1 FROM config.z3950_attr WHERE name = $1);
+$func$ LANGUAGE SQL STRICT IMMUTABLE;
+
+
+CREATE TABLE config.z3950_index_field_map (
+    id              SERIAL  PRIMARY KEY,
+    label           TEXT    NOT NULL, -- i18n
+    metabib_field   INTEGER REFERENCES config.metabib_field(id),
+    record_attr     TEXT    REFERENCES config.record_attr_definition(name),
+    z3950_attr      INTEGER REFERENCES config.z3950_attr(id),
+    z3950_attr_type TEXT,-- REFERENCES config.z3950_attr(name)
+    CONSTRAINT metabib_field_or_record_attr CHECK (
+        metabib_field IS NOT NULL OR 
+        record_attr IS NOT NULL
+    ),
+    CONSTRAINT attr_or_attr_type CHECK (
+        z3950_attr IS NOT NULL OR 
+        z3950_attr_type IS NOT NULL
+    ),
+    -- ensure the selected z3950_attr_type refers to a valid attr name
+    CONSTRAINT valid_z3950_attr_type CHECK (
+        z3950_attr_type IS NULL OR 
+            evergreen.z3950_name_is_valid(z3950_attr_type)
+    )
+);
+
+
+-- seed data
+
+INSERT INTO config.z3950_index_field_map 
+    (id, label, metabib_field, z3950_attr_type) VALUES 
+(1, oils_i18n_gettext(1, 'Title',   'czifm', 'label'), 5,  'title'),
+(2, oils_i18n_gettext(2, 'Author',  'czifm', 'label'), 8,  'author'),
+(3, oils_i18n_gettext(3, 'ISBN',    'czifm', 'label'), 18, 'isbn'),
+(4, oils_i18n_gettext(4, 'ISSN',    'czifm', 'label'), 19, 'issn'),
+(5, oils_i18n_gettext(5, 'LCCN',    'czifm', 'label'), 30, 'lccn');
+
+INSERT INTO config.z3950_index_field_map 
+    (id, label, record_attr, z3950_attr_type) VALUES 
+(6, oils_i18n_gettext(6, 'Pubdate',  'czifm', 'label'),'pubdate', 'pubdate'),
+(7, oils_i18n_gettext(7, 'Item Type', 'czifm', 'label'),'item_type', 'item_type');
+
+
+-- let's leave room for more stock mappings
+SELECT SETVAL('config.z3950_index_field_map_id_seq'::TEXT, 1000);
+
+COMMIT;

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |   34 ++-
 .../lib/OpenILS/Application/Search/Z3950.pm        |  380 +++++++++++++++++++-
 .../perlmods/lib/OpenILS/Application/Vandelay.pm   |   25 ++-
 .../lib/OpenILS/WWW/EGCatLoader/Account.pm         |    7 +
 Open-ILS/src/sql/Pg/002.schema.config.sql          |   37 ++-
 Open-ILS/src/sql/Pg/012.schema.vandelay.sql        |   21 +-
 Open-ILS/src/sql/Pg/800.fkeys.sql                  |    1 +
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |   58 +++
 .../0795.schema.z39-batch-fetch-overlay.sql        |  107 ++++++
 .../upgrade/0796.schema.vandelay_bucket_match.sql  |  136 +++++++
 .../conify/global/config/z3950_index_field_map.tt2 |  118 ++++++
 Open-ILS/src/templates/opac/myopac/lists.tt2       |   21 ++
 Open-ILS/src/templates/vandelay/inc/upload.tt2     |    5 +
 Open-ILS/web/js/ui/default/vandelay/vandelay.js    |   47 +++-
 Open-ILS/web/opac/locale/en-US/lang.dtd            |   13 +
 .../xul/staff_client/chrome/content/main/menu.js   |    4 +
 .../chrome/content/main/menu_frame_menus.xul       |    4 +
 .../staff_client/server/cat/bucketz39_dialog.js    |  236 ++++++++++++
 .../batch_receive.xul => cat/bucketz39_dialog.xul} |   13 +-
 .../server/cat/bucketz39_dialog_overlay.xul        |   97 +++++
 .../xul/staff_client/server/cat/record_buckets.js  |   25 ++
 .../server/cat/record_buckets_overlay.xul          |    3 +
 .../xul/staff_client/server/skin/bucketz39.css     |    5 +
 docs/RELEASE_NOTES_NEXT/z39_batch_search_queue.txt |   89 +++++
 24 files changed, 1464 insertions(+), 22 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0795.schema.z39-batch-fetch-overlay.sql
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0796.schema.vandelay_bucket_match.sql
 create mode 100644 Open-ILS/src/templates/conify/global/config/z3950_index_field_map.tt2
 create mode 100644 Open-ILS/xul/staff_client/server/cat/bucketz39_dialog.js
 copy Open-ILS/xul/staff_client/server/{serial/batch_receive.xul => cat/bucketz39_dialog.xul} (70%)
 create mode 100644 Open-ILS/xul/staff_client/server/cat/bucketz39_dialog_overlay.xul
 create mode 100644 Open-ILS/xul/staff_client/server/skin/bucketz39.css
 create mode 100644 docs/RELEASE_NOTES_NEXT/z39_batch_search_queue.txt


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list