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

Evergreen Git git at git.evergreen-ils.org
Wed Aug 19 12:17:46 EDT 2015


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  3cd206a5e61f6665420602dd2e9da15270a5b26c (commit)
       via  303608ce23452f1d59e924da7656e7e4c65b603c (commit)
       via  97806b5b66b3b5685e9bd231a2b3c59f4b729daf (commit)
       via  8df8d6be077fbaf5360c379e3ee1f0934dcc9dd0 (commit)
       via  f9747810978804a79786349e7f604e0f838ddefc (commit)
       via  b1c8c14e48abaa729ae164fd969ffe5c304a857d (commit)
       via  55f4479eb3adeb2f67ab88b566d8fc7453bc3ed3 (commit)
      from  9e946f5af3be4ecfe7d694a902c5a2ce8f9a4d23 (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 3cd206a5e61f6665420602dd2e9da15270a5b26c
Author: Ben Shum <bshum at biblio.org>
Date:   Wed Aug 19 12:17:17 2015 -0400

    LP#1379815: Stamping upgrade scripts for vandelay stat cat import
    
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 3b00fb8..209df2d 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 ('0927', :eg_version); -- dbwells/csharp/bshum
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0929', :eg_version); -- dbwells/remington/bshum
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.function.vandelay-oils_xpath_tag_to_table.sql b/Open-ILS/src/sql/Pg/upgrade/0928.function.vandelay-oils_xpath_tag_to_table.sql
similarity index 95%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.function.vandelay-oils_xpath_tag_to_table.sql
rename to Open-ILS/src/sql/Pg/upgrade/0928.function.vandelay-oils_xpath_tag_to_table.sql
index 48e44f8..9796f8d 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.function.vandelay-oils_xpath_tag_to_table.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0928.function.vandelay-oils_xpath_tag_to_table.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
---SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('0928', :eg_version);
 
 CREATE OR REPLACE FUNCTION oils_xpath_tag_to_table(marc text, tag text, xpaths text[]) RETURNS SETOF record AS $function$
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql b/Open-ILS/src/sql/Pg/upgrade/0929.schema.vandelay-stat-cat-import.sql
similarity index 99%
rename from Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql
rename to Open-ILS/src/sql/Pg/upgrade/0929.schema.vandelay-stat-cat-import.sql
index 57b0a45..e475366 100644
--- a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0929.schema.vandelay-stat-cat-import.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
---SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('0929', :eg_version);
 
 INSERT INTO vandelay.import_error ( code, description ) VALUES (
     'import.item.invalid.stat_cat_format', oils_i18n_gettext('import.item.invalid.stat_cat_format', 'Bad format for stat cat data, should be like: CAT 1|VALUE 1', 'vie', 'description') );

commit 303608ce23452f1d59e924da7656e7e4c65b603c
Author: Remington Steed <rjs7 at calvin.edu>
Date:   Tue Jul 7 16:08:23 2015 -0400

    LP#1379815 Better error handling
    
    This commit adds more thorough error checking and handling for the stat
    cat import feature, including adding two custom import error codes.
    
    Signed-off-by: Remington Steed <rjs7 at calvin.edu>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
index b5ae6f6..a93a51e 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
@@ -1931,16 +1931,28 @@ sub import_record_asset_list_impl {
                             push (@$stat_cat_entries, $stat_cat_entry) if $stat_cat_entry;
                         }
                     } else {
-                        $logger->warn("Bad format for stat cat data ($stat_cat_pair), should be like: CAT 1|VALUE 1");
+                        $$report_args{import_error} = "import.item.invalid.stat_cat_format";
+                        last;
                     }
 
                     if (!$stat_cat or !$stat_cat_entry) {
-                        $logger->warn("Invalid stat cat data ($stat_cat_pair): " . $e->die_event);
+                        $$report_args{import_error} = "import.item.invalid.stat_cat_data";
+                        last;
                     }
                 }
+                if ($$report_args{import_error}) {
+                    $logger->error("vl: invalid stat cat data: " . $item->stat_cat_data);
+                    respond_with_status($report_args);
+                    next;
+                }
                 $copy->stat_cat_entries( $stat_cat_entries );
                 $copy->ischanged(1);
                 $evt = OpenILS::Application::Cat::AssetCommon->update_copy_stat_entries($e, $copy, 0, 1); #delete_stats=0, add_or_update_only=1
