LP#121054: Add Deleted Flag to asset.copy_location
authorJason Boyer <JBoyer1@library.in.gov>
Thu, 25 Sep 2014 14:53:11 +0000 (10:53 -0400)
committerBen Shum <bshum@biblio.org>
Sat, 21 Feb 2015 07:24:06 +0000 (02:24 -0500)
Signed-off-by: Jason Boyer <JBoyer@library.in.gov>
Signed-off-by: Michele Morgan <mmorgan@noblenet.org>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Signed-off-by: Ben Shum <bshum@biblio.org>

19 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/extras/ils_events.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CopyLocations.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/asset.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/biblio.pm
Open-ILS/src/sql/Pg/040.schema.asset.sql
Open-ILS/src/sql/Pg/800.fkeys.sql
Open-ILS/src/sql/Pg/999.functions.global.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.asset.copy_location-delete-rule.sql [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
Open-ILS/web/js/ui/default/conify/global/asset/copy_location_group.js
Open-ILS/web/js/ui/default/conify/global/asset/copy_location_order.js
Open-ILS/web/js/ui/default/conify/global/config/circ_limit_set.js
Open-ILS/web/js/ui/default/opac/copyloc.js

index c7f05e4..18ccb4b 100644 (file)
@@ -4494,6 +4494,7 @@ SELECT  usr,
                        <field reporter:label="Label Prefix" name="label_prefix"  reporter:datatype="text" oils_persist:i18n="true"/>
                        <field reporter:label="Label Suffix" name="label_suffix"  reporter:datatype="text" oils_persist:i18n="true"/>
                        <field reporter:label="Checkin Alert" name="checkin_alert" reporter:datatype="bool" />
+                       <field reporter:label="Is Deleted?" name="deleted" reporter:datatype="bool" />
                </fields>
                <links>
                        <link field="owning_lib" reltype="has_a" key="id" map="" class="aou"/>
index 80892ad..63385f3 100644 (file)
        <event code='1701' textcode='COPY_LOCATION_EXISTS'>
                <desc xml:lang="en-US"> The copy location object already exists </desc>
        </event>
+       <event code='1716' textcode='COPY_LOCATION_NOT_EMPTY'>
+               <desc xml:lang="en-US"> The copy location is not empty </desc>
+       </event>
+       <event code='1717' textcode='COPY_LOCATION_NOT_FOUND'>
+               <desc xml:lang="en-US"> The copy location does not exist </desc>
+       </event>
        <event code='1702' textcode='OPEN_CIRCULATION_EXISTS'>
                <desc xml:lang="en-US"> There is an open circulation on the requested item </desc>
        </event>
index 69ecdf6..6883a0d 100644 (file)
@@ -1700,7 +1700,7 @@ sub extract_lineitem_detail_data {
             my $org = $cp_base_org;
             while ($org) {
                 $loc = $mgr->editor->search_asset_copy_location(
-                    {owning_lib => $org, name => $name}, {idlist => 1})->[0];
+                    {owning_lib => $org, name => $name, deleted => 'f'}, {idlist => 1})->[0];
                 last if $loc;
                 $org = $mgr->editor->retrieve_actor_org_unit($org)->parent_ou;
             }
index c8ae3c2..09c30ee 100644 (file)
@@ -640,7 +640,7 @@ sub fetch_copy_locations {
     my $self = shift; 
     return $self->simplereq(
         'open-ils.cstore', 
-        'open-ils.cstore.direct.asset.copy_location.search.atomic', { id => { '!=' => undef } });
+        'open-ils.cstore.direct.asset.copy_location.search.atomic', { id => { '!=' => undef }, deleted => 'f' });
 }
 
 sub fetch_copy_location_by_name {
@@ -648,7 +648,7 @@ sub fetch_copy_location_by_name {
     my $evt;
     my $cl = $self->cstorereq(
         'open-ils.cstore.direct.asset.copy_location.search',
-            { name => $name, owning_lib => $org } );
+            { name => $name, owning_lib => $org, deleted => 'f' } );
     $evt = OpenILS::Event->new('ASSET_COPY_LOCATION_NOT_FOUND') unless $cl;
     return ($cl, $evt);
 }
