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

Evergreen Git git at git.evergreen-ils.org
Tue Jun 21 16:14:29 EDT 2011


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  4c21518b658e7c56b819acb78b626a239a13ca24 (commit)
       via  bf635dc08a41bc6c58f40febb7818f08b67f7174 (commit)
       via  74ac0cc2971d53a24e1feb076f774133e31b964f (commit)
      from  ee4a2cb8f73cc15311f9fc777e287dd98744b3e1 (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 4c21518b658e7c56b819acb78b626a239a13ca24
Author: Bill Erickson <berick at esilibrary.com>
Date:   Tue Jun 21 16:11:44 2011 -0400

    Wrapped DB script
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index d998f7f..0a11e2f 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -86,7 +86,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 ('0561', :eg_version); -- miker
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0562', :eg_version); -- berick
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/100.circ_matrix.sql b/Open-ILS/src/sql/Pg/100.circ_matrix.sql
index 9074ba7..77e975e 100644
--- a/Open-ILS/src/sql/Pg/100.circ_matrix.sql
+++ b/Open-ILS/src/sql/Pg/100.circ_matrix.sql
@@ -128,9 +128,7 @@ BEGIN
     END IF;
 
     -- Ditto
-    IF item_object.active_date IS NOT NULL THEN
-        SELECT INTO my_item_age age(item_object.active_date);
-    END IF;
+    SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));
 
     -- Grab the closest set circ weight setting.
     SELECT INTO weights cw.*
diff --git a/Open-ILS/src/sql/Pg/110.hold_matrix.sql b/Open-ILS/src/sql/Pg/110.hold_matrix.sql
index 76a1259..0bb95de 100644
--- a/Open-ILS/src/sql/Pg/110.hold_matrix.sql
+++ b/Open-ILS/src/sql/Pg/110.hold_matrix.sql
@@ -81,9 +81,7 @@ BEGIN
     SELECT INTO item_cn_object      * FROM asset.call_number        WHERE id = item_object.call_number;
     SELECT INTO rec_descriptor      * FROM metabib.rec_descriptor   WHERE record = item_cn_object.record;
 
-    IF item_object.active_date IS NOT NULL THEN
-        SELECT INTO my_item_age age(item_object.active_date);
-    END IF;
+    SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));
 
     -- The item's owner should probably be the one determining if the item is holdable
     -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.active_date.sql b/Open-ILS/src/sql/Pg/upgrade/0562.schema.copy_active_date.sql
similarity index 98%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.active_date.sql
rename to Open-ILS/src/sql/Pg/upgrade/0562.schema.copy_active_date.sql
index 7c361ed..8bfc1dc 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.active_date.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0562.schema.copy_active_date.sql
@@ -1,3 +1,12 @@
+-- Evergreen DB patch 0562.schema.copy_active_date.sql
+--
+-- Active Date
+
+BEGIN;
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0562', :eg_version);
+
 ALTER TABLE asset.copy
     ADD COLUMN active_date TIMESTAMP WITH TIME ZONE;
 
@@ -277,9 +286,7 @@ BEGIN
     END IF;
 
     -- Ditto
-    IF item_object.active_date IS NOT NULL THEN
-        SELECT INTO my_item_age age(item_object.active_date);
-    END IF;
+    SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));
 
     -- Grab the closest set circ weight setting.
     SELECT INTO weights cw.*
@@ -460,9 +467,7 @@ BEGIN
     SELECT INTO item_cn_object      * FROM asset.call_number        WHERE id = item_object.call_number;
     SELECT INTO rec_descriptor      * FROM metabib.rec_descriptor   WHERE record = item_cn_object.record;
 
-    IF item_object.active_date IS NOT NULL THEN
-        SELECT INTO my_item_age age(item_object.active_date);
-    END IF;
+    SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));
 
     -- The item's owner should probably be the one determining if the item is holdable
     -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