+                if($evt) {
+                    $$report_args{evt} = $evt;
+                    respond_with_status($report_args);
+                    next;
+                }
             }
 
             # set the import data on the import item
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 0ce75a4..3e30e7c 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -10399,6 +10399,10 @@ INSERT INTO vandelay.import_error ( code, description ) VALUES (
     'import.item.invalid.circ_as_type', oils_i18n_gettext('import.item.invalid.circ_as_type', 'Invalid value for "circ_as_type"', 'vie', 'description') );
 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
     'import.record.perm_failure', oils_i18n_gettext('import.record.perm_failure', 'Perm failure creating a record', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES (
+    'import.item.invalid.stat_cat_format', oils_i18n_gettext('import.item.invalid.stat_cat_format', 'Bad format for stat cat data, should be like: CAT 1|VALUE 1', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES (
+    'import.item.invalid.stat_cat_data', oils_i18n_gettext('import.item.invalid.stat_cat_data', 'Invalid stat cat data', 'vie', 'description') );
 
 -- Event def for email notice for hold cancelled due to lack of target -----
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql
index 0256b1b..57b0a45 100644
--- a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql
@@ -2,6 +2,11 @@ BEGIN;
 
 --SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
 
+INSERT INTO vandelay.import_error ( code, description ) VALUES (
+    'import.item.invalid.stat_cat_format', oils_i18n_gettext('import.item.invalid.stat_cat_format', 'Bad format for stat cat data, should be like: CAT 1|VALUE 1', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES (
+    'import.item.invalid.stat_cat_data', oils_i18n_gettext('import.item.invalid.stat_cat_data', 'Invalid stat cat data', 'vie', 'description') );
+
 ALTER TABLE vandelay.import_item_attr_definition
     ADD COLUMN stat_cat_data TEXT;
 

commit 97806b5b66b3b5685e9bd231a2b3c59f4b729daf
Author: Remington Steed <rjs7 at calvin.edu>
Date:   Fri Feb 20 10:54:26 2015 -0500

    LP#1379815 Add release notes
    
    Signed-off-by: Remington Steed <rjs7 at calvin.edu>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/docs/RELEASE_NOTES_NEXT/Cataloging/importing_stat_cats.txt b/docs/RELEASE_NOTES_NEXT/Cataloging/importing_stat_cats.txt
new file mode 100644
index 0000000..7d2e828
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/Cataloging/importing_stat_cats.txt
@@ -0,0 +1,19 @@
+Importing Statistical Categories
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+You can now retrieve statistical categories (stat cats) from the MARC
+record and apply them to the items in Evergreen. When importing or
+overlaying items through the Vandelay MARC batch import process, edit
+your Holdings Import Profile to tell Evergreen which subfield contains
+your stat cat data. That subfield in your MARC records should be
+formatted like the following:
+
+----
+CATEGORY 1|VALUE 1||CATEGORY 2|VALUE 2
+----
+
+Notice that the pipe character '|' is used to separate each category
+from its value, and two pipes separate each pair of category values.
+
+If you are overlaying existing copies which already have stat cats
+attached to them, the overlay process will keep those values unless the
+incoming copies contain updated values for matching categories.

commit 8df8d6be077fbaf5360c379e3ee1f0934dcc9dd0
Author: Remington Steed <rjs7 at calvin.edu>
Date:   Fri Feb 20 09:57:18 2015 -0500

    LP#1379815 Add pgTAP test
    
    This commit adds a pgTAP test which ensures that imported items are
    being successfully added to the 'import_item' table. The test confirms
    that the correct number of items have stat cat data in the table. The
    test also ensures that the new xpath function works properly, simply by
    using it.
    
    Signed-off-by: Remington Steed <rjs7 at calvin.edu>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/sql/Pg/t/regress/lp1379815_vl_import_item_stat_cats.pg b/Open-ILS/src/sql/Pg/t/regress/lp1379815_vl_import_item_stat_cats.pg
new file mode 100644
index 0000000..1756266
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/t/regress/lp1379815_vl_import_item_stat_cats.pg
@@ -0,0 +1,52 @@
+BEGIN;
+
+SELECT plan(1);
+
+-------------------------
+-- Setup test environment
+--   Circ modifier
+--   Vandelay settings (queue, import_item_attr_definition)
+--   Bib record added to Vandelay queue
+--     including items with stat cat data
+-------------------------
+
+INSERT INTO config.circ_modifier (code, name, description, sip2_media_type)
+    VALUES ('TEST', 'TEST', 'TEST', 'TEST');
+
+INSERT INTO vandelay.import_item_attr_definition (
+    owner, name, tag, owning_lib, circ_lib,
+    call_number, internal_id, status, location,
+    barcode, circ_modifier, stat_cat_data)
+VALUES (
+    1, 'TEST', '999', 'b', 'b',
+    'j', 'e', 'z', 'c',
+    'p', 'a', 'd');
+
+INSERT INTO vandelay.bib_queue (owner, name, item_attr_def)
+    VALUES (1, 'TEST', CURRVAL('vandelay.import_item_attr_definition_id_seq'));
+
+----------------------
+-- Add record to queue
+--   This triggers ingest_bib_items()
+--   which calls ingest_items()
+----------------------
+INSERT INTO vandelay.queued_bib_record (queue, bib_source, marc)
+    VALUES ( CURRVAL('vandelay.queue_id_seq'), 2,
+            '<record xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.loc.gov/MARC21/slim" xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"><leader>01532nam a22003138a 4500</leader><controlfield tag="001">996</controlfield><controlfield tag="003">CONS</controlfield><controlfield tag="005">20140910210953.0</controlfield><controlfield tag="008">070119s2009    nyua   j      000 1 eng  </controlfield><datafield tag="010" ind1=" " ind2=" "><subfield code="a">  2006100441</subfield></datafield><datafield tag="019" ind1=" " ind2=" "><subfield code="a">232977713</subfield></datafield><datafield tag="020" ind1=" " ind2=" "><subfield code="a">97800608089 (trade bdg.)</subfield></datafield><datafield tag="035" ind1=" " ind2=" "><subfield code="a">test</subfield></datafield><datafield tag="100" ind1="1" ind2=" "><subfield code="a">Gaiman, Neil.</subfield></datafield><datafield tag="245" ind1="1" ind2="0
 "><subfield code="a">Blueberry girl 2 /</subfield><subfield code="c">written by Neil Gaiman.</subfield></datafield><datafield tag="250" ind1=" " ind2=" "><subfield code="a">1st ed.</subfield></datafield><datafield tag="260" ind1=" " ind2=" "><subfield code="a">New York :</subfield><subfield code="b">HarperCollinsPublishers,</subfield><subfield code="c">2009.</subfield></datafield><datafield tag="999" ind1="4" ind2=" "><subfield code="a">TEST</subfield><subfield code="b">BR1</subfield><subfield code="c">Stacks</subfield><subfield code="d">Item Category 1|EBOOK</subfield><subfield code="e">4674</subfield><subfield code="j">HV 1431 .S76 1990</subfield><subfield code="p">SC1001</subfield><subfield code="x">nonreference</subfield><subfield code="x">holdable</subfield><subfield code="x">circulating</subfield><subfield code="x">visible</subfield><subfield code="z">Available</subfield></datafield><datafield tag="999" ind1="4" ind2=" "><subfield code="a">TEST</subfield><subfield code
 ="b">BR1</subfield><subfield code="c">Stacks</subfield><subfield code="e">4675</subfield><subfield code="j">HV 1431 .S76 1990</subfield><subfield code="g">TEST</subfield><subfield code="p">SC1002</subfield><subfield code="x">nonreference</subfield><subfield code="x">holdable</subfield><subfield code="x">circulating</subfield><subfield code="x">visible</subfield><subfield code="z">Available</subfield></datafield><datafield tag="999" ind1="4" ind2=" "><subfield code="a">TEST</subfield><subfield code="b">BR1</subfield><subfield code="c">Stacks</subfield><subfield code="d">Item Category 1|EBOOK||Item Category 2|E-PROJECT MUSE</subfield><subfield code="j">HV 1431 .S76 1990</subfield><subfield code="p">SC3003</subfield><subfield code="x">nonreference</subfield><subfield code="x">holdable</subfield><subfield code="x">circulating</subfield><subfield code="x">visible</subfield><subfield code="z">Available</subfield></datafield><datafield tag="999" ind1="4" ind2=" "><subfield code="a"
 >TEST</subfield><subfield code="b">BR1</subfield><subfield code="c">Stacks</subfield><subfield code="j">HV 1431 .S76 1990</subfield><subfield code="p">SC3004</subfield><subfield code="x">nonreference</subfield><subfield code="x">holdable</subfield><subfield code="x">circulating</subfield><subfield code="x">visible</subfield><subfield code="z">Available</subfield></datafield></record>'
+    );
+
+-------------------
+-- Test the results
+-------------------
+SELECT is(
+    (
+        SELECT count(*)
+            FROM vandelay.import_item
+            WHERE record = CURRVAL('vandelay.queued_record_id_seq')
+            AND stat_cat_data IS NOT NULL
+    ),
+    2::bigint,
+    'Stat cat data?'
+);
+
+ROLLBACK;

commit f9747810978804a79786349e7f604e0f838ddefc
Author: Remington Steed <rjs7 at calvin.edu>
Date:   Mon Sep 8 16:11:37 2014 -0400

    LP#1379815 Add code to assign stat cats on Vandelay imported items
    
    This commit adds columns to the Holdings Import Profile table
    (vandelay.import_item_attr_definition) and to vandelay.import_item, adds
    the corresponding fields to the IDL, modifies the database functions
    which import the data (ingest_bib_items() and ingest_items()) and adds
    code that parses the imported stat cat data (of the form
    CAT 1|VALUE 1||CAT 2|VALUE 2) and applies it to the appropriate
    overlayed copy.
    
    Signed-off-by: Remington Steed <rjs7 at calvin.edu>
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 9776286..4686e7f 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -294,6 +294,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			<field reporter:label="Private Note" name="priv_note" reporter:datatype="text"/>
 			<field reporter:label="OPAC Visible" name="opac_visible" reporter:datatype="bool"/>
 			<field reporter:label="Overlay Match ID" name="internal_id" reporter:datatype="int"/>
+			<field reporter:label="Stat Cat Data" name="stat_cat_data" reporter:datatype="text"/>
 		</fields>
 		<links>
 			<link field="import_error" reltype="has_a" key="code" map="" class="vie"/>
@@ -346,6 +347,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			<field reporter:label="OPAC Visible" name="opac_visible" reporter:datatype="text"/>
 			<field reporter:label="Copy Number" name="copy_number" reporter:datatype="text"/>
 			<field reporter:label="Overlay Match ID" name="internal_id" reporter:datatype="text"/>
+			<field reporter:label="Stat Cat Data" name="stat_cat_data" reporter:datatype="text"/>
 		</fields>
 		<links>
 			<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
index 588642d..b5ae6f6 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
@@ -1916,6 +1916,33 @@ sub import_record_asset_list_impl {
                 }
             }
 
+            if ($item->stat_cat_data) {
+                $logger->info("vl: parsing stat cat data: " . $item->stat_cat_data);
+                my @stat_cat_pairs = split('\|\|', $item->stat_cat_data);
+                my $stat_cat_entries = [];
+                # lookup stat cats
+                foreach my $stat_cat_pair (@stat_cat_pairs) {
+                    my ($stat_cat, $stat_cat_entry);
+                    my @pair_pieces = split('\|', $stat_cat_pair);
+                    if (@pair_pieces == 2) {
+                        $stat_cat = $e->search_asset_stat_cat({name=>$pair_pieces[0]})->[0];
+                        if ($stat_cat) {
+                            $stat_cat_entry = $e->search_asset_stat_cat_entry({'value' => $pair_pieces[1], 'stat_cat' => $stat_cat->id})->[0];
+                            push (@$stat_cat_entries, $stat_cat_entry) if $stat_cat_entry;
+                        }
+                    } else {
+                        $logger->warn("Bad format for stat cat data ($stat_cat_pair), should be like: CAT 1|VALUE 1");
+                    }
+
+                    if (!$stat_cat or !$stat_cat_entry) {
+                        $logger->warn("Invalid stat cat data ($stat_cat_pair): " . $e->die_event);
+                    }
+                }
+                $copy->stat_cat_entries( $stat_cat_entries );
+                $copy->ischanged(1);
+                $evt = OpenILS::Application::Cat::AssetCommon->update_copy_stat_entries($e, $copy, 0, 1); #delete_stats=0, add_or_update_only=1
+            }
+
             # set the import data on the import item
             $item->imported_as($copy->id); # $copy->id is set by create_copy() ^--
             $item->import_time('now');
diff --git a/Open-ILS/src/sql/Pg/012.schema.vandelay.sql b/Open-ILS/src/sql/Pg/012.schema.vandelay.sql
index 27eb528..f750adb 100644
--- a/Open-ILS/src/sql/Pg/012.schema.vandelay.sql
+++ b/Open-ILS/src/sql/Pg/012.schema.vandelay.sql
@@ -107,6 +107,7 @@ CREATE TABLE vandelay.import_item_attr_definition (
     priv_note_title TEXT,
     priv_note       TEXT,
     internal_id     TEXT,
+    stat_cat_data   TEXT,
 	CONSTRAINT vand_import_item_attr_def_idx UNIQUE (owner,name)
 );
 
@@ -178,6 +179,7 @@ CREATE TABLE vandelay.import_item (
     alert_message   TEXT,
     pub_note        TEXT,
     priv_note       TEXT,
+    stat_cat_data   TEXT,
     opac_visible    BOOL,
     internal_id     BIGINT -- queue_type == 'acq' ? acq.lineitem_detail.id : asset.copy.id
 );
diff --git a/Open-ILS/src/sql/Pg/999.functions.global.sql b/Open-ILS/src/sql/Pg/999.functions.global.sql
index bb87a6b..589eab8 100644
--- a/Open-ILS/src/sql/Pg/999.functions.global.sql
+++ b/Open-ILS/src/sql/Pg/999.functions.global.sql
@@ -1724,12 +1724,13 @@ DECLARE
     pub_note        TEXT;
     priv_note       TEXT;
     internal_id     TEXT;
+    stat_cat_data   TEXT;
 
     attr_def        RECORD;
     tmp_attr_set    RECORD;
     attr_set        vandelay.import_item%ROWTYPE;
 
-    xpath           TEXT;
+    xpaths          TEXT[];
     tmp_str         TEXT;
 
 BEGIN
@@ -1745,172 +1746,162 @@ BEGIN
         owning_lib :=
             CASE
                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
+                WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '*[@code="' || attr_def.owning_lib || '"]'
+                ELSE '*' || attr_def.owning_lib
             END;
 
         circ_lib :=
             CASE
                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
+                WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '*[@code="' || attr_def.circ_lib || '"]'
+                ELSE '*' || attr_def.circ_lib
             END;
 
         call_number :=
             CASE
                 WHEN attr_def.call_number IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
+                WHEN LENGTH( attr_def.call_number ) = 1 THEN '*[@code="' || attr_def.call_number || '"]'
+                ELSE '*' || attr_def.call_number
             END;
 
         copy_number :=
             CASE
                 WHEN attr_def.copy_number IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
+                WHEN LENGTH( attr_def.copy_number ) = 1 THEN '*[@code="' || attr_def.copy_number || '"]'
+                ELSE '*' || attr_def.copy_number
             END;
 
         status :=
             CASE
                 WHEN attr_def.status IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
+                WHEN LENGTH( attr_def.status ) = 1 THEN '*[@code="' || attr_def.status || '"]'
+                ELSE '*' || attr_def.status
             END;
 
         location :=
             CASE
                 WHEN attr_def.location IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
+                WHEN LENGTH( attr_def.location ) = 1 THEN '*[@code="' || attr_def.location || '"]'
+                ELSE '*' || attr_def.location
             END;
 
         circulate :=
             CASE
                 WHEN attr_def.circulate IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
+                WHEN LENGTH( attr_def.circulate ) = 1 THEN '*[@code="' || attr_def.circulate || '"]'
+                ELSE '*' || attr_def.circulate
             END;
 
         deposit :=
             CASE
                 WHEN attr_def.deposit IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
+                WHEN LENGTH( attr_def.deposit ) = 1 THEN '*[@code="' || attr_def.deposit || '"]'
+                ELSE '*' || attr_def.deposit
             END;
 
         deposit_amount :=
             CASE
                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
+                WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '*[@code="' || attr_def.deposit_amount || '"]'
+                ELSE '*' || attr_def.deposit_amount
             END;
 
         ref :=
             CASE
                 WHEN attr_def.ref IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
+                WHEN LENGTH( attr_def.ref ) = 1 THEN '*[@code="' || attr_def.ref || '"]'
+                ELSE '*' || attr_def.ref
             END;
 
         holdable :=
             CASE
                 WHEN attr_def.holdable IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
+                WHEN LENGTH( attr_def.holdable ) = 1 THEN '*[@code="' || attr_def.holdable || '"]'
+                ELSE '*' || attr_def.holdable
             END;
 
         price :=
             CASE
                 WHEN attr_def.price IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
+                WHEN LENGTH( attr_def.price ) = 1 THEN '*[@code="' || attr_def.price || '"]'
+                ELSE '*' || attr_def.price
             END;
 
         barcode :=
             CASE
                 WHEN attr_def.barcode IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
+                WHEN LENGTH( attr_def.barcode ) = 1 THEN '*[@code="' || attr_def.barcode || '"]'
+                ELSE '*' || attr_def.barcode
             END;
 
         circ_modifier :=
             CASE
                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
+                WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '*[@code="' || attr_def.circ_modifier || '"]'
+                ELSE '*' || attr_def.circ_modifier
             END;
 
         circ_as_type :=
             CASE
                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
+                WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '*[@code="' || attr_def.circ_as_type || '"]'
+                ELSE '*' || attr_def.circ_as_type
             END;
 
         alert_message :=
             CASE
                 WHEN attr_def.alert_message IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
+                WHEN LENGTH( attr_def.alert_message ) = 1 THEN '*[@code="' || attr_def.alert_message || '"]'
+                ELSE '*' || attr_def.alert_message
             END;
 
         opac_visible :=
             CASE
                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
+                WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '*[@code="' || attr_def.opac_visible || '"]'
+                ELSE '*' || attr_def.opac_visible
             END;
 
         pub_note :=
             CASE
                 WHEN attr_def.pub_note IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
+                WHEN LENGTH( attr_def.pub_note ) = 1 THEN '*[@code="' || attr_def.pub_note || '"]'
+                ELSE '*' || attr_def.pub_note
             END;
         priv_note :=
             CASE
                 WHEN attr_def.priv_note IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
+                WHEN LENGTH( attr_def.priv_note ) = 1 THEN '*[@code="' || attr_def.priv_note || '"]'
+                ELSE '*' || attr_def.priv_note
             END;
 
         internal_id :=
             CASE
                 WHEN attr_def.internal_id IS NULL THEN 'null()'
-                WHEN LENGTH( attr_def.internal_id ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.internal_id || '"]'
-                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.internal_id
+                WHEN LENGTH( attr_def.internal_id ) = 1 THEN '*[@code="' || attr_def.internal_id || '"]'
+                ELSE '*' || attr_def.internal_id
+            END;
+
+        stat_cat_data :=
+            CASE
+                WHEN attr_def.stat_cat_data IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.stat_cat_data ) = 1 THEN '*[@code="' || attr_def.stat_cat_data || '"]'
+                ELSE '*' || attr_def.stat_cat_data
             END;
 
 
 
-        xpath :=
-            owning_lib      || '|' ||
-            circ_lib        || '|' ||
-            call_number     || '|' ||
-            copy_number     || '|' ||
-            status          || '|' ||
-            location        || '|' ||
-            circulate       || '|' ||
-            deposit         || '|' ||
-            deposit_amount  || '|' ||
-            ref             || '|' ||
-            holdable        || '|' ||
-            price           || '|' ||
-            barcode         || '|' ||
-            circ_modifier   || '|' ||
-            circ_as_type    || '|' ||
-            alert_message   || '|' ||
-            pub_note        || '|' ||
-            priv_note       || '|' ||
-            internal_id     || '|' ||
-            opac_visible;
+        xpaths := ARRAY[owning_lib, circ_lib, call_number, copy_number, status, location, circulate,
+                        deposit, deposit_amount, ref, holdable, price, barcode, circ_modifier, circ_as_type,
+                        alert_message, pub_note, priv_note, internal_id, stat_cat_data, opac_visible];
 
         FOR tmp_attr_set IN
                 SELECT  *
-                  FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
-                            AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
+                  FROM  oils_xpath_tag_to_table( (SELECT marc FROM vandelay.queued_bib_record WHERE id = import_id), attr_def.tag, xpaths)
+                            AS t( ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
-                                  circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, internal_id TEXT, opac_vis TEXT )
+                                  circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, internal_id TEXT,
+                                  stat_cat_data TEXT, opac_vis TEXT )
         LOOP
 
             attr_set.import_error := NULL;
@@ -2092,6 +2083,7 @@ BEGIN
             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
             attr_set.internal_id    := tmp_attr_set.internal_id::BIGINT;
+            attr_set.stat_cat_data  := tmp_attr_set.stat_cat_data; -- TEXT,
 
             RETURN NEXT attr_set;
 
@@ -2143,6 +2135,7 @@ BEGIN
             priv_note,
             internal_id,
             opac_visible,
+            stat_cat_data,
             import_error,
             error_detail
         ) VALUES (
@@ -2168,6 +2161,7 @@ BEGIN
             item_data.priv_note,
             item_data.internal_id,
             item_data.opac_visible,
+            item_data.stat_cat_data,
             item_data.import_error,
             item_data.error_detail
         );
diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql
new file mode 100644
index 0000000..0256b1b
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.vandelay-stat-cat-import.sql
@@ -0,0 +1,476 @@
+BEGIN;
+
+--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+ALTER TABLE vandelay.import_item_attr_definition
+    ADD COLUMN stat_cat_data TEXT;
+
+ALTER TABLE vandelay.import_item
+    ADD COLUMN stat_cat_data TEXT;
+
+CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
+DECLARE
+
+    owning_lib      TEXT;
+    circ_lib        TEXT;
+    call_number     TEXT;
+    copy_number     TEXT;
+    status          TEXT;
+    location        TEXT;
+    circulate       TEXT;
+    deposit         TEXT;
+    deposit_amount  TEXT;
+    ref             TEXT;
+    holdable        TEXT;
+    price           TEXT;
+    barcode         TEXT;
+    circ_modifier   TEXT;
+    circ_as_type    TEXT;
+    alert_message   TEXT;
+    opac_visible    TEXT;
+    pub_note        TEXT;
+    priv_note       TEXT;
+    internal_id     TEXT;
+    stat_cat_data   TEXT;
+
+    attr_def        RECORD;
+    tmp_attr_set    RECORD;
+    attr_set        vandelay.import_item%ROWTYPE;
+
+    xpaths          TEXT[];
+    tmp_str         TEXT;
+
+BEGIN
+
+    SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
+
+    IF FOUND THEN
+
+        attr_set.definition := attr_def.id;
+
+        -- Build the combined XPath
+
+        owning_lib :=
+            CASE
+                WHEN attr_def.owning_lib IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '*[@code="' || attr_def.owning_lib || '"]'
+                ELSE '*' || attr_def.owning_lib
+            END;
+
+        circ_lib :=
+            CASE
+                WHEN attr_def.circ_lib IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '*[@code="' || attr_def.circ_lib || '"]'
+                ELSE '*' || attr_def.circ_lib
+            END;
+
+        call_number :=
+            CASE
+                WHEN attr_def.call_number IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.call_number ) = 1 THEN '*[@code="' || attr_def.call_number || '"]'
+                ELSE '*' || attr_def.call_number
+            END;
+
+        copy_number :=
+            CASE
+                WHEN attr_def.copy_number IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.copy_number ) = 1 THEN '*[@code="' || attr_def.copy_number || '"]'
+                ELSE '*' || attr_def.copy_number
+            END;
+
+        status :=
+            CASE
+                WHEN attr_def.status IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.status ) = 1 THEN '*[@code="' || attr_def.status || '"]'
+                ELSE '*' || attr_def.status
+            END;
+
+        location :=
+            CASE
+                WHEN attr_def.location IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.location ) = 1 THEN '*[@code="' || attr_def.location || '"]'
+                ELSE '*' || attr_def.location
+            END;
+
+        circulate :=
+            CASE
+                WHEN attr_def.circulate IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.circulate ) = 1 THEN '*[@code="' || attr_def.circulate || '"]'
+                ELSE '*' || attr_def.circulate
+            END;
+
+        deposit :=
+            CASE
+                WHEN attr_def.deposit IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.deposit ) = 1 THEN '*[@code="' || attr_def.deposit || '"]'
+                ELSE '*' || attr_def.deposit
+            END;
+
+        deposit_amount :=
+            CASE
+                WHEN attr_def.deposit_amount IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '*[@code="' || attr_def.deposit_amount || '"]'
+                ELSE '*' || attr_def.deposit_amount
+            END;
+
+        ref :=
+            CASE
+                WHEN attr_def.ref IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.ref ) = 1 THEN '*[@code="' || attr_def.ref || '"]'
+                ELSE '*' || attr_def.ref
+            END;
+
+        holdable :=
+            CASE
+                WHEN attr_def.holdable IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.holdable ) = 1 THEN '*[@code="' || attr_def.holdable || '"]'
+                ELSE '*' || attr_def.holdable
+            END;
+
+        price :=
+            CASE
+                WHEN attr_def.price IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.price ) = 1 THEN '*[@code="' || attr_def.price || '"]'
+                ELSE '*' || attr_def.price
+            END;
+
+        barcode :=
+            CASE
+                WHEN attr_def.barcode IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.barcode ) = 1 THEN '*[@code="' || attr_def.barcode || '"]'
+                ELSE '*' || attr_def.barcode
+            END;
+
+        circ_modifier :=
+            CASE
+                WHEN attr_def.circ_modifier IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '*[@code="' || attr_def.circ_modifier || '"]'
+                ELSE '*' || attr_def.circ_modifier
+            END;
+
+        circ_as_type :=
+            CASE
+                WHEN attr_def.circ_as_type IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '*[@code="' || attr_def.circ_as_type || '"]'
+                ELSE '*' || attr_def.circ_as_type
+            END;
+
+        alert_message :=
+            CASE
+                WHEN attr_def.alert_message IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.alert_message ) = 1 THEN '*[@code="' || attr_def.alert_message || '"]'
+                ELSE '*' || attr_def.alert_message
+            END;
+
+        opac_visible :=
+            CASE
+                WHEN attr_def.opac_visible IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '*[@code="' || attr_def.opac_visible || '"]'
+                ELSE '*' || attr_def.opac_visible
+            END;
+
+        pub_note :=
+            CASE
+                WHEN attr_def.pub_note IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.pub_note ) = 1 THEN '*[@code="' || attr_def.pub_note || '"]'
+                ELSE '*' || attr_def.pub_note
+            END;
+        priv_note :=
+            CASE
+                WHEN attr_def.priv_note IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.priv_note ) = 1 THEN '*[@code="' || attr_def.priv_note || '"]'
+                ELSE '*' || attr_def.priv_note
+            END;
+
+        internal_id :=
+            CASE
+                WHEN attr_def.internal_id IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.internal_id ) = 1 THEN '*[@code="' || attr_def.internal_id || '"]'
+                ELSE '*' || attr_def.internal_id
+            END;
+
+        stat_cat_data :=
+            CASE
+                WHEN attr_def.stat_cat_data IS NULL THEN 'null()'
+                WHEN LENGTH( attr_def.stat_cat_data ) = 1 THEN '*[@code="' || attr_def.stat_cat_data || '"]'
+                ELSE '*' || attr_def.stat_cat_data
+            END;
+
+
+
+        xpaths := ARRAY[owning_lib, circ_lib, call_number, copy_number, status, location, circulate,
+                        deposit, deposit_amount, ref, holdable, price, barcode, circ_modifier, circ_as_type,
+                        alert_message, pub_note, priv_note, internal_id, stat_cat_data, opac_visible];
+
+        FOR tmp_attr_set IN
+                SELECT  *
+                  FROM  oils_xpath_tag_to_table( (SELECT marc FROM vandelay.queued_bib_record WHERE id = import_id), attr_def.tag, xpaths)
+                            AS t( ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
+                                  dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
+                                  circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, internal_id TEXT,
+                                  stat_cat_data TEXT, opac_vis TEXT )
+        LOOP
+
+            attr_set.import_error := NULL;
+            attr_set.error_detail := NULL;
+            attr_set.deposit_amount := NULL;
+            attr_set.copy_number := NULL;
+            attr_set.price := NULL;
+            attr_set.circ_modifier := NULL;
+            attr_set.location := NULL;
+            attr_set.barcode := NULL;
+            attr_set.call_number := NULL;
+
+            IF tmp_attr_set.pr != '' THEN
+                tmp_str = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
+                IF tmp_str = '' THEN
+                    attr_set.import_error := 'import.item.invalid.price';
+                    attr_set.error_detail := tmp_attr_set.pr; -- original value
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+                attr_set.price := tmp_str::NUMERIC(8,2);
+            END IF;
+
+            IF tmp_attr_set.dep_amount != '' THEN
+                tmp_str = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
+                IF tmp_str = '' THEN
+                    attr_set.import_error := 'import.item.invalid.deposit_amount';
+                    attr_set.error_detail := tmp_attr_set.dep_amount;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+                attr_set.deposit_amount := tmp_str::NUMERIC(8,2);
+            END IF;
+
+            IF tmp_attr_set.cnum != '' THEN
+                tmp_str = REGEXP_REPLACE(tmp_attr_set.cnum, E'[^0-9]', '', 'g');
+                IF tmp_str = '' THEN
+                    attr_set.import_error := 'import.item.invalid.copy_number';
+                    attr_set.error_detail := tmp_attr_set.cnum;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+                attr_set.copy_number := tmp_str::INT;
+            END IF;
+
+            IF tmp_attr_set.ol != '' THEN
+                SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
+                IF NOT FOUND THEN
+                    attr_set.import_error := 'import.item.invalid.owning_lib';
+                    attr_set.error_detail := tmp_attr_set.ol;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+            END IF;
+
+            IF tmp_attr_set.clib != '' THEN
+                SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
+                IF NOT FOUND THEN
+                    attr_set.import_error := 'import.item.invalid.circ_lib';
+                    attr_set.error_detail := tmp_attr_set.clib;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+            END IF;
+
+            IF tmp_attr_set.cs != '' THEN
+                SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
+                IF NOT FOUND THEN
+                    attr_set.import_error := 'import.item.invalid.status';
+                    attr_set.error_detail := tmp_attr_set.cs;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+            END IF;
+
+            IF COALESCE(tmp_attr_set.circ_mod, '') = '' THEN
+
+                -- no circ mod defined, see if we should apply a default
+                SELECT INTO attr_set.circ_modifier TRIM(BOTH '"' FROM value)
+                    FROM actor.org_unit_ancestor_setting(
+                        'vandelay.item.circ_modifier.default',
+                        attr_set.owning_lib
+                    );
+
+                -- make sure the value from the org setting is still valid
+                PERFORM 1 FROM config.circ_modifier WHERE code = attr_set.circ_modifier;
+                IF NOT FOUND THEN
+                    attr_set.import_error := 'import.item.invalid.circ_modifier';
+                    attr_set.error_detail := tmp_attr_set.circ_mod;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+
+            ELSE
+
+                SELECT code INTO attr_set.circ_modifier FROM config.circ_modifier WHERE code = tmp_attr_set.circ_mod;
+                IF NOT FOUND THEN
+                    attr_set.import_error := 'import.item.invalid.circ_modifier';
+                    attr_set.error_detail := tmp_attr_set.circ_mod;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+            END IF;
+
+            IF tmp_attr_set.circ_as != '' THEN
+                SELECT code INTO attr_set.circ_as_type FROM config.coded_value_map WHERE ctype = 'item_type' AND code = tmp_attr_set.circ_as;
+                IF NOT FOUND THEN
+                    attr_set.import_error := 'import.item.invalid.circ_as_type';
+                    attr_set.error_detail := tmp_attr_set.circ_as;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+            END IF;
+
+            IF COALESCE(tmp_attr_set.cl, '') = '' THEN
+                -- no location specified, see if we should apply a default
+
+                SELECT INTO attr_set.location TRIM(BOTH '"' FROM value)
+                    FROM actor.org_unit_ancestor_setting(
+                        'vandelay.item.copy_location.default',
+                        attr_set.owning_lib
+                    );
+
+                -- make sure the value from the org setting is still valid
+                PERFORM 1 FROM asset.copy_location WHERE id = attr_set.location;
+                IF NOT FOUND THEN
+                    attr_set.import_error := 'import.item.invalid.location';
+                    attr_set.error_detail := tmp_attr_set.cs;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+            ELSE
+
+                -- search up the org unit tree for a matching copy location
+                WITH RECURSIVE anscestor_depth AS (
+                    SELECT  ou.id,
+                        out.depth AS depth,
+                        ou.parent_ou
+                    FROM  actor.org_unit ou
+                        JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
+                    WHERE ou.id = COALESCE(attr_set.owning_lib, attr_set.circ_lib)
+                        UNION ALL
+                    SELECT  ou.id,
+                        out.depth,
+                        ou.parent_ou
+                    FROM  actor.org_unit ou
+                        JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
+                        JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
+                ) SELECT  cpl.id INTO attr_set.location
+                    FROM  anscestor_depth a
+                        JOIN asset.copy_location cpl ON (cpl.owning_lib = a.id)
+                    WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl)
+                    ORDER BY a.depth DESC
+                    LIMIT 1;
+
+                IF NOT FOUND THEN
+                    attr_set.import_error := 'import.item.invalid.location';
+                    attr_set.error_detail := tmp_attr_set.cs;
+                    RETURN NEXT attr_set; CONTINUE;
+                END IF;
+            END IF;
+
+            attr_set.circulate      :=
+                LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
+                OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
+
+            attr_set.deposit        :=
+                LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
+                OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
+
+            attr_set.holdable       :=
+                LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
+                OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
+
+            attr_set.opac_visible   :=
+                LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
+                OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
+
+            attr_set.ref            :=
+                LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
+                OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
+
+            attr_set.call_number    := tmp_attr_set.cn; -- TEXT
+            attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
+            attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
+            attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
+            attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
+            attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
+            attr_set.internal_id    := tmp_attr_set.internal_id::BIGINT;
+            attr_set.stat_cat_data  := tmp_attr_set.stat_cat_data; -- TEXT,
+
+            RETURN NEXT attr_set;
+
+        END LOOP;
+
+    END IF;
+
+    RETURN;
+
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
+DECLARE
+    attr_def    BIGINT;
+    item_data   vandelay.import_item%ROWTYPE;
+BEGIN
+
+    IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
+        RETURN NEW;
+    END IF;
+
+    SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
+
+    FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
+        INSERT INTO vandelay.import_item (
+            record,
+            definition,
+            owning_lib,
+            circ_lib,
+            call_number,
+            copy_number,
+            status,
+            location,
+            circulate,
+            deposit,
+            deposit_amount,
+            ref,
+            holdable,
+            price,
+            barcode,
+            circ_modifier,
+            circ_as_type,
+            alert_message,
+            pub_note,
+            priv_note,
+            internal_id,
+            opac_visible,
+            stat_cat_data,
+            import_error,
+            error_detail
+        ) VALUES (
+            NEW.id,
+            item_data.definition,
+            item_data.owning_lib,
+            item_data.circ_lib,
+            item_data.call_number,
+            item_data.copy_number,
+            item_data.status,
+            item_data.location,
+            item_data.circulate,
+            item_data.deposit,
+            item_data.deposit_amount,
+            item_data.ref,
+            item_data.holdable,
+            item_data.price,
+            item_data.barcode,
+            item_data.circ_modifier,
+            item_data.circ_as_type,
+            item_data.alert_message,
+            item_data.pub_note,
+            item_data.priv_note,
+            item_data.internal_id,
+            item_data.opac_visible,
+            item_data.stat_cat_data,
+            item_data.import_error,
+            item_data.error_detail
+        );
+    END LOOP;
+
+    RETURN NULL;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+COMMIT;