index 869894f..07357ea 100644 (file)
@@ -55,6 +55,11 @@ sub create_copy {
     
     return OpenILS::Event->new('ITEM_BARCODE_EXISTS') if @$existing;
 
+    my $copy_loc = $editor->search_asset_copy_location(
+        { id => $copy->location, deleted => 'f' } );
+        
+    return OpenILS::Event->new('COPY_LOCATION_NOT_FOUND') unless @$copy_loc;
+    
    # see if the volume this copy references is marked as deleted
     return OpenILS::Event->new('VOLUME_DELETED', vol => $vol->id) 
         if $U->is_true($vol->deleted);
@@ -198,6 +203,12 @@ sub update_copy {
     $override = { all => 1 } if($override && !ref $override);
     $override = { all => 0 } if(!ref $override);
 
+    # Duplicated check from create_copy in case a copy template with a deleted location is applied later
+    my $copy_loc = $editor->search_asset_copy_location(
+        { id => $copy->location, deleted => 'f' } );
+        
+    return OpenILS::Event->new('COPY_LOCATION_NOT_FOUND') unless @$copy_loc;
+    
     my $evt;
     my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
     return $evt if ( $evt = $class->org_cannot_have_vols($editor, $org) );
index a72e13c..23061ca 100644 (file)
@@ -40,7 +40,8 @@ sub cl_retrieve_all {
     }
 
     return new_editor()->search_asset_copy_location([{
-        owning_lib => $U->get_org_full_path($org_id)
+        owning_lib => $U->get_org_full_path($org_id),
+       deleted => "f"
     }, $second_cstore_arg]);
 }
 
@@ -60,7 +61,8 @@ sub cl_retrieve_distinct {
         "select" => {
             "acpl" => [{"transform" => "distinct", "column" => "name"}]
         },
-        "from" => {"acpl" => {}}
+        "from" => {"acpl" => {}},
+       "where" => {"deleted" => "f"}
     }) or return $e->die_event;
     $e->disconnect;
 
@@ -91,7 +93,7 @@ sub cl_create {
 
     # make sure there is no copy_location with the same name in the same place
     my $existing = $e->search_asset_copy_location(
-        {owning_lib => $location->owning_lib, name => $location->name}, {idlist=>1});
+        {owning_lib => $location->owning_lib, name => $location->name, deleted => "f"}, {idlist=>1});
     return OpenILS::Event->new('COPY_LOCATION_EXISTS') if @$existing;
 
     $e->create_asset_copy_location($location) or return $e->die_event;
@@ -120,6 +122,9 @@ sub cl_delete {
     my $e = new_editor(authtoken=>$auth, xact=>1);
     return $e->die_event unless $e->checkauth;
 
+    my $cps = $e->search_asset_copy({location=>$id, deleted=>'f'});
+    return OpenILS::Event->new('COPY_LOCATION_NOT_EMPTY', payload=>$id) if @$cps;
+
     my $cloc = $e->retrieve_asset_copy_location($id) 
         or return $e->die_event;
     return $e->die_event unless 
index 22c0966..5c84493 100644 (file)
@@ -2719,7 +2719,7 @@ sub rec_to_mr_rec_descriptors {
                 deleted  => 'f'
             },
             "+ccs" => { holdable => 't' },
-            "+acpl" => { holdable => 't' }
+            "+acpl" => { holdable => 't', deleted => 'f' }
         }
     };
 
index 30109db..b9f6354 100644 (file)
@@ -10,7 +10,7 @@ use base qw/asset/;
 
 __PACKAGE__->table( 'asset_copy_location' );
 __PACKAGE__->columns( Primary => qw/id/ );
-__PACKAGE__->columns( Essential => qw/name owning_lib holdable hold_verify opac_visible circulate label_prefix label_suffix checkin_alert/ );
+__PACKAGE__->columns( Essential => qw/name owning_lib holdable hold_verify opac_visible circulate label_prefix label_suffix checkin_alert deleted/ );
 
 #-------------------------------------------------------------------------------
 package asset::copy_location_order;