@@ -616,3 +621,5 @@ UPDATE asset.copy SET active_date = create_date WHERE id IN (SELECT id FROM exte
 
 -- Assume create date for status change time while we are at it. Because being created WAS a change in status.
 UPDATE asset.copy SET status_changed_time = create_date WHERE status_changed_time IS NULL;
+
+COMMIT;

commit bf635dc08a41bc6c58f40febb7818f08b67f7174
Author: Thomas Berezansky <tsbere at mvlc.org>
Date:   Sun Jun 12 21:11:25 2011 -0400

    Unwrapped upgrade script for active date
    
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.active_date.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.active_date.sql
new file mode 100644
index 0000000..7c361ed
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.active_date.sql
@@ -0,0 +1,618 @@
+ALTER TABLE asset.copy
+    ADD COLUMN active_date TIMESTAMP WITH TIME ZONE;
+
+ALTER TABLE auditor.asset_copy_history
+    ADD COLUMN active_date TIMESTAMP WITH TIME ZONE;
+
+ALTER TABLE config.copy_status
+    ADD COLUMN copy_active BOOL NOT NULL DEFAULT FALSE;
+
+ALTER TABLE config.circ_matrix_weights
+    ADD COLUMN item_age NUMERIC(6,2) NOT NULL DEFAULT 0.0;
+
+ALTER TABLE config.hold_matrix_weights
+    ADD COLUMN item_age NUMERIC(6,2) NOT NULL DEFAULT 0.0;
+
+-- The two defaults above were to stop erroring on NOT NULL
+-- Remove them here
+ALTER TABLE config.circ_matrix_weights
+    ALTER COLUMN item_age DROP DEFAULT;
+
+ALTER TABLE config.hold_matrix_weights
+    ALTER COLUMN item_age DROP DEFAULT;
+
+ALTER TABLE config.circ_matrix_matchpoint
+    ADD COLUMN item_age INTERVAL;
+
+ALTER TABLE config.hold_matrix_matchpoint
+    ADD COLUMN item_age INTERVAL;
+
+CREATE OR REPLACE FUNCTION asset.acp_status_changed()
+RETURNS TRIGGER AS $$
+BEGIN
+    IF NEW.status <> OLD.status THEN
+        NEW.status_changed_time := now();
+        IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
+            NEW.active_date := now();
+        END IF;
+    END IF;
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION asset.acp_created()
+RETURNS TRIGGER AS $$
+BEGIN
+    IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
+        NEW.active_date := now();
+    END IF;
+    IF NEW.status_changed_time IS NULL THEN
+        NEW.status_changed_time := now();
+    END IF;
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER acp_created_trig
+    BEFORE INSERT ON asset.copy
+    FOR EACH ROW EXECUTE PROCEDURE asset.acp_created();
+
+CREATE TRIGGER sunit_created_trig
+    BEFORE INSERT ON serial.unit
+    FOR EACH ROW EXECUTE PROCEDURE asset.acp_created();
+
+CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT, retargetting BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
+DECLARE
+    matchpoint_id        INT;
+    user_object        actor.usr%ROWTYPE;
+    age_protect_object    config.rule_age_hold_protect%ROWTYPE;
+    standing_penalty    config.standing_penalty%ROWTYPE;
+    transit_range_ou_type    actor.org_unit_type%ROWTYPE;
+    transit_source        actor.org_unit%ROWTYPE;
+    item_object        asset.copy%ROWTYPE;
+    item_cn_object     asset.call_number%ROWTYPE;
+    ou_skip              actor.org_unit_setting%ROWTYPE;
+    result            action.matrix_test_result;
+    hold_test        config.hold_matrix_matchpoint%ROWTYPE;
+    use_active_date   TEXT;
+    age_protect_date  TIMESTAMP WITH TIME ZONE;
+    hold_count        INT;
+    hold_transit_prox    INT;
+    frozen_hold_count    INT;
+    context_org_list    INT[];
+    done            BOOL := FALSE;
+BEGIN
+    SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+    SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
+
+    result.success := TRUE;
+
+    -- Fail if we couldn't find a user
+    IF user_object.id IS NULL THEN
+        result.fail_part := 'no_user';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+
+    -- Fail if we couldn't find a copy
+    IF item_object.id IS NULL THEN
+        result.fail_part := 'no_item';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
+    result.matchpoint := matchpoint_id;
+
+    SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
+
+    -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
+    IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
+        result.fail_part := 'circ.holds.target_skip_me';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    -- Fail if user is barred
+    IF user_object.barred IS TRUE THEN
+        result.fail_part := 'actor.usr.barred';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    -- Fail if we couldn't find any matchpoint (requires a default)
+    IF matchpoint_id IS NULL THEN
+        result.fail_part := 'no_matchpoint';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
+
+    IF hold_test.holdable IS FALSE THEN
+        result.fail_part := 'config.hold_matrix_test.holdable';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END IF;
+
+    IF hold_test.transit_range IS NOT NULL THEN
+        SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
+        IF hold_test.distance_is_from_owner THEN
+            SELECT INTO transit_source ou.* FROM actor.org_unit ou JOIN asset.call_number cn ON (cn.owning_lib = ou.id) WHERE cn.id = item_object.call_number;
+        ELSE
+            SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
+        END IF;
+
+        PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
+
+        IF NOT FOUND THEN
+            result.fail_part := 'transit_range';
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END IF;
+    END IF;
+ 
+    FOR standing_penalty IN
+        SELECT  DISTINCT csp.*
+          FROM  actor.usr_standing_penalty usp
+                JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+          WHERE usr = match_user
+                AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
+                AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+                AND csp.block_list LIKE '%HOLD%' LOOP
+
+        result.fail_part := standing_penalty.name;
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END LOOP;
+
+    IF hold_test.stop_blocked_user IS TRUE THEN
+        FOR standing_penalty IN
+            SELECT  DISTINCT csp.*
+              FROM  actor.usr_standing_penalty usp
+                    JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+              WHERE usr = match_user
+                    AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
+                    AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+                    AND csp.block_list LIKE '%CIRC%' LOOP
+    
+            result.fail_part := standing_penalty.name;
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END LOOP;
+    END IF;
+
+    IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
+        SELECT    INTO hold_count COUNT(*)
+          FROM    action.hold_request
+          WHERE    usr = match_user
+            AND fulfillment_time IS NULL
+            AND cancel_time IS NULL
+            AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
+
+        IF hold_count >= hold_test.max_holds THEN
+            result.fail_part := 'config.hold_matrix_test.max_holds';
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END IF;
+    END IF;
+
+    IF item_object.age_protect IS NOT NULL THEN
+        SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
+        IF hold_test.distance_is_from_owner THEN
+            SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_cn_object.owning_lib);
+        ELSE
+            SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_object.circ_lib);
+        END IF;
+        IF use_active_date = 'true' THEN
+            age_protect_date := COALESCE(item_object.active_date, NOW());
+        ELSE
+            age_protect_date := item_object.create_date;
+        END IF;
+        IF age_protect_date + age_protect_object.age > NOW() THEN
+            IF hold_test.distance_is_from_owner THEN
+                SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+                SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
+            ELSE
+                SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
+            END IF;
+
+            IF hold_transit_prox > age_protect_object.prox THEN
+                result.fail_part := 'config.rule_age_hold_protect.prox';
+                result.success := FALSE;
+                done := TRUE;
+                RETURN NEXT result;
+            END IF;
+        END IF;
+    END IF;
+
+    IF NOT done THEN
+        RETURN NEXT result;
+    END IF;
+
+    RETURN;
+END;
+$func$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, item_object asset.copy, user_object actor.usr, renewal BOOL ) RETURNS action.found_circ_matrix_matchpoint AS $func$
+DECLARE
+    cn_object       asset.call_number%ROWTYPE;
+    rec_descriptor  metabib.rec_descriptor%ROWTYPE;
+    cur_matchpoint  config.circ_matrix_matchpoint%ROWTYPE;
+    matchpoint      config.circ_matrix_matchpoint%ROWTYPE;
+    weights         config.circ_matrix_weights%ROWTYPE;
+    user_age        INTERVAL;
+    my_item_age     INTERVAL;
+    denominator     NUMERIC(6,2);
+    row_list        INT[];
+    result          action.found_circ_matrix_matchpoint;
+BEGIN
+    -- Assume failure
+    result.success = false;
+
+    -- Fetch useful data
+    SELECT INTO cn_object       * FROM asset.call_number        WHERE id = item_object.call_number;
+    SELECT INTO rec_descriptor  * FROM metabib.rec_descriptor   WHERE record = cn_object.record;
+
+    -- Pre-generate this so we only calc it once
+    IF user_object.dob IS NOT NULL THEN
+        SELECT INTO user_age age(user_object.dob);
+    END IF;
+
+    -- Ditto
+    IF item_object.active_date IS NOT NULL THEN
+        SELECT INTO my_item_age age(item_object.active_date);
+    END IF;
+
+    -- Grab the closest set circ weight setting.
+    SELECT INTO weights cw.*
+      FROM config.weight_assoc wa
+           JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
+           JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
+      WHERE active
+      ORDER BY d.distance
+      LIMIT 1;
+
+    -- No weights? Bad admin! Defaults to handle that anyway.
+    IF weights.id IS NULL THEN
+        weights.grp                 := 11.0;
+        weights.org_unit            := 10.0;
+        weights.circ_modifier       := 5.0;
+        weights.marc_type           := 4.0;
+        weights.marc_form           := 3.0;
+        weights.marc_bib_level      := 2.0;
+        weights.marc_vr_format      := 2.0;
+        weights.copy_circ_lib       := 8.0;
+        weights.copy_owning_lib     := 8.0;
+        weights.user_home_ou        := 8.0;
+        weights.ref_flag            := 1.0;
+        weights.juvenile_flag       := 6.0;
+        weights.is_renewal          := 7.0;
+        weights.usr_age_lower_bound := 0.0;
+        weights.usr_age_upper_bound := 0.0;
+        weights.item_age            := 0.0;
+    END IF;
+
+    -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
+    -- If you break your org tree with funky parenting this may be wrong
+    -- Note: This CTE is duplicated in the find_hold_matrix_matchpoint function, and it may be a good idea to split it off to a function
+    -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
+    WITH all_distance(distance) AS (
+            SELECT depth AS distance FROM actor.org_unit_type
+        UNION
+       	    SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
+	)
+    SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
+
+    -- Loop over all the potential matchpoints
+    FOR cur_matchpoint IN
+        SELECT m.*
+          FROM  config.circ_matrix_matchpoint m
+                /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
+                /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
+                LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
+                LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
+                LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
+          WHERE m.active
+                -- Permission Groups
+             -- AND (m.grp                      IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
+                -- Org Units
+             -- AND (m.org_unit                 IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
+                AND (m.copy_owning_lib          IS NULL OR cnoua.id IS NOT NULL)
+                AND (m.copy_circ_lib            IS NULL OR iooua.id IS NOT NULL)
+                AND (m.user_home_ou             IS NULL OR uhoua.id IS NOT NULL)
+                -- Circ Type
+                AND (m.is_renewal               IS NULL OR m.is_renewal = renewal)
+                -- Static User Checks
+                AND (m.juvenile_flag            IS NULL OR m.juvenile_flag = user_object.juvenile)
+                AND (m.usr_age_lower_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
+                AND (m.usr_age_upper_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
+                -- Static Item Checks
+                AND (m.circ_modifier            IS NULL OR m.circ_modifier = item_object.circ_modifier)
+                AND (m.marc_type                IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
+                AND (m.marc_form                IS NULL OR m.marc_form = rec_descriptor.item_form)
+                AND (m.marc_bib_level           IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
+                AND (m.marc_vr_format           IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
+                AND (m.ref_flag                 IS NULL OR m.ref_flag = item_object.ref)
+                AND (m.item_age                 IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
+          ORDER BY
+                -- Permission Groups
+                CASE WHEN upgad.distance        IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
+                -- Org Units
+                CASE WHEN ctoua.distance        IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
+                CASE WHEN cnoua.distance        IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
+                CASE WHEN iooua.distance        IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
+                CASE WHEN uhoua.distance        IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
+                -- Circ Type                    -- Note: 4^x is equiv to 2^(2*x)
+                CASE WHEN m.is_renewal          IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
+                -- Static User Checks
+                CASE WHEN m.juvenile_flag       IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
+                CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
+                CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
+                -- Static Item Checks
+                CASE WHEN m.circ_modifier       IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
+                CASE WHEN m.marc_type           IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
+                CASE WHEN m.marc_form           IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
+                CASE WHEN m.marc_vr_format      IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
+                CASE WHEN m.ref_flag            IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
+                -- Item age has a slight adjustment to weight based on value.
+                -- This should ensure that a shorter age limit comes first when all else is equal.
+                -- NOTE: This assumes that intervals will normally be in days.
+                CASE WHEN m.item_age            IS NOT NULL THEN 4^weights.item_age - 1 + 86400/EXTRACT(EPOCH FROM m.item_age) ELSE 0.0 END DESC,
+                -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
+                -- This prevents "we changed the table order by updating a rule, and we started getting different results"
+                m.id LOOP
+
+        -- Record the full matching row list
+        row_list := row_list || cur_matchpoint.id;
+
+        -- No matchpoint yet?
+        IF matchpoint.id IS NULL THEN
+            -- Take the entire matchpoint as a starting point
+            matchpoint := cur_matchpoint;
+            CONTINUE; -- No need to look at this row any more.
+        END IF;
+
+        -- Incomplete matchpoint?
+        IF matchpoint.circulate IS NULL THEN
+            matchpoint.circulate := cur_matchpoint.circulate;
+        END IF;
+        IF matchpoint.duration_rule IS NULL THEN
+            matchpoint.duration_rule := cur_matchpoint.duration_rule;
+        END IF;
+        IF matchpoint.recurring_fine_rule IS NULL THEN
+            matchpoint.recurring_fine_rule := cur_matchpoint.recurring_fine_rule;
+        END IF;
+        IF matchpoint.max_fine_rule IS NULL THEN
+            matchpoint.max_fine_rule := cur_matchpoint.max_fine_rule;
+        END IF;
+        IF matchpoint.hard_due_date IS NULL THEN
+            matchpoint.hard_due_date := cur_matchpoint.hard_due_date;
+        END IF;
+        IF matchpoint.total_copy_hold_ratio IS NULL THEN
+            matchpoint.total_copy_hold_ratio := cur_matchpoint.total_copy_hold_ratio;
+        END IF;
+        IF matchpoint.available_copy_hold_ratio IS NULL THEN
+            matchpoint.available_copy_hold_ratio := cur_matchpoint.available_copy_hold_ratio;
+        END IF;
+        IF matchpoint.renewals IS NULL THEN
+            matchpoint.renewals := cur_matchpoint.renewals;
+        END IF;
+        IF matchpoint.grace_period IS NULL THEN
+            matchpoint.grace_period := cur_matchpoint.grace_period;
+        END IF;
+    END LOOP;
+
+    -- Check required fields
+    IF matchpoint.circulate             IS NOT NULL AND
+       matchpoint.duration_rule         IS NOT NULL AND
+       matchpoint.recurring_fine_rule   IS NOT NULL AND
+       matchpoint.max_fine_rule         IS NOT NULL THEN
+        -- All there? We have a completed match.
+        result.success := true;
+    END IF;
+
+    -- Include the assembled matchpoint, even if it isn't complete
+    result.matchpoint := matchpoint;
+
+    -- Include (for debugging) the full list of matching rows
+    result.buildrows := row_list;
+
+    -- Hand the result back to caller
+    RETURN result;
+END;
+$func$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
+  RETURNS integer AS
+$func$
+DECLARE
+    requestor_object    actor.usr%ROWTYPE;
+    user_object         actor.usr%ROWTYPE;
+    item_object         asset.copy%ROWTYPE;
+    item_cn_object      asset.call_number%ROWTYPE;
+    my_item_age         INTERVAL;
+    rec_descriptor      metabib.rec_descriptor%ROWTYPE;
+    matchpoint          config.hold_matrix_matchpoint%ROWTYPE;
+    weights             config.hold_matrix_weights%ROWTYPE;
+    denominator         NUMERIC(6,2);
+BEGIN
+    SELECT INTO user_object         * FROM actor.usr                WHERE id = match_user;
+    SELECT INTO requestor_object    * FROM actor.usr                WHERE id = match_requestor;
+    SELECT INTO item_object         * FROM asset.copy               WHERE id = match_item;
+    SELECT INTO item_cn_object      * FROM asset.call_number        WHERE id = item_object.call_number;
+    SELECT INTO rec_descriptor      * FROM metabib.rec_descriptor   WHERE record = item_cn_object.record;
+
+    IF item_object.active_date IS NOT NULL THEN
+        SELECT INTO my_item_age age(item_object.active_date);
+    END IF;
+
+    -- The item's owner should probably be the one determining if the item is holdable
+    -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
+    -- This flag will allow for setting it to the owning library (where the call number "lives")
+    PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
+
+    -- Grab the closest set circ weight setting.
+    IF NOT FOUND THEN
+        -- Default to circ library
+        SELECT INTO weights hw.*
+          FROM config.weight_assoc wa
+               JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
+               JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
+          WHERE active
+          ORDER BY d.distance
+          LIMIT 1;
+    ELSE
+        -- Flag is set, use owning library
+        SELECT INTO weights hw.*
+          FROM config.weight_assoc wa
+               JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
+               JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) d ON (wa.org_unit = d.id)
+          WHERE active
+          ORDER BY d.distance
+          LIMIT 1;
+    END IF;
+
+    -- No weights? Bad admin! Defaults to handle that anyway.
+    IF weights.id IS NULL THEN
+        weights.user_home_ou    := 5.0;
+        weights.request_ou      := 5.0;
+        weights.pickup_ou       := 5.0;
+        weights.item_owning_ou  := 5.0;
+        weights.item_circ_ou    := 5.0;
+        weights.usr_grp         := 7.0;
+        weights.requestor_grp   := 8.0;
+        weights.circ_modifier   := 4.0;
+        weights.marc_type       := 3.0;
+        weights.marc_form       := 2.0;
+        weights.marc_bib_level  := 1.0;
+        weights.marc_vr_format  := 1.0;
+        weights.juvenile_flag   := 4.0;
+        weights.ref_flag        := 0.0;
+        weights.item_age        := 0.0;
+    END IF;
+
+    -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
+    -- If you break your org tree with funky parenting this may be wrong
+    -- Note: This CTE is duplicated in the find_circ_matrix_matchpoint function, and it may be a good idea to split it off to a function
+    -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
+    WITH all_distance(distance) AS (
+            SELECT depth AS distance FROM actor.org_unit_type
+        UNION
+            SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
+	)
+    SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
+
+    -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
+    -- This may be better implemented as part of the upgrade script?
+    -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
+    -- Then remove this flag, of course.
+    PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
+
+    IF FOUND THEN
+        -- Note: This, to me, is REALLY hacky. I put it in anyway.
+        -- If you can't tell, this is a single call swap on two variables.
+        SELECT INTO user_object.profile, requestor_object.profile
+                    requestor_object.profile, user_object.profile;
+    END IF;
+
+    -- Select the winning matchpoint into the matchpoint variable for returning
+    SELECT INTO matchpoint m.*
+      FROM  config.hold_matrix_matchpoint m
+            /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
+            LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
+            LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
+            LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
+            LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
+            LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
+            LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
+      WHERE m.active
+            -- Permission Groups
+         -- AND (m.requestor_grp        IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
+            AND (m.usr_grp              IS NULL OR upgad.id IS NOT NULL)
+            -- Org Units
+            AND (m.pickup_ou            IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
+            AND (m.request_ou           IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
+            AND (m.item_owning_ou       IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
+            AND (m.item_circ_ou         IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
+            AND (m.user_home_ou         IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
+            -- Static User Checks
+            AND (m.juvenile_flag        IS NULL OR m.juvenile_flag = user_object.juvenile)
+            -- Static Item Checks
+            AND (m.circ_modifier        IS NULL OR m.circ_modifier = item_object.circ_modifier)
+            AND (m.marc_type            IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
+            AND (m.marc_form            IS NULL OR m.marc_form = rec_descriptor.item_form)
+            AND (m.marc_bib_level       IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
+            AND (m.marc_vr_format       IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
+            AND (m.ref_flag             IS NULL OR m.ref_flag = item_object.ref)
+            AND (m.item_age             IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
+      ORDER BY
+            -- Permission Groups
+            CASE WHEN rpgad.distance    IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
+            CASE WHEN upgad.distance    IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
+            -- Org Units
+            CASE WHEN puoua.distance    IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
+            CASE WHEN rqoua.distance    IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
+            CASE WHEN cnoua.distance    IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
+            CASE WHEN iooua.distance    IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
+            CASE WHEN uhoua.distance    IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
+            -- Static User Checks       -- Note: 4^x is equiv to 2^(2*x)
+            CASE WHEN m.juvenile_flag   IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
+            -- Static Item Checks
+            CASE WHEN m.circ_modifier   IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
+            CASE WHEN m.marc_type       IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
+            CASE WHEN m.marc_form       IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
+            CASE WHEN m.marc_vr_format  IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
+            CASE WHEN m.ref_flag        IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
+            -- Item age has a slight adjustment to weight based on value.
+            -- This should ensure that a shorter age limit comes first when all else is equal.
+            -- NOTE: This assumes that intervals will normally be in days.
+            CASE WHEN m.item_age            IS NOT NULL THEN 4^weights.item_age - 86400/EXTRACT(EPOCH FROM m.item_age) ELSE 0.0 END DESC,
+            -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
+            -- This prevents "we changed the table order by updating a rule, and we started getting different results"
+            m.id;
+
+    -- Return just the ID for now
+    RETURN matchpoint.id;
+END;
+$func$ LANGUAGE 'plpgsql';
+
+DROP INDEX IF EXISTS config.ccmm_once_per_paramset;
+
+DROP INDEX IF EXISTS config.chmm_once_per_paramset;
+
+CREATE UNIQUE INDEX ccmm_once_per_paramset ON config.circ_matrix_matchpoint (org_unit, grp, COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_bib_level,''), COALESCE(marc_vr_format, ''), COALESCE(copy_circ_lib::TEXT, ''), COALESCE(copy_owning_lib::TEXT, ''), COALESCE(user_home_ou::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(is_renewal::TEXT, ''), COALESCE(usr_age_lower_bound::TEXT, ''), COALESCE(usr_age_upper_bound::TEXT, ''), COALESCE(item_age::TEXT, '')) WHERE active;
+
+CREATE UNIQUE INDEX chmm_once_per_paramset ON config.hold_matrix_matchpoint (COALESCE(user_home_ou::TEXT, ''), COALESCE(request_ou::TEXT, ''), COALESCE(pickup_ou::TEXT, ''), COALESCE(item_owning_ou::TEXT, ''), COALESCE(item_circ_ou::TEXT, ''), COALESCE(usr_grp::TEXT, ''), COALESCE(requestor_grp::TEXT, ''), COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_bib_level, ''), COALESCE(marc_vr_format, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(item_age::TEXT, '')) WHERE active;
+
+UPDATE config.copy_status SET copy_active = true WHERE id IN (0, 1, 7, 8, 10, 12, 15);
+
+INSERT into config.org_unit_setting_type
+( name, label, description, datatype ) VALUES
+( 'circ.holds.age_protect.active_date', 'Holds: Use Active Date for Age Protection', 'When calculating age protection rules use the active date instead of the creation date.', 'bool');
+
+-- Assume create date when item is in status we would update active date for anyway
+UPDATE asset.copy SET active_date = create_date WHERE status IN (SELECT id FROM config.copy_status WHERE copy_active = true);
+
+-- Assume create date for any item with circs
+UPDATE asset.copy SET active_date = create_date WHERE id IN (SELECT id FROM extend_reporter.full_circ_count WHERE circ_count > 0);
+
+-- Assume create date for status change time while we are at it. Because being created WAS a change in status.
+UPDATE asset.copy SET status_changed_time = create_date WHERE status_changed_time IS NULL;

commit 74ac0cc2971d53a24e1feb076f774133e31b964f
Author: Thomas Berezansky <tsbere at mvlc.org>
Date:   Sun Jun 12 21:11:13 2011 -0400

    Active date
    
    Record the date a copy first became "active" after creation in active_date.
    Offer Org Unit setting for using the active date for age hold protection.
    Any copy without an active_date, with age hold protect using it, will be
        protected until it has an active date. Regardless of create_date.
    Circ/Hold matrix can match on item age based on active_date.
    
    Active is defined as entering a status with copy_active set to true. By default:
    
    Available
    Checked out
    Reshelving
    On holds shelf
    ILL
    Reserves
    On reservation shelf
    
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>
    Signed-off-by: Bill Erickson <berick at esilibrary.com>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index e41464e..ce42aa3 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -1154,6 +1154,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             <field reporter:label="MARC Form" name="marc_form" oils_persist:primitive="string" reporter:datatype="float"/>
             <field reporter:label="Videorecording Format" name="marc_vr_format" oils_persist:primitive="string" reporter:datatype="float"/>
             <field reporter:label="Reference?" name="ref_flag" reporter:datatype="float"/>
+            <field reporter:label="Item Age &lt;" name="item_age" reporter:datatype="float"/>
         </fields>
         <links/>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
@@ -1182,6 +1183,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             <field reporter:label="Reference?" name="ref_flag" reporter:datatype="float"/>
             <field reporter:label="User Age: Lower Bound" name="usr_age_lower_bound" reporter:datatype="float"/>
             <field reporter:label="User Age: Upper Bound" name="usr_age_upper_bound" reporter:datatype="float"/>
+            <field reporter:label="Item Age &lt;" name="item_age" reporter:datatype="float"/>
         </fields>
         <links/>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
@@ -1235,6 +1237,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			<field reporter:label="MARC Bib Level" name="marc_bib_level" oils_persist:primitive="string" reporter:datatype="link"/>
 			<field reporter:label="Videorecording Format" name="marc_vr_format" oils_persist:primitive="string" reporter:datatype="link"/>
 			<field reporter:label="Reference?" name="ref_flag" reporter:datatype="bool"/>
+            <field reporter:label="Item Age &lt;" name="item_age" reporter:datatype="text"/>
 			<field reporter:label="Holdable?" name="holdable" reporter:datatype="bool"/>
 			<field reporter:label="Range is from Owning Lib?" name="distance_is_from_owner" reporter:datatype="bool"/>
 			<field reporter:label="Transit Range" name="transit_range" reporter:datatype="link"/>
@@ -1287,6 +1290,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             <field reporter:label="Juvenile?" name="juvenile_flag" reporter:datatype="bool"/>
 			<field reporter:label="User Age: Lower Bound" name="usr_age_lower_bound" reporter:datatype="text"/>
 			<field reporter:label="User Age: Upper Bound" name="usr_age_upper_bound" reporter:datatype="text"/>
+            <field reporter:label="Item Age &lt;" name="item_age" reporter:datatype="text"/>
 			<field reporter:label="Circulate?" name="circulate" reporter:datatype="bool"/>
 			<field reporter:label="Duration Rule" name="duration_rule" reporter:datatype="link"/>
 			<field reporter:label="Recurring Fine Rule" name="recurring_fine_rule" reporter:datatype="link"/>
@@ -2709,6 +2713,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			<field name="id" reporter:selector="name" reporter:datatype="id"/>
 			<field name="name"  reporter:datatype="text" oils_persist:i18n="true"/>
 			<field name="opac_visible" reporter:datatype="bool"/>
+            <field name="copy_active" reporter:datatype="bool"/>
 		</fields>
 		<links/>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
@@ -3787,6 +3792,7 @@ SELECT  usr,
 			<field reporter:label="Can Circulate" name="circulate" reporter:datatype="bool"/>
 			<field reporter:label="Copy Number on Volume" name="copy_number" reporter:datatype="text"/>
 			<field reporter:label="Creation Date/Time" name="create_date" reporter:datatype="timestamp"/>
+			<field reporter:label="Active Date/Time" name="active_date" reporter:datatype="timestamp"/>
 			<field reporter:label="Creating User" name="creator" reporter:datatype="link"/>
 			<field reporter:label="Is Deleted" name="deleted" reporter:datatype="bool"/>
 			<field reporter:label="Dummy ISBN" name="dummy_isbn" reporter:datatype="text"/>
@@ -4813,6 +4819,7 @@ SELECT  usr,
 			<field reporter:label="Can Circulate" name="circulate" reporter:datatype="bool"/>
 			<field reporter:label="Copy Number on Volume" name="copy_number" reporter:datatype="text"/>
 			<field reporter:label="Creation Date/Time" name="create_date" reporter:datatype="timestamp"/>
+			<field reporter:label="Active Date/Time" name="active_date" reporter:datatype="timestamp"/>
 			<field reporter:label="Creating User" name="creator" reporter:datatype="link"/>
 			<field reporter:label="Is Deleted" name="deleted" reporter:datatype="bool"/>
 			<field reporter:label="Dummy ISBN" name="dummy_isbn" reporter:datatype="text"/>
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm
index 6cecb8d..0fc1061 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/asset.pm
@@ -72,7 +72,7 @@ __PACKAGE__->columns( Essential => qw/call_number barcode creator create_date ed
 				   fine_level circulate deposit price ref opac_visible
 				   circ_as_type circ_modifier deposit_amount location mint_condition
 				   holdable dummy_title dummy_author deleted alert_message
-				   age_protect floating cost status_changed_time/ );
+				   age_protect floating cost status_changed_time active_date/ );
 
 #-------------------------------------------------------------------------------
 package asset::copy_part_map;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/config.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/config.pm
index da03f1f..5034730 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/config.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/config.pm
@@ -73,7 +73,7 @@ package config::copy_status;
 use base qw/config/;
 __PACKAGE__->table('config_copy_status');
 __PACKAGE__->columns(Primary => 'id');
-__PACKAGE__->columns(Essential => qw/name holdable opac_visible/);
+__PACKAGE__->columns(Essential => qw/name holdable opac_visible copy_active/);
 #-------------------------------------------------------------------------------
 
 package config::net_access_level;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/serial.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/serial.pm
index 431c672..0264aa6 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/serial.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/serial.pm
@@ -45,7 +45,7 @@ __PACKAGE__->columns( Essential => qw/call_number barcode creator create_date ed
 				   fine_level circulate deposit price ref opac_visible dummy_isbn
 				   circ_as_type circ_modifier deposit_amount location mint_condition
 				   holdable dummy_title dummy_author deleted alert_message
-				   age_protect floating summary_contents detailed_contents/ );
+				   age_protect floating summary_contents detailed_contents active_date/ );
 
 #-------------------------------------------------------------------------------
 package serial::record_entry;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/PermitHold.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/PermitHold.pm
index 47a561a..39bfaeb 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Utils/PermitHold.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/PermitHold.pm
@@ -162,13 +162,19 @@ sub check_age_protect {
 		{ order_by => 'age' }
 	);
 
-	# Now, now many seconds old is this copy
-	my $create_date = DateTime::Format::ISO8601
-		->new
-		->parse_datetime( OpenSRF::Utils::cleanse_ISO8601($copy->create_date) )
-		->epoch;
-
-	my $age = time - $create_date;
+    my $age_protect_date = $copy->create_date;
+    $age_protect_date = $copy->active_date if($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.holds.age_protect.active_date'));
+
+    my $age = 0;
+    my $age_protect_parsed;
+    if($age_protect_date) {
+    	# Now, now many seconds old is this copy
+	    $age_protect_parsed = DateTime::Format::ISO8601
+		    ->new
+    		->parse_datetime( OpenSRF::Utils::cleanse_ISO8601($age_protect_date) )
+	    	->epoch;
+	    $age = time - $age_protect_parsed;
+    }
 
 	for my $protection ( @$protection_list ) {
 
@@ -180,7 +186,7 @@ sub check_age_protect {
 		# How many seconds old does the copy have to be to escape age protection
 		my $interval = OpenSRF::Utils::interval_to_seconds($protection->age);
 
-		$logger->info("age_protect interval=$interval, create_date=$create_date, age=$age");
+		$logger->info("age_protect interval=$interval, age_protect_date=$age_protect_parsed, age=$age");
 
 		if( $interval > $age ) { 
 			# if age of the item is less than the protection interval, 
diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index e2922c4..d998f7f 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -349,7 +349,8 @@ CREATE TABLE config.copy_status (
 	id		SERIAL	PRIMARY KEY,
 	name		TEXT	NOT NULL UNIQUE,
 	holdable	BOOL	NOT NULL DEFAULT FALSE,
-	opac_visible	BOOL	NOT NULL DEFAULT FALSE
+	opac_visible	BOOL	NOT NULL DEFAULT FALSE,
+    copy_active  BOOL    NOT NULL DEFAULT FALSE
 );
 COMMENT ON TABLE config.copy_status IS $$
 Copy Statuses
diff --git a/Open-ILS/src/sql/Pg/040.schema.asset.sql b/Open-ILS/src/sql/Pg/040.schema.asset.sql
index 010c3bc..ddb116a 100644
--- a/Open-ILS/src/sql/Pg/040.schema.asset.sql
+++ b/Open-ILS/src/sql/Pg/040.schema.asset.sql
@@ -80,6 +80,7 @@ CREATE TABLE asset.copy (
 	floating		BOOL				NOT NULL DEFAULT FALSE,
 	dummy_isbn      TEXT,
 	status_changed_time TIMESTAMP WITH TIME ZONE,
+	active_date TIMESTAMP WITH TIME ZONE,
 	mint_condition      BOOL        NOT NULL DEFAULT TRUE,
     cost    NUMERIC(8,2)
 );
@@ -118,6 +119,23 @@ RETURNS TRIGGER AS $$
 BEGIN
     IF NEW.status <> OLD.status THEN
         NEW.status_changed_time := now();
+        IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
+            NEW.active_date := now();
+        END IF;
+    END IF;
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Need to check on initial create. Fast adds, manual edit of status at create, etc.
+CREATE OR REPLACE FUNCTION asset.acp_created()
+RETURNS TRIGGER AS $$
+BEGIN
+    IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
+        NEW.active_date := now();
+    END IF;
+    IF NEW.status_changed_time IS NULL THEN
+        NEW.status_changed_time := now();
     END IF;
     RETURN NEW;
 END;
@@ -127,6 +145,10 @@ CREATE TRIGGER acp_status_changed_trig
     BEFORE UPDATE ON asset.copy
     FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
 
+CREATE TRIGGER acp_created_trig
+    BEFORE INSERT ON asset.copy
+    FOR EACH ROW EXECUTE PROCEDURE asset.acp_created();
+
 CREATE TABLE asset.stat_cat_sip_fields (
     field   CHAR(2) PRIMARY KEY,
     name    TEXT    NOT NULL,
diff --git a/Open-ILS/src/sql/Pg/099.matrix_weights.sql b/Open-ILS/src/sql/Pg/099.matrix_weights.sql
index febf569..5854d3e 100644
--- a/Open-ILS/src/sql/Pg/099.matrix_weights.sql
+++ b/Open-ILS/src/sql/Pg/099.matrix_weights.sql
@@ -19,7 +19,8 @@ CREATE TABLE config.circ_matrix_weights (
     juvenile_flag           NUMERIC(6,2)   NOT NULL,
     is_renewal              NUMERIC(6,2)   NOT NULL,
     usr_age_lower_bound     NUMERIC(6,2)   NOT NULL,
-    usr_age_upper_bound     NUMERIC(6,2)   NOT NULL
+    usr_age_upper_bound     NUMERIC(6,2)   NOT NULL,
+    item_age                NUMERIC(6,2)   NOT NULL
 );
 
 -- Hold Matrix Weights
@@ -39,7 +40,8 @@ CREATE TABLE config.hold_matrix_weights (
     marc_bib_level          NUMERIC(6,2)   NOT NULL,
     marc_vr_format          NUMERIC(6,2)   NOT NULL,
     juvenile_flag           NUMERIC(6,2)   NOT NULL,
-    ref_flag                NUMERIC(6,2)   NOT NULL
+    ref_flag                NUMERIC(6,2)   NOT NULL,
+    item_age                NUMERIC(6,2)   NOT NULL
 );
 
 -- Linking between weights and org units
diff --git a/Open-ILS/src/sql/Pg/100.circ_matrix.sql b/Open-ILS/src/sql/Pg/100.circ_matrix.sql
index fadc392..9074ba7 100644
--- a/Open-ILS/src/sql/Pg/100.circ_matrix.sql
+++ b/Open-ILS/src/sql/Pg/100.circ_matrix.sql
@@ -69,6 +69,7 @@ CREATE TABLE config.circ_matrix_matchpoint (
     is_renewal           BOOL,
     usr_age_lower_bound  INTERVAL,
     usr_age_upper_bound  INTERVAL,
+    item_age             INTERVAL,
     -- "Result" Fields
     circulate            BOOL,   -- Hard "can't circ" flag requiring an override
     duration_rule        INT     REFERENCES config.rule_circ_duration (id) DEFERRABLE INITIALLY DEFERRED,
@@ -83,7 +84,7 @@ CREATE TABLE config.circ_matrix_matchpoint (
 );
 
 -- Nulls don't count for a constraint match, so we have to coalesce them into something that does.
-CREATE UNIQUE INDEX ccmm_once_per_paramset ON config.circ_matrix_matchpoint (org_unit, grp, COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_bib_level,''), COALESCE(marc_vr_format, ''), COALESCE(copy_circ_lib::TEXT, ''), COALESCE(copy_owning_lib::TEXT, ''), COALESCE(user_home_ou::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(is_renewal::TEXT, ''), COALESCE(usr_age_lower_bound::TEXT, ''), COALESCE(usr_age_upper_bound::TEXT, '')) WHERE active;
+CREATE UNIQUE INDEX ccmm_once_per_paramset ON config.circ_matrix_matchpoint (org_unit, grp, COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_bib_level,''), COALESCE(marc_vr_format, ''), COALESCE(copy_circ_lib::TEXT, ''), COALESCE(copy_owning_lib::TEXT, ''), COALESCE(user_home_ou::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(is_renewal::TEXT, ''), COALESCE(usr_age_lower_bound::TEXT, ''), COALESCE(usr_age_upper_bound::TEXT, ''), COALESCE(item_age::TEXT, '')) WHERE active;
 
 -- Tests for max items out by circ_modifier
 CREATE TABLE config.circ_matrix_circ_mod_test (
@@ -109,6 +110,7 @@ DECLARE
     matchpoint      config.circ_matrix_matchpoint%ROWTYPE;
     weights         config.circ_matrix_weights%ROWTYPE;
     user_age        INTERVAL;
+    my_item_age     INTERVAL;
     denominator     NUMERIC(6,2);
     row_list        INT[];
     result          action.found_circ_matrix_matchpoint;
@@ -125,6 +127,11 @@ BEGIN
         SELECT INTO user_age age(user_object.dob);
     END IF;
 
+    -- Ditto
+    IF item_object.active_date IS NOT NULL THEN
+        SELECT INTO my_item_age age(item_object.active_date);
+    END IF;
+
     -- Grab the closest set circ weight setting.
     SELECT INTO weights cw.*
       FROM config.weight_assoc wa
@@ -151,6 +158,7 @@ BEGIN
         weights.is_renewal          := 7.0;
         weights.usr_age_lower_bound := 0.0;
         weights.usr_age_upper_bound := 0.0;
+        weights.item_age            := 0.0;
     END IF;
 
     -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
@@ -194,6 +202,7 @@ BEGIN
                 AND (m.marc_bib_level           IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
                 AND (m.marc_vr_format           IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
                 AND (m.ref_flag                 IS NULL OR m.ref_flag = item_object.ref)
+                AND (m.item_age                 IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
           ORDER BY
                 -- Permission Groups
                 CASE WHEN upgad.distance        IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
@@ -213,7 +222,11 @@ BEGIN
                 CASE WHEN m.marc_type           IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
                 CASE WHEN m.marc_form           IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
                 CASE WHEN m.marc_vr_format      IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
-                CASE WHEN m.ref_flag            IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
+                CASE WHEN m.ref_flag            IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
+                -- Item age has a slight adjustment to weight based on value.
+                -- This should ensure that a shorter age limit comes first when all else is equal.
+                -- NOTE: This assumes that intervals will normally be in days.
+                CASE WHEN m.item_age            IS NOT NULL THEN 4^weights.item_age - 1 + 86400/EXTRACT(EPOCH FROM m.item_age) ELSE 0.0 END DESC,
                 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
                 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
                 m.id LOOP
diff --git a/Open-ILS/src/sql/Pg/110.hold_matrix.sql b/Open-ILS/src/sql/Pg/110.hold_matrix.sql
index 5ffa88f..76a1259 100644
--- a/Open-ILS/src/sql/Pg/110.hold_matrix.sql
+++ b/Open-ILS/src/sql/Pg/110.hold_matrix.sql
@@ -47,6 +47,7 @@ CREATE TABLE config.hold_matrix_matchpoint (
     marc_vr_format          TEXT,
     juvenile_flag           BOOL,
     ref_flag                BOOL,
+    item_age                INTERVAL,
     -- "Result" Fields
     holdable                BOOL    NOT NULL DEFAULT TRUE,                -- Hard "can't hold" flag requiring an override
     distance_is_from_owner  BOOL    NOT NULL DEFAULT FALSE,                -- How to calculate transit_range.  True means owning lib, false means copy circ lib
@@ -58,7 +59,7 @@ CREATE TABLE config.hold_matrix_matchpoint (
 );
 
 -- Nulls don't count for a constraint match, so we have to coalesce them into something that does.
-CREATE UNIQUE INDEX chmm_once_per_paramset ON config.hold_matrix_matchpoint (COALESCE(user_home_ou::TEXT, ''), COALESCE(request_ou::TEXT, ''), COALESCE(pickup_ou::TEXT, ''), COALESCE(item_owning_ou::TEXT, ''), COALESCE(item_circ_ou::TEXT, ''), COALESCE(usr_grp::TEXT, ''), COALESCE(requestor_grp::TEXT, ''), COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_bib_level, ''), COALESCE(marc_vr_format, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(ref_flag::TEXT, '')) WHERE active;
+CREATE UNIQUE INDEX chmm_once_per_paramset ON config.hold_matrix_matchpoint (COALESCE(user_home_ou::TEXT, ''), COALESCE(request_ou::TEXT, ''), COALESCE(pickup_ou::TEXT, ''), COALESCE(item_owning_ou::TEXT, ''), COALESCE(item_circ_ou::TEXT, ''), COALESCE(usr_grp::TEXT, ''), COALESCE(requestor_grp::TEXT, ''), COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_bib_level, ''), COALESCE(marc_vr_format, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(item_age::TEXT, '')) WHERE active;
 
 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
   RETURNS integer AS
@@ -68,6 +69,7 @@ DECLARE
     user_object         actor.usr%ROWTYPE;
     item_object         asset.copy%ROWTYPE;
     item_cn_object      asset.call_number%ROWTYPE;
+    my_item_age         INTERVAL;
     rec_descriptor      metabib.rec_descriptor%ROWTYPE;
     matchpoint          config.hold_matrix_matchpoint%ROWTYPE;
     weights             config.hold_matrix_weights%ROWTYPE;
@@ -79,6 +81,10 @@ BEGIN
     SELECT INTO item_cn_object      * FROM asset.call_number        WHERE id = item_object.call_number;
     SELECT INTO rec_descriptor      * FROM metabib.rec_descriptor   WHERE record = item_cn_object.record;
 
+    IF item_object.active_date IS NOT NULL THEN
+        SELECT INTO my_item_age age(item_object.active_date);
+    END IF;
+
     -- The item's owner should probably be the one determining if the item is holdable
     -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
     -- This flag will allow for setting it to the owning library (where the call number "lives")
@@ -99,7 +105,7 @@ BEGIN
         SELECT INTO weights hw.*
           FROM config.weight_assoc wa
                JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
-               JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id)
+               JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) d ON (wa.org_unit = d.id)
           WHERE active
           ORDER BY d.distance
           LIMIT 1;
@@ -121,6 +127,7 @@ BEGIN
         weights.marc_vr_format  := 1.0;
         weights.juvenile_flag   := 4.0;
         weights.ref_flag        := 0.0;
+        weights.item_age        := 0.0;
     END IF;
 
     -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
@@ -176,6 +183,7 @@ BEGIN
             AND (m.marc_bib_level       IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
             AND (m.marc_vr_format       IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
             AND (m.ref_flag             IS NULL OR m.ref_flag = item_object.ref)
+            AND (m.item_age             IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
       ORDER BY
             -- Permission Groups
             CASE WHEN rpgad.distance    IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
@@ -193,7 +201,11 @@ BEGIN
             CASE WHEN m.marc_type       IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
             CASE WHEN m.marc_form       IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
             CASE WHEN m.marc_vr_format  IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
-            CASE WHEN m.ref_flag        IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
+            CASE WHEN m.ref_flag        IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
+            -- Item age has a slight adjustment to weight based on value.
+            -- This should ensure that a shorter age limit comes first when all else is equal.
+            -- NOTE: This assumes that intervals will normally be in days.
+            CASE WHEN m.item_age            IS NOT NULL THEN 4^weights.item_age - 86400/EXTRACT(EPOCH FROM m.item_age) ELSE 0.0 END DESC,
             -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
             -- This prevents "we changed the table order by updating a rule, and we started getting different results"
             m.id;
@@ -217,6 +229,8 @@ DECLARE
     ou_skip              actor.org_unit_setting%ROWTYPE;
     result            action.matrix_test_result;
     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
+    use_active_date   TEXT;
+    age_protect_date  TIMESTAMP WITH TIME ZONE;
     hold_count        INT;
     hold_transit_prox    INT;
     frozen_hold_count    INT;
@@ -357,8 +371,17 @@ BEGIN
 
     IF item_object.age_protect IS NOT NULL THEN
         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
-
-        IF item_object.create_date + age_protect_object.age > NOW() THEN
+        IF hold_test.distance_is_from_owner THEN
+            SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_cn_object.owning_lib);
+        ELSE
+            SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_object.circ_lib);
+        END IF;
+        IF use_active_date = 'true' THEN
+            age_protect_date := COALESCE(item_object.active_date, NOW());
+        ELSE
+            age_protect_date := item_object.create_date;
+        END IF;
+        IF age_protect_date + age_protect_object.age > NOW() THEN
             IF hold_test.distance_is_from_owner THEN
                 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
diff --git a/Open-ILS/src/sql/Pg/210.schema.serials.sql b/Open-ILS/src/sql/Pg/210.schema.serials.sql
index 9b884b6..29617cd 100644
--- a/Open-ILS/src/sql/Pg/210.schema.serials.sql
+++ b/Open-ILS/src/sql/Pg/210.schema.serials.sql
@@ -230,6 +230,11 @@ CREATE TRIGGER sunit_status_changed_trig
     BEFORE UPDATE ON serial.unit
     FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
 
+-- ditto
+CREATE TRIGGER sunit_created_trig
+    BEFORE INSERT ON serial.unit
+    FOR EACH ROW EXECUTE PROCEDURE asset.acp_created();
+
 CREATE TABLE serial.item (
 	id              SERIAL  PRIMARY KEY,
 	creator         INT     NOT NULL
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 35651d2..8dd539c 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -215,22 +215,22 @@ INSERT INTO config.rule_age_hold_protect VALUES
 	(2, oils_i18n_gettext(2, '6month', 'crahp', 'name'), '6 months', 2);
 SELECT SETVAL('config.rule_age_hold_protect_id_seq'::TEXT, 100);
 
-INSERT INTO config.copy_status (id,name,holdable,opac_visible) VALUES (0,oils_i18n_gettext(0, 'Available', 'ccs', 'name'),'t','t');
-INSERT INTO config.copy_status (id,name,holdable,opac_visible) VALUES (1,oils_i18n_gettext(1, 'Checked out', 'ccs', 'name'),'t','t');
+INSERT INTO config.copy_status (id,name,holdable,opac_visible,copy_active) VALUES (0,oils_i18n_gettext(0, 'Available', 'ccs', 'name'),'t','t','t');
+INSERT INTO config.copy_status (id,name,holdable,opac_visible,copy_active) VALUES (1,oils_i18n_gettext(1, 'Checked out', 'ccs', 'name'),'t','t','t');
 INSERT INTO config.copy_status (id,name) VALUES (2,oils_i18n_gettext(2, 'Bindery', 'ccs', 'name'));
 INSERT INTO config.copy_status (id,name) VALUES (3,oils_i18n_gettext(3, 'Lost', 'ccs', 'name'));
 INSERT INTO config.copy_status (id,name) VALUES (4,oils_i18n_gettext(4, 'Missing', 'ccs', 'name'));
 INSERT INTO config.copy_status (id,name,holdable,opac_visible) VALUES (5,oils_i18n_gettext(5, 'In process', 'ccs', 'name'),'t','t');
 INSERT INTO config.copy_status (id,name,holdable,opac_visible) VALUES (6,oils_i18n_gettext(6, 'In transit', 'ccs', 'name'),'t','t');
-INSERT INTO config.copy_status (id,name,holdable,opac_visible) VALUES (7,oils_i18n_gettext(7, 'Reshelving', 'ccs', 'name'),'t','t');
-INSERT INTO config.copy_status (id,name,holdable,opac_visible) VALUES (8,oils_i18n_gettext(8, 'On holds shelf', 'ccs', 'name'),'t','t');
+INSERT INTO config.copy_status (id,name,holdable,opac_visible,copy_active) VALUES (7,oils_i18n_gettext(7, 'Reshelving', 'ccs', 'name'),'t','t','t');
+INSERT INTO config.copy_status (id,name,holdable,opac_visible,copy_active) VALUES (8,oils_i18n_gettext(8, 'On holds shelf', 'ccs', 'name'),'t','t','t');
 INSERT INTO config.copy_status (id,name,holdable,opac_visible) VALUES (9,oils_i18n_gettext(9, 'On order', 'ccs', 'name'),'t','t');
-INSERT INTO config.copy_status (id,name) VALUES (10,oils_i18n_gettext(10, 'ILL', 'ccs', 'name'));
+INSERT INTO config.copy_status (id,name,copy_active) VALUES (10,oils_i18n_gettext(10, 'ILL', 'ccs', 'name'),'t');
 INSERT INTO config.copy_status (id,name) VALUES (11,oils_i18n_gettext(11, 'Cataloging', 'ccs', 'name'));
-INSERT INTO config.copy_status (id,name,opac_visible) VALUES (12,oils_i18n_gettext(12, 'Reserves', 'ccs', 'name'),'t');
+INSERT INTO config.copy_status (id,name,opac_visible,copy_active) VALUES (12,oils_i18n_gettext(12, 'Reserves', 'ccs', 'name'),'t','t');
 INSERT INTO config.copy_status (id,name) VALUES (13,oils_i18n_gettext(13, 'Discard/Weed', 'ccs', 'name'));
 INSERT INTO config.copy_status (id,name) VALUES (14,oils_i18n_gettext(14, 'Damaged', 'ccs', 'name'));
-INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
+INSERT INTO config.copy_status (id,name,copy_active) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'),'t');
 
 SELECT SETVAL('config.copy_status_id_seq'::TEXT, 100);
 
@@ -2278,20 +2278,20 @@ INSERT INTO asset.call_number VALUES (-1,1,NOW(),1,NOW(),-1,1,'UNCATALOGED');
 -- circ matrix
 INSERT INTO config.circ_matrix_matchpoint (org_unit,grp,circulate,duration_rule,recurring_fine_rule,max_fine_rule) VALUES (1,1,true,11,1,1);
 
-INSERT INTO config.circ_matrix_weights(name, org_unit, grp, circ_modifier, marc_type, marc_form, marc_bib_level, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_upper_bound, usr_age_lower_bound) VALUES 
-    ('Default', 10.0, 11.0, 5.0, 4.0, 3.0, 2.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
-    ('Org_Unit_First', 11.0, 10.0, 5.0, 4.0, 3.0, 2.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
-    ('Item_Owner_First', 8.0, 8.0, 5.0, 4.0, 3.0, 2.0, 2.0, 10.0, 11.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
-    ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+INSERT INTO config.circ_matrix_weights(name, org_unit, grp, circ_modifier, marc_type, marc_form, marc_bib_level, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_upper_bound, usr_age_lower_bound, item_age) VALUES 
+    ('Default', 10.0, 11.0, 5.0, 4.0, 3.0, 2.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0, 0.0),
+    ('Org_Unit_First', 11.0, 10.0, 5.0, 4.0, 3.0, 2.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0, 0.0),
+    ('Item_Owner_First', 8.0, 8.0, 5.0, 4.0, 3.0, 2.0, 2.0, 10.0, 11.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0, 0.0),
+    ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
 
 -- hold matrix - 110.hold_matrix.sql:
 INSERT INTO config.hold_matrix_matchpoint (requestor_grp) VALUES (1);
 
-INSERT INTO config.hold_matrix_weights(name, user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_bib_level, marc_vr_format, juvenile_flag, ref_flag) VALUES
-    ('Default', 5.0, 5.0, 5.0, 5.0, 5.0, 7.0, 8.0, 4.0, 3.0, 2.0, 1.0, 1.0, 4.0, 0.0),
-    ('Item_Owner_First', 5.0, 5.0, 5.0, 8.0, 7.0, 5.0, 5.0, 4.0, 3.0, 2.0, 1.0, 1.0, 4.0, 0.0),
-    ('User_Before_Requestor', 5.0, 5.0, 5.0, 5.0, 5.0, 8.0, 7.0, 4.0, 3.0, 2.0, 1.0, 1.0, 4.0, 0.0),
-    ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+INSERT INTO config.hold_matrix_weights(name, user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_bib_level, marc_vr_format, juvenile_flag, ref_flag, item_age) VALUES
+    ('Default', 5.0, 5.0, 5.0, 5.0, 5.0, 7.0, 8.0, 4.0, 3.0, 2.0, 1.0, 1.0, 4.0, 0.0, 0.0),
+    ('Item_Owner_First', 5.0, 5.0, 5.0, 8.0, 7.0, 5.0, 5.0, 4.0, 3.0, 2.0, 1.0, 1.0, 4.0, 0.0, 0.0),
+    ('User_Before_Requestor', 5.0, 5.0, 5.0, 5.0, 5.0, 8.0, 7.0, 4.0, 3.0, 2.0, 1.0, 1.0, 4.0, 0.0, 0.0),
+    ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
 
 -- dynamic weight associations
 INSERT INTO config.weight_assoc(active, org_unit, circ_weights, hold_weights) VALUES
@@ -2427,6 +2427,11 @@ INSERT into config.org_unit_setting_type
     oils_i18n_gettext('circ.holds.min_estimated_wait_interval', 'When predicting the amount of time a patron will be waiting for a hold to be fulfilled, this is the minimum estimated length of time to assume an item will be checked out. Examples: "2 weeks", "5 days"', 'coust', 'description'),
     'interval'),
 
+( 'circ.holds.age_protect.active_date',
+    oils_i18n_gettext('circ.holds.age_protect.active_date', 'Holds: Use Active Date for Age Protection', 'coust', 'label'),
+    oils_i18n_gettext('circ.holds.age_protect.active_date', 'When calculating age protection rules use the active date instead of the creation date.', 'coust', 'description'),
+    'bool'),
+
 ( 'circ.selfcheck.patron_login_timeout',
     oils_i18n_gettext('circ.selfcheck.patron_login_timeout', 'Selfcheck: Patron Login Timeout (in seconds)', 'coust', 'label'),
     oils_i18n_gettext('circ.selfcheck.patron_login_timeout', 'Number of seconds of inactivity before the patron is logged out of the selfcheck interface', 'coust', 'description'),
diff --git a/Open-ILS/web/conify/global/config/copy_status.html b/Open-ILS/web/conify/global/config/copy_status.html
index 943fd24..262eaf2 100644
--- a/Open-ILS/web/conify/global/config/copy_status.html
+++ b/Open-ILS/web/conify/global/config/copy_status.html
@@ -82,6 +82,8 @@
                 if (attr == 'opac_visible' && typeof n != 'string')
                     this.setValue(item, 'opac_visible', n ? 't' : 'f');
 
+                if (attr == 'copy_active' && typeof n != 'string')
+                    this.setValue(item, 'copy_active', n ? 't' : 'f');
             };
 
             dojo.addOnUnload( function (event) {
@@ -212,6 +214,18 @@
 												return false;
 											}
 										  }
+										},
+										{ name : ccs_strings.COPY_ACTIVE,
+										  field : "copy_active",
+										  editor : dojox.grid.editors.bool,
+										  get : function (row) {
+											var r = window.status_data_model.getRow(row);
+											if (r) {
+												var h = r.copy_active;
+												if (h == 't' || h === true) return true;
+												return false;
+											}
+										  }
 										}
 									]
 								]
diff --git a/Open-ILS/web/js/dojo/openils/conify/nls/conify.js b/Open-ILS/web/js/dojo/openils/conify/nls/conify.js
index 363d21d..c482bb6 100644
--- a/Open-ILS/web/js/dojo/openils/conify/nls/conify.js
+++ b/Open-ILS/web/js/dojo/openils/conify/nls/conify.js
@@ -6,6 +6,7 @@
 	"CONFIRM_EXIT_PGT": "There are unsaved modified permission maps. Click OK to save these changes, or Cancel to abandon them.",
 	"CONFIRM_EXIT_PPL": "There are unsaved modified permissions. Click OK to save these changes, or Cancel to abandon them.",
 	"CONFIRM_UNSAVED_CHANGES": "There are unsaved changes to one or more organization types. Click OK to save these changes, or Cancel to abandon them.",
+	"COPY_ACTIVE": "Sets copy active",
 	"ERROR_CALLING_METHOD_AOUT": "Problem calling method to create child organization type",
 	"ERROR_CALLING_METHOD_CAM": "Problem calling method to create new ${0}",
 	"ERROR_CALLING_METHOD_CCS": "Problem calling method to create new copy status",
diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd
index 893059a..67c73b2 100644
--- a/Open-ILS/web/opac/locale/en-US/lang.dtd
+++ b/Open-ILS/web/opac/locale/en-US/lang.dtd
@@ -246,6 +246,7 @@
 <!ENTITY staff.browse_list.circulate "Circulate">
 <!ENTITY staff.browse_list.copy_number "Copy Number">
 <!ENTITY staff.browse_list.create_date "Creation Date">
+<!ENTITY staff.browse_list.active_date "Active Date">
 <!ENTITY staff.browse_list.creator "Creator">
 <!ENTITY staff.browse_list.deposit "Deposit">
 <!ENTITY staff.browse_list.deposit_amount "Deposit Amount">
@@ -2687,6 +2688,7 @@
 <!ENTITY staff.cat.copy_summary.created.label "Created:">
 <!ENTITY staff.cat.copy_summary.edited.label "Edited:">
 <!ENTITY staff.cat.copy_summary.age_protect.label "Age Protect:">
+<!ENTITY staff.cat.copy_summary.active_date.label "Active Date:">
 <!ENTITY staff.cat.copy_summary.total_circs.label "Total Circulations:">
 <!ENTITY staff.cat.copy_summary.alternate_view.label "Alternate View">
 <!ENTITY staff.cat.copy_summary.alternate_view.accesskey "">
@@ -3487,6 +3489,7 @@
 <!ENTITY staff.circ.alternate_copy_summary.Copy_Location.label "Copy Location">
 <!ENTITY staff.circ.alternate_copy_summary.Renewal_Type.label "Renewal Type">
 <!ENTITY staff.circ.alternate_copy_summary.Date_Created.label "Date Created">
+<!ENTITY staff.circ.alternate_copy_summary.Date_Active.label "Date Active">
 <!ENTITY staff.circ.alternate_copy_summary.Status_Changed_Time.label "Status Changed">
 <!ENTITY staff.circ.alternate_copy_summary.Due_Date.label "Due Date">
 <!ENTITY staff.circ.alternate_copy_summary.Edition.label "Edition">
diff --git a/Open-ILS/web/opac/locale/en-US/opac.dtd b/Open-ILS/web/opac/locale/en-US/opac.dtd
index 8203ce4..cac50e0 100644
--- a/Open-ILS/web/opac/locale/en-US/opac.dtd
+++ b/Open-ILS/web/opac/locale/en-US/opac.dtd
@@ -494,6 +494,7 @@ Please see a librarian to renew your account.">
 <!ENTITY rdetail.cn.location "Location">
 <!ENTITY rdetail.cn.hold.age "Age Hold Protection">
 <!ENTITY rdetail.cn.genesis "Create Date">
+<!ENTITY rdetail.cn.active "Active Date">
 <!ENTITY rdetail.cn.holdable "Holdable">
 <!ENTITY rdetail.cn.due "Due Date">
 <!ENTITY rdetail.cn.more "more info...">
diff --git a/Open-ILS/web/opac/skin/craftsman/xml/rdetail/rdetail_cn_details.xml b/Open-ILS/web/opac/skin/craftsman/xml/rdetail/rdetail_cn_details.xml
index 116f1fb..6dc5120 100644
--- a/Open-ILS/web/opac/skin/craftsman/xml/rdetail/rdetail_cn_details.xml
+++ b/Open-ILS/web/opac/skin/craftsman/xml/rdetail/rdetail_cn_details.xml
@@ -15,6 +15,7 @@
 									<td>&rdetail.cn.location;</td>
 									<td name='age_protect_label' class='hide_me'>&rdetail.cn.hold.age;</td>
 									<td name='create_date_label' class='hide_me'>&rdetail.cn.genesis;</td>
+                                    <td name='active_date_label' class='hide_me'>&rdetail.cn.active;</td>
 									<td name='holdable_label' class='hide_me'>&rdetail.cn.holdable;</td>
 									<td name='due_date_label' class='hide_me'>&rdetail.cn.due;</td>
 								</tr>
@@ -33,6 +34,7 @@
 									<td name='location'> </td>
 									<td name='age_protect_value' class='hide_me'>&rdetail.cn.disabled;</td>
 									<td name='create_date_value' class='hide_me'> </td>
+                                    <td name='active_date_value' class='hide_me'> </td>
 	
 									<td name='copy_holdable_td' class='hide_me'>
 										<span name='copy_is_holdable'> </span>
diff --git a/Open-ILS/web/opac/skin/default/js/copy_details.js b/Open-ILS/web/opac/skin/default/js/copy_details.js
index c4c623a..253e671 100644
--- a/Open-ILS/web/opac/skin/default/js/copy_details.js
+++ b/Open-ILS/web/opac/skin/default/js/copy_details.js
@@ -31,6 +31,7 @@ function cpdBuild( contextTbody, contextRow, record, callnumber, orgid, depth, c
 		/* unhide before we unhide/clone the parent */
 		unHideMe($n(templateRow, 'age_protect_label'));
 		unHideMe($n(templateRow, 'create_date_label'));
+        unHideMe($n(templateRow, 'active_date_label'));
 		unHideMe($n(templateRow, 'holdable_label'));
 	}
 
@@ -205,6 +206,7 @@ function cpdDrawCopies(r) {
 		/* unhide before we unhide/clone the parent */
 		unHideMe($n(copyrow, 'age_protect_value'));
 		unHideMe($n(copyrow, 'create_date_value'));
+        unHideMe($n(copyrow, 'active_date_value'));
 		unHideMe($n(copyrow, 'copy_holdable_td'));
 	}
 
@@ -343,6 +345,12 @@ function cpdDrawCopy(r) {
 		cd = cd.replace(/T.*/, '');
 		$n(row, 'create_date_value').appendChild(text(cd));
 
+        var ad = copy.active_date();
+        if(ad) {
+            ad = ad.replace(/T.*/, '');
+            $n(row, 'active_date_value').appendChild(text(ad));
+        }
+
 		var yes = $('rdetail.yes').innerHTML;
 		var no = $('rdetail.no').innerHTML;
 
diff --git a/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_cn_details.xml b/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_cn_details.xml
index f937089..d443b9f 100644
--- a/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_cn_details.xml
+++ b/Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_cn_details.xml
@@ -16,6 +16,7 @@
 								<td name='copy_part_label' class='hide_me'>&rdetail.cn.part;</td>
 								<td name='age_protect_label' class='hide_me'>&rdetail.cn.hold.age;</td>
 								<td name='create_date_label' class='hide_me'>&rdetail.cn.genesis;</td>
+                                <td name='active_date_label' class='hide_me'>&rdetail.cn.active;</td>
 								<td name='holdable_label' class='hide_me'>&rdetail.cn.holdable;</td>
 								<td name='due_date_label' class='hide_me'>&rdetail.cn.due;</td>
 							</tr>
@@ -41,6 +42,7 @@
 								<td name='copy_part' class='hide_me'> </td>
 								<td name='age_protect_value' class='hide_me'>&rdetail.cn.disabled;</td>
 								<td name='create_date_value' class='hide_me'> </td>
+                                <td name='active_date_value' class='hide_me'> </td>
 
 								<td name='copy_holdable_td' class='hide_me'>
 									<span name='copy_is_holdable'> </span>
diff --git a/Open-ILS/web/templates/default/conify/global/config/circ_matrix_matchpoint.tt2 b/Open-ILS/web/templates/default/conify/global/config/circ_matrix_matchpoint.tt2
index bd19e7e..e3725a2 100644
--- a/Open-ILS/web/templates/default/conify/global/config/circ_matrix_matchpoint.tt2
+++ b/Open-ILS/web/templates/default/conify/global/config/circ_matrix_matchpoint.tt2
@@ -9,7 +9,7 @@
     <table  jsId="cmGrid"
             style="height: 600px;"
             dojoType="openils.widget.AutoGrid"
-            fieldOrder="['id', 'active', 'grp', 'org_unit', 'copy_circ_lib', 'copy_owning_lib', 'user_home_ou', 'is_renewal', 'juvenile_flag', 'circ_modifier', 'marc_type', 'marc_form', 'marc_bib_level', 'marc_vr_format', 'ref_flag', 'usr_age_lower_bound', 'usr_age_upper_bound', 'circulate', 'duration_rule', 'renewals', 'hard_due_date', 'recurring_fine_rule', 'grace_period', 'max_fine_rule', 'available_copy_hold_ratio', 'total_copy_hold_ratio', 'script_test']"
+            fieldOrder="['id', 'active', 'grp', 'org_unit', 'copy_circ_lib', 'copy_owning_lib', 'user_home_ou', 'is_renewal', 'juvenile_flag', 'circ_modifier', 'marc_type', 'marc_form', 'marc_bib_level', 'marc_vr_format', 'ref_flag', 'usr_age_lower_bound', 'usr_age_upper_bound', 'item_age', 'circulate', 'duration_rule', 'renewals', 'hard_due_date', 'recurring_fine_rule', 'grace_period', 'max_fine_rule', 'available_copy_hold_ratio', 'total_copy_hold_ratio', 'script_test']"
             defaultCellWidth='"auto"'
             query="{id: '*'}"
             fmClass='ccmm'
diff --git a/Open-ILS/xul/staff_client/server/cat/copy_editor.js b/Open-ILS/xul/staff_client/server/cat/copy_editor.js
index 5512b36..3991283 100644
--- a/Open-ILS/xul/staff_client/server/cat/copy_editor.js
+++ b/Open-ILS/xul/staff_client/server/cat/copy_editor.js
@@ -866,6 +866,12 @@ g.panes_and_field_names = {
         }
     ],
     [
+        $('catStrings').getString('staff.cat.copy_editor.field.active_date.label'),
+        { 
+            render: 'util.date.formatted_date( fm.active_date(), "%F");',
+        }
+    ],
+    [
         $('catStrings').getString('staff.cat.copy_editor.field.creator.label'),
         { 
             render: 'fm.creator();',
diff --git a/Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.js b/Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.js
index bfb015c..af6d1c0 100644
--- a/Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.js
+++ b/Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.js
@@ -135,6 +135,7 @@ function load_item() {
         set("floating", '');
         set("copy_number", '');
         set("copy_create_date", '');
+        set("copy_active_date", '');
         set("status_changed_time", '');
         set("copy_creator", '');
         set("deleted", '');
@@ -197,6 +198,7 @@ function load_item() {
             set("floating", get_localized_bool( details.copy.floating() )); 
             set("copy_number", details.copy.copy_number()); 
             set("copy_create_date", util.date.formatted_date( details.copy.create_date(), '%{localized}' )); 
+            set("copy_active_date", util.date.formatted_date( details.copy.active_date(), '%{localized}' ));
             set("status_changed_time", util.date.formatted_date( details.copy.status_changed_time(), '%{localized}' )); 
             set("copy_creator", details.copy.creator()); 
             set("deleted", details.copy.deleted()); 
diff --git a/Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.xul b/Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.xul
index 2b21d65..2091b43 100644
--- a/Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.xul
+++ b/Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.xul
@@ -119,8 +119,8 @@
                                 <textbox name="checkout_workstation" readonly="true" context="clipboard"/>
                             </row>
                             <row>
-                                <label value="&staff.circ.alternate_copy_summary.Status_Changed_Time.label;" />
-                                <textbox name="status_changed_time" readonly="true" context="clipboard"/>
+                                <label value="&staff.circ.alternate_copy_summary.Date_Active.label;" />
+                                <textbox name="copy_active_date" readonly="true" context="clipboard"/>
                                 <label value="&staff.circ.alternate_copy_summary.Fine_Level.label;" />
                                 <textbox name="fine_level" readonly="true" context="clipboard"/>
                                 <label value="&staff.circ.alternate_copy_summary.Total_Circs___Prev_Year.label;" />
@@ -129,8 +129,8 @@
                                 <textbox name="duration_rule" readonly="true" context="clipboard"/>
                             </row>
                             <row>
-                                <label value="&staff.circ.alternate_copy_summary.Copy_ID.label;" />
-                                <textbox name="copy_id" readonly="true" context="clipboard"/>
+                                <label value="&staff.circ.alternate_copy_summary.Status_Changed_Time.label;" />
+                                <textbox name="status_changed_time" readonly="true" context="clipboard"/>
                                 <label value="&staff.circ.alternate_copy_summary.Reference.label;" />
                                 <textbox name="ref" readonly="true" context="clipboard"/>
                                 <!--
@@ -143,8 +143,8 @@
                                 <textbox name="recurring_fine_rule" readonly="true" context="clipboard"/>
                             </row>
                             <row>
-                                <label value="&staff.circ.alternate_copy_summary.TCN.label;" />
-                                <textbox name="tcn" readonly="true" context="clipboard"/>
+                                <label value="&staff.circ.alternate_copy_summary.Copy_ID.label;" />
+                                <textbox name="copy_id" readonly="true" context="clipboard"/>
                                 <label value="&staff.circ.alternate_copy_summary.OPAC_Visible.label;" />
                                 <textbox name="opac_visible" readonly="true" context="clipboard"/>
                                 <label value="&staff.circ.alternate_copy_summary.Remaining_Renewals.label;" />
@@ -153,8 +153,8 @@
                                 <textbox name="max_fine_rule" readonly="true" context="clipboard"/>
                             </row>
                             <row>
-                                <label value="&staff.circ.alternate_copy_summary.Floating.label;" />
-                                <textbox name="floating" readonly="true" context="clipboard"/>
+                                <label value="&staff.circ.alternate_copy_summary.TCN.label;" />
+                                <textbox name="tcn" readonly="true" context="clipboard"/>
                                 <label value="&staff.circ.alternate_copy_summary.Holdable.label;" />
                                 <textbox name="holdable" readonly="true" context="clipboard"/>
                                 <spacer /><spacer />
@@ -162,7 +162,8 @@
                                 <textbox name="checkin_time" readonly="true" context="clipboard"/>
                             </row>
                             <row>
-                                <spacer /><spacer />
+                                <label value="&staff.circ.alternate_copy_summary.Floating.label;" />
+                                <textbox name="floating" readonly="true" context="clipboard"/>
                                 <label value="&staff.circ.alternate_copy_summary.Circulate.label;" />
                                 <textbox name="circulate" readonly="true" context="clipboard"/>
                                 <!--
diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties b/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
index b2d4373..0287758 100644
--- a/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
+++ b/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
@@ -166,6 +166,7 @@ staff.cat.copy_editor.field.creator.label=Creator
 staff.cat.copy_editor.field.last_editor.label=Last Editor
 staff.cat.copy_editor.field.barcode.label=Barcode
 staff.cat.copy_editor.field.creation_date.label=Creation Date
+staff.cat.copy_editor.field.active_date.label=Active Date
 staff.cat.copy_editor.field.last_edit_date.label=Last Edit Date
 staff.cat.copy_editor.field.location.label=Location/Collection
 staff.cat.copy_editor.field.circulation_library.label=Circulation Library

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

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |    7 +
 .../lib/OpenILS/Application/Storage/CDBI/asset.pm  |    2 +-
 .../lib/OpenILS/Application/Storage/CDBI/config.pm |    2 +-
 .../lib/OpenILS/Application/Storage/CDBI/serial.pm |    2 +-
 .../src/perlmods/lib/OpenILS/Utils/PermitHold.pm   |   22 +-
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    5 +-
 Open-ILS/src/sql/Pg/040.schema.asset.sql           |   22 +
 Open-ILS/src/sql/Pg/099.matrix_weights.sql         |    6 +-
 Open-ILS/src/sql/Pg/100.circ_matrix.sql            |   15 +-
 Open-ILS/src/sql/Pg/110.hold_matrix.sql            |   31 +-
 Open-ILS/src/sql/Pg/210.schema.serials.sql         |    5 +
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |   39 +-
 .../Pg/upgrade/0562.schema.copy_active_date.sql    |  625 ++++++++++++++++++++
 Open-ILS/web/conify/global/config/copy_status.html |   14 +
 Open-ILS/web/js/dojo/openils/conify/nls/conify.js  |    1 +
 Open-ILS/web/opac/locale/en-US/lang.dtd            |    3 +
 Open-ILS/web/opac/locale/en-US/opac.dtd            |    1 +
 .../craftsman/xml/rdetail/rdetail_cn_details.xml   |    2 +
 Open-ILS/web/opac/skin/default/js/copy_details.js  |    8 +
 .../default/xml/rdetail/rdetail_cn_details.xml     |    2 +
 .../global/config/circ_matrix_matchpoint.tt2       |    2 +-
 .../xul/staff_client/server/cat/copy_editor.js     |    6 +
 .../server/circ/alternate_copy_summary.js          |    2 +
 .../server/circ/alternate_copy_summary.xul         |   19 +-
 .../server/locale/en-US/cat.properties             |    1 +
 25 files changed, 795 insertions(+), 49 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0562.schema.copy_active_date.sql


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list