commit b1c8c14e48abaa729ae164fd969ffe5c304a857d
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Wed Oct 8 13:35:43 2014 -0400

    LP#1379815 Add missing behavior to update_copy_stat_entries
    
    The current function doesn't provide the level of control we need, even
    with the 'delete_stats' flag set to zero. This commit adds a new
    'add_or_update_only' option which preserves existing stat cat entry maps
    for any stat cat not represented on the incoming copy object, and
    updates values when appropriate.
    
    This commit also updates the function comment for better clarity.
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
index 2dcc62d..4d78cb5 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
@@ -80,11 +80,17 @@ sub create_copy {
 }
 
 
-# if 'delete_stats' is true, the copy->stat_cat_entries data is 
-# treated as the authoritative list for the copy. existing entries
-# that are not in said list will be deleted from the DB
+# 'delete_stats' is somewhat of a misnomer.  With no flags set, this method
+# still deletes any existing maps not represented in $copy->stat_cat_entries,
+# but aborts when $copy->stat_cat_entries is empty or undefined.  If
+# 'delete_stats' is true, this method will delete all the maps when
+# $copy->stat_cat_entries is empty or undefined.
+#
+# The 'add_or_update_only' flag is more straightforward.  It adds missing
+# maps, updates present maps with any new values, and leaves the rest
+# alone.
 sub update_copy_stat_entries {
-    my($class, $editor, $copy, $delete_stats) = @_;
+    my($class, $editor, $copy, $delete_stats, $add_or_update_only) = @_;
 
     return undef if $copy->isdeleted;
     return undef unless $copy->ischanged or $copy->isnew;
@@ -104,13 +110,24 @@ sub update_copy_stat_entries {
         # if there is no stat cat entry on the copy who's id matches the
         # current map's id, remove the map from the database
         for my $map (@$maps) {
-            if(! grep { $_->id == $map->stat_cat_entry } @$entries ) {
+            if (!$add_or_update_only) {
+                if(! grep { $_->id == $map->stat_cat_entry } @$entries ) {
 
-                $logger->info("copy update found stale ".
-                    "stat cat entry map ".$map->id. " on copy ".$copy->id);
+                    $logger->info("copy update found stale ".
+                        "stat cat entry map ".$map->id. " on copy ".$copy->id);
 
-                $editor->delete_asset_stat_cat_entry_copy_map($map)
-                    or return $editor->event;
+                    $editor->delete_asset_stat_cat_entry_copy_map($map)
+                        or return $editor->event;
+                }
+            } else {
+                if( grep { $_->stat_cat == $map->stat_cat and $_->id != $map->stat_cat_entry } @$entries ) {
+
+                    $logger->info("copy update found ".
+                        "stat cat entry map ".$map->id. " needing update on copy ".$copy->id);
+
+                    $editor->delete_asset_stat_cat_entry_copy_map($map)
+                        or return $editor->event;
+                }
             }
         }
     }

commit 55f4479eb3adeb2f67ab88b566d8fc7453bc3ed3
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Mon Oct 6 11:52:27 2014 -0400

    LP#1379815 Fetch tag data as a table using tag/xpath combo
    
    New function: oils_xpath_tag_to_table()
    
    This function is adapted from oils_xpath_table() with the goal of being
    more targeted and simpler to use.
    
    The main issue with oils_xpath_table() is that it relies on peer
    UNNEST() functions, and that leads to unexpected behavior whenever the
    xpath arguments result in uneven or "gapped" selections.  In the first
    type of case, the resulting table includes rows representing the least
    common multiple of the underlying xpath selections.  In the second
    type, though the xpaths may sometimes return the same number of values,
    those values are not correlated except by order in the marc, which does
    not account for the real possibility of null values in the set.
    
    Crude Example:
    
    999 $d ABC $e 123
    999 $d DEF
    999 $d GHI
    999 $e 456
    
    We need a table representing subfields 'd' and 'e' of the '999' fields,
    so we might try an xpath like:
    
    //*[@tag="999"]/*[@code="d"]|//*[@tag="999"]/*[@code="e"]
    
    We want:
    
     d  |  e
    ---------
    ABC | 123
    DEF |
    GHI |
        | 456
    
    but we get:
    
     d  |  e
    ---------
    ABC | 123
    DEF | 456
    GHI | 123
    ABC | 456
    DEF | 123
    GHI | 456
    
    This example illustrates both negative behaviors (non-correlated fields
    and least-common-multiple row multiplication).
    
    The new method, while internally quite similar, has a different
    signature, with the most significant change being a 'tag' argument
    which serves as a common base element for the xpaths (now an array
    rather than a pipe-delimited string).
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/sql/Pg/002.functions.config.sql b/Open-ILS/src/sql/Pg/002.functions.config.sql
index 077e9e8..527e89b 100644
--- a/Open-ILS/src/sql/Pg/002.functions.config.sql
+++ b/Open-ILS/src/sql/Pg/002.functions.config.sql
@@ -185,6 +185,48 @@ SELECT * FROM (
 END;
 $func$ LANGUAGE PLPGSQL IMMUTABLE;
 
+CREATE OR REPLACE FUNCTION oils_xpath_tag_to_table(marc text, tag text, xpaths text[]) RETURNS SETOF record AS $function$
+
+-- This function currently populates columns with the FIRST matching value
+-- of each XPATH.  It would be reasonable to add a 'return_arrays' option
+-- where each column is an array of all matching values for each path, but
+-- that remains as a TODO
+
+DECLARE
+    field RECORD;
+    output RECORD;
+    select_list TEXT[];
+    from_list TEXT[];
+    q TEXT;
+BEGIN
+    -- setup query select
+    FOR i IN 1 .. ARRAY_UPPER(xpaths,1) LOOP
+        IF xpaths[i] = 'null()' THEN
+            select_list := ARRAY_APPEND(select_list, 'NULL::TEXT AS c_' || i );
+        ELSE
+            select_list := ARRAY_APPEND(select_list, '(oils_xpath(' ||
+                quote_literal(
+                    CASE
+                        WHEN xpaths[i] ~ $re$/[^/[]*@[^/]+$$re$ -- attribute
+                            OR xpaths[i] ~ $re$text\(\)$$re$
+                        THEN xpaths[i]
+                        ELSE xpaths[i] || '//text()'
+                    END
+                ) || ', field_marc))[1] AS cl_' || i);
+                -- hardcoded to first value for each path
+        END IF;
+    END LOOP;
+
+    -- run query over tag set
+    q := 'SELECT ' || ARRAY_TO_STRING(select_list, ',')
+        || ' FROM UNNEST(oils_xpath(' || quote_literal('//*[@tag="' || tag
+        || '"]') || ', ' || quote_literal(marc) || ')) AS field_marc;';
+    --RAISE NOTICE '%', q;
+
+    RETURN QUERY EXECUTE q;
+END;
+
+$function$ LANGUAGE PLPGSQL;
 
 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
 DECLARE
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.function.vandelay-oils_xpath_tag_to_table.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.function.vandelay-oils_xpath_tag_to_table.sql
new file mode 100644
index 0000000..48e44f8
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.function.vandelay-oils_xpath_tag_to_table.sql
@@ -0,0 +1,48 @@
+BEGIN;
+
+--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+CREATE OR REPLACE FUNCTION oils_xpath_tag_to_table(marc text, tag text, xpaths text[]) RETURNS SETOF record AS $function$
+
+-- This function currently populates columns with the FIRST matching value
+-- of each XPATH.  It would be reasonable to add a 'return_arrays' option
+-- where each column is an array of all matching values for each path, but
+-- that remains as a TODO
+
+DECLARE
+    field RECORD;
+    output RECORD;
+    select_list TEXT[];
+    from_list TEXT[];
+    q TEXT;
+BEGIN
+    -- setup query select
+    FOR i IN 1 .. ARRAY_UPPER(xpaths,1) LOOP
+        IF xpaths[i] = 'null()' THEN
+            select_list := ARRAY_APPEND(select_list, 'NULL::TEXT AS c_' || i );
+        ELSE
+            select_list := ARRAY_APPEND(select_list, '(oils_xpath(' ||
+                quote_literal(
+                    CASE
+                        WHEN xpaths[i] ~ $re$/[^/[]*@[^/]+$$re$ -- attribute
+                            OR xpaths[i] ~ $re$text\(\)$$re$
+                        THEN xpaths[i]
+                        ELSE xpaths[i] || '//text()'
+                    END
+                ) || ', field_marc))[1] AS cl_' || i);
+                -- hardcoded to first value for each path
+        END IF;
+    END LOOP;
+
+    -- run query over tag set
+    q := 'SELECT ' || ARRAY_TO_STRING(select_list, ',')
+        || ' FROM UNNEST(oils_xpath(' || quote_literal('//*[@tag="' || tag
+        || '"]') || ', ' || quote_literal(marc) || ')) AS field_marc;';
+    --RAISE NOTICE '%', q;
+
+    RETURN QUERY EXECUTE q;
+END;
+
+$function$ LANGUAGE PLPGSQL;
+
+COMMIT;

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

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |    2 +
 .../lib/OpenILS/Application/Cat/AssetCommon.pm     |   35 ++-
 .../perlmods/lib/OpenILS/Application/Vandelay.pm   |   39 +++
 Open-ILS/src/sql/Pg/002.functions.config.sql       |   42 +++
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/012.schema.vandelay.sql        |    2 +
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |    4 +
 Open-ILS/src/sql/Pg/999.functions.global.sql       |  124 ++++----
 .../regress/lp1379815_vl_import_item_stat_cats.pg  |   52 +++
 ...8.function.vandelay-oils_xpath_tag_to_table.sql |   48 +++
 ...ql => 0929.schema.vandelay-stat-cat-import.sql} |  359 +++++++++++---------
 .../Cataloging/importing_stat_cats.txt             |   19 +
 12 files changed, 488 insertions(+), 240 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/t/regress/lp1379815_vl_import_item_stat_cats.pg
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0928.function.vandelay-oils_xpath_tag_to_table.sql
 copy Open-ILS/src/sql/Pg/upgrade/{0731.schema.vandelay_item_overlay.sql => 0929.schema.vandelay-stat-cat-import.sql} (61%)
 create mode 100644 docs/RELEASE_NOTES_NEXT/Cataloging/importing_stat_cats.txt


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list