index 435f104..f5e3279 100644 (file)
@@ -409,6 +409,7 @@ sub asset_copy_location_all {
     my $client = shift;
 
     for my $rec ( asset::copy_location->retrieve_all ) {
+        next if $rec->deleted eq "t";
         $client->respond( $rec->to_fieldmapper );
     }
 
@@ -439,6 +440,7 @@ sub ranged_asset_copy_location {
                   FROM  $ctable c
                         JOIN $descendants d
                                 ON (d.id = c.owning_lib)
+                  WHERE deleted IS FALSE
                  ORDER BY name
     SQL
         
index 8d8648d..c285489 100644 (file)
@@ -48,7 +48,8 @@ sub record_copy_count {
                   WHERE cn.record = ?
                     $visible
                     AND cn.deleted IS FALSE
-                    AND cp.deleted IS FALSE)
+                    AND cp.deleted IS FALSE
+                    AND loc.deleted IS FALSE)
             ) AS count,
             sum(
                 (SELECT count(cp.id)
@@ -61,6 +62,7 @@ sub record_copy_count {
                     $visible
                     AND cn.deleted IS FALSE
                     AND cp.deleted IS FALSE
+                    AND loc.deleted IS FALSE
                     AND cp.status IN (0,7,12))
             ) AS available,
             sum(
@@ -74,7 +76,8 @@ sub record_copy_count {
                     AND loc.opac_visible = TRUE
                     AND cp.opac_visible = TRUE
                     AND cn.deleted IS FALSE
-                    AND cp.deleted IS FALSE)
+                    AND cp.deleted IS FALSE
+                    AND loc.deleted IS FALSE)
             ) AS unshadow,
                         sum(    
                                 (SELECT sum(1)
@@ -411,6 +414,7 @@ sub record_copy_status_count {
             AND cl.opac_visible IS TRUE
             AND cp.opac_visible IS TRUE
             AND cp.deleted IS FALSE
+            AND cl.deleted IS FALSE
             AND cs.opac_visible IS TRUE
           GROUP BY 1,2,3,4,5;
     SQL
@@ -490,6 +494,7 @@ sub record_copy_status_location_count {
             AND cl.opac_visible IS TRUE
             AND cp.opac_visible IS TRUE
             AND cp.deleted IS FALSE
+            AND cl.deleted IS FALSE
             AND cs.opac_visible IS TRUE
           GROUP BY 1,2,3,4,5,6;
     SQL
index e7b1daa..4375013 100644 (file)
@@ -32,8 +32,9 @@ CREATE TABLE asset.copy_location (
        label_prefix    TEXT,
        label_suffix    TEXT,
        checkin_alert   BOOL    NOT NULL DEFAULT FALSE,
-       CONSTRAINT acl_name_once_per_lib UNIQUE (name, owning_lib)
+       deleted         BOOL    NOT NULL DEFAULT FALSE
 );
+CREATE UNIQUE INDEX acl_name_once_per_lib ON asset.copy_location (name, owning_lib) WHERE deleted = FALSE OR deleted IS FALSE;
 
 CREATE TABLE asset.copy_location_order
 (
@@ -67,7 +68,6 @@ CREATE TABLE asset.copy_location_group_map (
     CONSTRAINT  lgroup_once_per_group UNIQUE (lgroup,location)
 );
 
-
 CREATE TABLE asset.copy (
        id              BIGSERIAL                       PRIMARY KEY,
        circ_lib        INT                             NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
@@ -179,9 +179,9 @@ BEGIN
             RETURN NEW;
         END IF;
     END IF;
-    SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance((SELECT owning_lib FROM asset.call_number WHERE id = NEW.call_number)) aouad ON acpl.owning_lib = aouad.id WHERE name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
+    SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance((SELECT owning_lib FROM asset.call_number WHERE id = NEW.call_number)) aouad ON acpl.owning_lib = aouad.id WHERE deleted IS FALSE AND name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
     IF new_copy_location IS NULL THEN
-        SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance(NEW.circ_lib) aouad ON acpl.owning_lib = aouad.id WHERE name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
+        SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance(NEW.circ_lib) aouad ON acpl.owning_lib = aouad.id WHERE deleted IS FALSE AND name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
     END IF;
     IF new_copy_location IS NOT NULL THEN
         NEW.location = new_copy_location;
@@ -615,7 +615,7 @@ BEGIN
           FROM
                 actor.org_unit_descendants(ans.id) d
                 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
-                JOIN asset.copy_location cl ON (cp.location = cl.id)
+                JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
                 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
           GROUP BY 1,2,6;
 
@@ -647,7 +647,7 @@ BEGIN
           FROM
                 actor.org_unit_descendants(ans.id) d
                 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
-                JOIN asset.copy_location cl ON (cp.location = cl.id)
+                JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
                 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
           GROUP BY 1,2,6;
 
@@ -695,6 +695,7 @@ BEGIN
             AND acpl.holdable = true
             AND ccs.holdable = true
             AND acp.deleted = false
+            AND acpl.deleted = false
             AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
         LIMIT 1;
     IF FOUND THEN
@@ -867,6 +868,7 @@ BEGIN
             AND acpl.holdable = true
             AND ccs.holdable = true
             AND acp.deleted = false
+            AND acpl.deleted = false
             AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
         LIMIT 1;
     IF FOUND THEN
index ee9886d..009b915 100644 (file)
@@ -26,6 +26,15 @@ CREATE RULE protect_bib_rec_delete AS
             WHERE OLD.id = biblio.record_entry.id
     );
 
+CREATE RULE protect_copy_location_delete AS
+    ON DELETE TO asset.copy_location DO INSTEAD (
+        UPDATE asset.copy_location SET deleted = TRUE WHERE OLD.id = asset.copy_location.id;
+        UPDATE acq.lineitem_detail SET location = NULL WHERE location = OLD.id;
+        DELETE FROM asset.copy_location_order WHERE location = OLD.id;
+        DELETE FROM asset.copy_location_group_map WHERE location = OLD.id;
+        DELETE FROM config.circ_limit_set_copy_loc_map WHERE copy_loc = OLD.id;
+    );
+    
 ALTER TABLE actor.usr ADD CONSTRAINT actor_usr_mailing_address_fkey FOREIGN KEY (mailing_address) REFERENCES actor.usr_address (id) DEFERRABLE INITIALLY DEFERRED;
 ALTER TABLE actor.usr ADD CONSTRAINT actor_usr_billing_address_fkey FOREIGN KEY (billing_address) REFERENCES actor.usr_address (id) DEFERRABLE INITIALLY DEFERRED;
 ALTER TABLE actor.usr ADD CONSTRAINT actor_usr_home_ou_fkey FOREIGN KEY (home_ou) REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
index d724afc..bb87a6b 100644 (file)
@@ -1229,6 +1229,7 @@ CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS
         JOIN config.copy_status cs ON (cp.status = cs.id)
         JOIN biblio.record_entry b ON (cn.record = b.id)
     WHERE NOT cp.deleted
+        AND NOT cl.deleted
         AND NOT cn.deleted
         AND NOT b.deleted
         AND cs.opac_visible
@@ -1243,6 +1244,7 @@ CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS
         JOIN asset.copy_location cl ON (cp.location = cl.id)
         JOIN config.copy_status cs ON (cp.status = cs.id)
     WHERE NOT cp.deleted
+        AND NOT cl.deleted
         AND cs.opac_visible
         AND cl.opac_visible
         AND cp.opac_visible
@@ -1272,6 +1274,7 @@ BEGIN
                 JOIN config.copy_status cs ON (cp.status = cs.id)
                 JOIN biblio.record_entry b ON (cn.record = b.id)
           WHERE NOT cp.deleted
+                AND NOT cl.deleted
                 AND NOT cn.deleted
                 AND NOT b.deleted
                 AND cs.opac_visible
@@ -1287,6 +1290,7 @@ BEGIN
                 JOIN asset.copy_location cl ON (cp.location = cl.id)
                 JOIN config.copy_status cs ON (cp.status = cs.id)
           WHERE NOT cp.deleted
+                AND NOT cl.deleted
                 AND cs.opac_visible
                 AND cl.opac_visible
                 AND cp.opac_visible
@@ -1372,7 +1376,7 @@ BEGIN
 
     END IF;
 
-    IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
+    IF TG_TABLE_NAME IN ('call_number', 'copy_location', 'record_entry') THEN -- these have a 'deleted' column
  
         IF OLD.deleted AND NEW.deleted THEN -- do nothing
 
@@ -1382,6 +1386,8 @@ BEGIN
  
             IF TG_TABLE_NAME = 'call_number' THEN
                 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
+            ELSIF TG_TABLE_NAME = 'copy_location' THEN
+                DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE location = NEW.id);
             ELSIF TG_TABLE_NAME = 'record_entry' THEN
                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
             END IF;
@@ -1393,6 +1399,9 @@ BEGIN
             IF TG_TABLE_NAME = 'call_number' THEN
                 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
                 EXECUTE add_front || add_base_query || add_back;
+            ELSIF TG_TABLE_NAME = 'copy_location' THEN
+                add_base_query := add_base_query || 'AND cl.id = ' || NEW.id;
+                EXECUTE add_front || add_base_query || add_back;
             ELSIF TG_TABLE_NAME = 'record_entry' THEN
                 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
                 add_peer_query := add_peer_query || ' AND pbcm.peer_record = ' || NEW.id;
@@ -2017,7 +2026,8 @@ BEGIN
                     );
 
                 -- make sure the value from the org setting is still valid
-                PERFORM 1 FROM asset.copy_location WHERE id = attr_set.location;
+                PERFORM 1 FROM asset.copy_location 
+                    WHERE id = attr_set.location AND NOT deleted;
                 IF NOT FOUND THEN
                     attr_set.import_error := 'import.item.invalid.location';
                     attr_set.error_detail := tmp_attr_set.cs;
@@ -2043,7 +2053,8 @@ BEGIN
                 ) 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)
+                    WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl) 
+                        AND NOT cpl.deleted
                     ORDER BY a.depth DESC
                     LIMIT 1; 
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.asset.copy_location-delete-rule.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.asset.copy_location-delete-rule.sql
new file mode 100644 (file)
index 0000000..16cc585
--- /dev/null
@@ -0,0 +1,811 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+ALTER TABLE asset.copy_location ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE;
+
+CREATE OR REPLACE RULE protect_copy_location_delete AS
+    ON DELETE TO asset.copy_location DO INSTEAD (
+        UPDATE asset.copy_location SET deleted = TRUE WHERE OLD.id = asset.copy_location.id;
+        UPDATE acq.lineitem_detail SET location = NULL WHERE location = OLD.id;
+        DELETE FROM asset.copy_location_order WHERE location = OLD.id;
+        DELETE FROM asset.copy_location_group_map WHERE location = OLD.id;
+        DELETE FROM config.circ_limit_set_copy_loc_map WHERE copy_loc = OLD.id;
+    );
+
+ALTER TABLE asset.copy_location DROP CONSTRAINT acl_name_once_per_lib;
+CREATE UNIQUE INDEX acl_name_once_per_lib ON asset.copy_location (name, owning_lib) WHERE deleted = FALSE OR deleted IS FALSE;
+
+CREATE OR REPLACE FUNCTION asset.acp_location_fixer()
+RETURNS TRIGGER AS $$
+DECLARE
+    new_copy_location INT;
+BEGIN
+    IF (TG_OP = 'UPDATE') THEN
+        IF NEW.location = OLD.location AND NEW.call_number = OLD.call_number AND NEW.circ_lib = OLD.circ_lib THEN
+            RETURN NEW;
+        END IF;
+    END IF;
+    SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance((SELECT owning_lib FROM asset.call_number WHERE id = NEW.call_number)) aouad ON acpl.owning_lib = aouad.id WHERE deleted IS FALSE AND name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
+    IF new_copy_location IS NULL THEN
+        SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance(NEW.circ_lib) aouad ON acpl.owning_lib = aouad.id WHERE deleted IS FALSE AND name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
+    END IF;
+    IF new_copy_location IS NOT NULL THEN
+        NEW.location = new_copy_location;
+    END IF;
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION asset.staff_ou_record_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE         
+    ans RECORD; 
+    trans INT;
+BEGIN           
+    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+    FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
+        RETURN QUERY
+        SELECT  ans.depth,
+                ans.id,
+                COUNT( cp.id ),
+                SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+                SUM( CASE WHEN cl.opac_visible AND cp.opac_visible THEN 1 ELSE 0 END),
+                trans
+          FROM
+                actor.org_unit_descendants(ans.id) d
+                JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
+                JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
+                JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
+          GROUP BY 1,2,6;
+
+        IF NOT FOUND THEN
+            RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+        END IF;
+
+    END LOOP;
+
+    RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION asset.staff_lasso_record_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
+DECLARE
+    ans RECORD;
+    trans INT;
+BEGIN
+    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
+
+    FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
+        RETURN QUERY
+        SELECT  -1,
+                ans.id,
+                COUNT( cp.id ),
+                SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
+                SUM( CASE WHEN cl.opac_visible AND cp.opac_visible THEN 1 ELSE 0 END),
+                trans
+          FROM
+                actor.org_unit_descendants(ans.id) d
+                JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
+                JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
+                JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
+          GROUP BY 1,2,6;
+
+        IF NOT FOUND THEN
+            RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
+        END IF;
+
+    END LOOP;
+
+    RETURN;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION asset.record_has_holdable_copy ( rid BIGINT, ou INT DEFAULT NULL) RETURNS BOOL AS $f$
+BEGIN
+    PERFORM 1
+        FROM
+            asset.copy acp
+            JOIN asset.call_number acn ON acp.call_number = acn.id
+            JOIN asset.copy_location acpl ON acp.location = acpl.id
+            JOIN config.copy_status ccs ON acp.status = ccs.id
+        WHERE
+            acn.record = rid
+            AND acp.holdable = true
+            AND acpl.holdable = true
+            AND ccs.holdable = true
+            AND acp.deleted = false
+            AND acpl.deleted = false
+            AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
+        LIMIT 1;
+    IF FOUND THEN
+        RETURN true;
+    END IF;
+    RETURN FALSE;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION asset.metarecord_has_holdable_copy ( rid BIGINT, ou INT DEFAULT NULL) RETURNS BOOL AS $f$
+BEGIN
+    PERFORM 1
+        FROM
+            asset.copy acp
+            JOIN asset.call_number acn ON acp.call_number = acn.id
+            JOIN asset.copy_location acpl ON acp.location = acpl.id
+            JOIN config.copy_status ccs ON acp.status = ccs.id
+            JOIN metabib.metarecord_source_map mmsm ON acn.record = mmsm.source
+        WHERE
+            mmsm.metarecord = rid
+            AND acp.holdable = true
+            AND acpl.holdable = true
+            AND ccs.holdable = true
+            AND acp.deleted = false
+            AND acpl.deleted = false
+            AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
+        LIMIT 1;
+    IF FOUND THEN
+        RETURN true;
+    END IF;
+    RETURN FALSE;
+END;
+$f$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
+
+    TRUNCATE TABLE asset.opac_visible_copies;
+
+    INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
+    SELECT  cp.id, cp.circ_lib, cn.record
+    FROM  asset.copy cp
+        JOIN asset.call_number cn ON (cn.id = cp.call_number)
+        JOIN actor.org_unit a ON (cp.circ_lib = a.id)
+        JOIN asset.copy_location cl ON (cp.location = cl.id)
+        JOIN config.copy_status cs ON (cp.status = cs.id)
+        JOIN biblio.record_entry b ON (cn.record = b.id)
+    WHERE NOT cp.deleted
+        AND NOT cl.deleted
+        AND NOT cn.deleted
+        AND NOT b.deleted
+        AND cs.opac_visible
+        AND cl.opac_visible
+        AND cp.opac_visible
+        AND a.opac_visible
+            UNION
+    SELECT  cp.id, cp.circ_lib, pbcm.peer_record AS record
+    FROM  asset.copy cp
+        JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
+        JOIN actor.org_unit a ON (cp.circ_lib = a.id)
+        JOIN asset.copy_location cl ON (cp.location = cl.id)
+        JOIN config.copy_status cs ON (cp.status = cs.id)
+    WHERE NOT cp.deleted
+        AND NOT cl.deleted
+        AND cs.opac_visible
+        AND cl.opac_visible
+        AND cp.opac_visible
+        AND a.opac_visible;
+
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
+DECLARE
+    add_front       TEXT;
+    add_back        TEXT;
+    add_base_query  TEXT;
+    add_peer_query  TEXT;
+    remove_query    TEXT;
+    do_add          BOOLEAN := false;
+    do_remove       BOOLEAN := false;
+BEGIN
+    add_base_query := $$
+        SELECT  cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
+          FROM  asset.copy cp
+                JOIN asset.call_number cn ON (cn.id = cp.call_number)
+                JOIN actor.org_unit a ON (cp.circ_lib = a.id)
+                JOIN asset.copy_location cl ON (cp.location = cl.id)
+                JOIN config.copy_status cs ON (cp.status = cs.id)
+                JOIN biblio.record_entry b ON (cn.record = b.id)
+          WHERE NOT cp.deleted
+                AND NOT cl.deleted
+                AND NOT cn.deleted
+                AND NOT b.deleted
+                AND cs.opac_visible
+                AND cl.opac_visible
+                AND cp.opac_visible
+                AND a.opac_visible
+    $$;
+    add_peer_query := $$
+        SELECT  cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
+          FROM  asset.copy cp
+                JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
+                JOIN actor.org_unit a ON (cp.circ_lib = a.id)
+                JOIN asset.copy_location cl ON (cp.location = cl.id)
+                JOIN config.copy_status cs ON (cp.status = cs.id)
+          WHERE NOT cp.deleted
+                AND NOT cl.deleted
+                AND cs.opac_visible
+                AND cl.opac_visible
+                AND cp.opac_visible
+                AND a.opac_visible
+    $$;
+    add_front := $$
+        INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
+          SELECT DISTINCT ON (id, record) id, circ_lib, record FROM (
+    $$;
+    add_back := $$
+        ) AS x
+    $$;
+    remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
+
+    IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
+        IF TG_OP = 'INSERT' THEN
+            add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.peer_record = ' || NEW.peer_record;
+            EXECUTE add_front || add_peer_query || add_back;
+            RETURN NEW;
+        ELSE
+            remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
+            EXECUTE remove_query;
+            RETURN OLD;
+        END IF;
+    END IF;
+
+    IF TG_OP = 'INSERT' THEN
+
+        IF TG_TABLE_NAME IN ('copy', 'unit') THEN
+            add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
+            EXECUTE add_front || add_base_query || add_back;
+        END IF;
+
+        RETURN NEW;
+
+    END IF;
+
+    -- handle items first, since with circulation activity
+    -- their statuses change frequently
+    IF TG_TABLE_NAME IN ('copy', 'unit') THEN
+
+        IF OLD.location    <> NEW.location OR
+           OLD.call_number <> NEW.call_number OR
+           OLD.status      <> NEW.status OR
+           OLD.circ_lib    <> NEW.circ_lib THEN
+            -- any of these could change visibility, but
+            -- we'll save some queries and not try to calculate
+            -- the change directly
+            do_remove := true;
+            do_add := true;
+        ELSE
+
+            IF OLD.deleted <> NEW.deleted THEN
+                IF NEW.deleted THEN
+                    do_remove := true;
+                ELSE
+                    do_add := true;
+                END IF;
+            END IF;
+
+            IF OLD.opac_visible <> NEW.opac_visible THEN
+                IF OLD.opac_visible THEN
+                    do_remove := true;
+                ELSIF NOT do_remove THEN -- handle edge case where deleted item
+                                        -- is also marked opac_visible
+                    do_add := true;
+                END IF;
+            END IF;
+
+        END IF;
+
+        IF do_remove THEN
+            DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
+        END IF;
+        IF do_add THEN
+            add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
+            add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
+            EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
+        END IF;
+
+        RETURN NEW;
+
+    END IF;
+
+    IF TG_TABLE_NAME IN ('call_number', 'copy_location', 'record_entry') THEN -- these have a 'deleted' column
+        IF OLD.deleted AND NEW.deleted THEN -- do nothing
+
+            RETURN NEW;
+        ELSIF NEW.deleted THEN -- remove rows
+            IF TG_TABLE_NAME = 'call_number' THEN
+                DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
+            ELSIF TG_TABLE_NAME = 'copy_location' THEN
+                DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE location = NEW.id);
+            ELSIF TG_TABLE_NAME = 'record_entry' THEN
+                DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
+            END IF;
+            RETURN NEW;
+        ELSIF OLD.deleted THEN -- add rows
+            IF TG_TABLE_NAME = 'call_number' THEN
+                add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
+                EXECUTE add_front || add_base_query || add_back;
+            ELSIF TG_TABLE_NAME = 'copy_location' THEN
+                add_base_query := add_base_query || 'AND cl.id = ' || NEW.id;
+                EXECUTE add_front || add_base_query || add_back;
+            ELSIF TG_TABLE_NAME = 'record_entry' THEN
+                add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
+                add_peer_query := add_peer_query || ' AND pbcm.peer_record = ' || NEW.id;
+                EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
+            END IF;
+            RETURN NEW;
+        END IF;
+    END IF;
+
+    IF TG_TABLE_NAME = 'call_number' THEN
+
+        IF OLD.record <> NEW.record THEN
+            -- call number is linked to different bib
+            remove_query := remove_query || 'call_number = ' || NEW.id || ');';
+            EXECUTE remove_query;
+            add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
+            EXECUTE add_front || add_base_query || add_back;
+        END IF;
+
+        RETURN NEW;
+
+    END IF;
+
+    IF TG_TABLE_NAME IN ('record_entry') THEN
+        RETURN NEW; -- don't have 'opac_visible'
+    END IF;
+
+    -- actor.org_unit, asset.copy_location, asset.copy_status
+    IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
+
+        RETURN NEW;
+
+    ELSIF NEW.opac_visible THEN -- add rows
+
+        IF TG_TABLE_NAME = 'org_unit' THEN
+            add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id;
+            add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id;
+        ELSIF TG_TABLE_NAME = 'copy_location' THEN
+            add_base_query := add_base_query || ' AND cp.location = ' || NEW.id;
+            add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id;
+        ELSIF TG_TABLE_NAME = 'copy_status' THEN
+            add_base_query := add_base_query || ' AND cp.status = ' || NEW.id;
+            add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id;
+        END IF;
+        EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
+    ELSE -- delete rows
+
+        IF TG_TABLE_NAME = 'org_unit' THEN
+            remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
+        ELSIF TG_TABLE_NAME = 'copy_location' THEN
+            remove_query := remove_query || 'location = ' || NEW.id || ');';
+        ELSIF TG_TABLE_NAME = 'copy_status' THEN
+            remove_query := remove_query || 'status = ' || NEW.id || ');';
+        END IF;
+        EXECUTE remove_query;
+    END IF;
+    RETURN NEW;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+-- updated copy location validity test to disallow deleted locations
+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;
+
+    attr_def        RECORD;
+    tmp_attr_set    RECORD;
+    attr_set        vandelay.import_item%ROWTYPE;
+
+    xpath           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 '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
+                ELSE '//*[@tag="' || attr_def.tag || '"]/*' || 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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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
+            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;
+
+        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,
+                                  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 )
+        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 AND NOT deleted;
+                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) 
+                        AND NOT cpl.deleted
+                    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;
+
+            RETURN NEXT attr_set;
+
+        END LOOP;
+
+    END IF;
+
+    RETURN;
+
+END;
+$$ LANGUAGE PLPGSQL;
+
+
+
+COMMIT;
+
index 6738ffd..9ae36f2 100644 (file)
@@ -705,7 +705,7 @@ if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
             orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
 
             var self = this;
-            var search = {owning_lib : orgs};
+            var search = {owning_lib : orgs, deleted : 'f'};
 
             if(this.cache.copyLocStore) {
                 var store = this.cache.copyLocStore;
@@ -744,7 +744,7 @@ if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
                 } else {
                     // cached IDs plus id of this.widgetValue;
                     locIds.push(this.widgetValue);
-                    search = {id : locIds};
+                    search = {id : locIds, deleted: 'f'};
                 }
             } 
 
index 9a9770b..634e403 100644 (file)
@@ -64,7 +64,7 @@ function fetchCopyLocations() {
 
             var pcrud = new openils.PermaCrud({authtoken : user.authtoken});
             pcrud.search('acpl', // this can take some time...
-                {owning_lib : ownerOrgList},
+                {owning_lib : ownerOrgList, deleted: 'f'},
                 {   
                     async : true,
                     join : 'aou',
index e11f34f..22b60eb 100644 (file)
@@ -38,7 +38,7 @@ function filterGrid(org) {
         var pcrud = new openils.PermaCrud({authtoken : user.authtoken});
         orders = pcrud.search('acplo', {org : org}, {order_by : {acplo : 'position'}});
         locations = pcrud.search('acpl', 
-            {owning_lib : fieldmapper.aou.orgNodeTrail(fieldmapper.aou.findOrgUnit(org), true)}, 
+            {owning_lib : fieldmapper.aou.orgNodeTrail(fieldmapper.aou.findOrgUnit(org), true), deleted : 'f'}, 
             {order_by : {acpl : 'name'}}
         ); 
     }
index eca7c98..4bc6f4b 100644 (file)
@@ -34,7 +34,7 @@ function load(){
     new openils.User().getPermOrgList(
         'ADMIN_CIRC_MATRIX_MATCHPOINT',
         function (orgList) {
-            temp = pcrud.search('acpl', {owning_lib : orgList});
+            temp = pcrud.search('acpl', {owning_lib : orgList, deleted: 'f'});
             dojo.forEach(temp, function(g) { copyLocCache[g.id()] = g; } );
         }, 
         true, true 
index 1eaae20..ddd1a5c 100644 (file)
@@ -44,7 +44,7 @@ function fetch_adv_copy_locations(org_ids) {
     var params = [{
         cache : 1, 
         fields : ['name', 'id', 'owning_lib'],
-        query : {owning_lib : org_ids, opac_visible : 't'}
+        query : {owning_lib : org_ids, opac_visible : 't', deleted : 'f'}
     }];
 
     new OpenSRF.ClientSession('open-ils.fielder').request({