[open-ils-commits] [GIT] Evergreen ILS branch master updated. cc0ef7448c8a92f1715dd1726aead4409a8b0e08
Evergreen Git
git at git.evergreen-ils.org
Fri Jun 7 15:35:14 EDT 2013
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".
The branch, master has been updated
via cc0ef7448c8a92f1715dd1726aead4409a8b0e08 (commit)
via eb4fc15d55d1a74a9fafe24a033955a6a757a2c6 (commit)
via 246fff2d71f640954194f999af0a59f06a416e9a (commit)
via dc40c1c748cfbb0c4b14d94b5fe72825cb110a08 (commit)
via 765aea78207f879e81eeb9f3d7614699399e585f (commit)
via d43e92f682f6e06a61d508944cf340093168e77a (commit)
via 86995bbb791f3ba0771e92921f756f6680a22571 (commit)
via 0771f7c6ea249a336ec7e68bd6e1949311ce3856 (commit)
via 1523a8b6c8512cac58d8ecbe5a5456265eb6fcf3 (commit)
from 288c1d1c5f23e991bbaf74c42ed4fba7ef5c1151 (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 cc0ef7448c8a92f1715dd1726aead4409a8b0e08
Author: Dan Wells <dbw2 at calvin.edu>
Date: Fri Jun 7 15:31:37 2013 -0400
Stamping upgrade script for purge holds functionality
Signed-off-by: Dan Wells <dbw2 at calvin.edu>
diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 769ad25..e7f467f 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -91,7 +91,7 @@ CREATE TRIGGER no_overlapping_deps
BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0796', :eg_version); -- berick/dbwells
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0797', :eg_version); -- tsbere/Dyrcona/dbwells
CREATE TABLE config.bib_source (
id SERIAL PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.action.purge_holds.sql b/Open-ILS/src/sql/Pg/upgrade/0797.schema.action.purge_holds.sql
similarity index 99%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.schema.action.purge_holds.sql
rename to Open-ILS/src/sql/Pg/upgrade/0797.schema.action.purge_holds.sql
index 5389856..dc50c92 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.action.purge_holds.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0797.schema.action.purge_holds.sql
@@ -1,3 +1,6 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0797', :eg_version); -- tsbere/Dyrcona/dbwells
-- New global flags for the purge function
INSERT INTO config.global_flag (name, label, enabled)
@@ -366,3 +369,4 @@ CREATE TRIGGER action_hold_request_aging_tgr
FOR EACH ROW
EXECUTE PROCEDURE action.age_hold_on_delete ();
+COMMIT;
commit eb4fc15d55d1a74a9fafe24a033955a6a757a2c6
Author: Jason Stephenson <jason at sigio.com>
Date: Wed Apr 10 17:29:28 2013 -0400
Update the purge_holds.txt release notes.
Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
Signed-off-by: Dan Wells <dbw2 at calvin.edu>
diff --git a/docs/RELEASE_NOTES_NEXT/purge_holds.txt b/docs/RELEASE_NOTES_NEXT/purge_holds.txt
index f015f6d..75d2371 100644
--- a/docs/RELEASE_NOTES_NEXT/purge_holds.txt
+++ b/docs/RELEASE_NOTES_NEXT/purge_holds.txt
@@ -2,6 +2,8 @@ New Feature: "Purge Holds"
==========================
Similar to purging circulations one may wish to purge old (filled or canceled) hold information. This feature adds a database function and settings for doing so.
+Purged holds are moved to the action.aged_hold_request table with patron identifying information scrubbed, much like circulations are moved to action.aged_circulation.
+
The settings allow for a default retention age as well as filled, canceled, and canceled by cancel cause ages. The most specific one wins unless a patron is retaining their hold history. In the latter case the patron's holds are retained either way.
-Note that the function still needs to be called, which could be set up as a cron job or done more manually, say after statistics collection.
+Note that the function still needs to be called, which could be set up as a cron job or done more manually, say after statistics collection. A new script, purge_holds.srfsh, is added that can be used to purge holds from cron.
commit 246fff2d71f640954194f999af0a59f06a416e9a
Author: Jason Stephenson <jstephenson at mvlc.org>
Date: Thu Apr 4 11:30:25 2013 -0400
Add table, view and trigger for "aging" hold requests on delete.
This creates the action.aged_hold_request table, the
action.all_hold_request view, the action.age_hold_on_delete
function, and the action_hold_request_aging_tgr on action.hold_request.
Add fieldmapper entries for action.all_hold_request view and
action.aged_hold_request table.
Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
Signed-off-by: Dan Wells <dbw2 at calvin.edu>
diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index ec6525e..b4614ee 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -5421,6 +5421,123 @@ SELECT usr,
</actions>
</permacrud>
</class>
+ <class id="combahr" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action::all_hold_request" oils_persist:tablename="action.all_hold_request" reporter:core="true" reporter:label="Combined (Active & Aged) Hold Request">
+ <fields oils_persist:primary="id" oils_persist:sequence="action.hold_request_id_seq">
+ <field reporter:label="Status" name="status" oils_persist:virtual="true" />
+ <field reporter:label="Capture Date/Time" name="capture_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Currently Targeted Copy" name="current_copy" />
+ <field reporter:label="Notify by Email?" name="email_notify" reporter:datatype="bool"/>
+ <field reporter:label="Hold Expire Date/Time" name="expire_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Fulfilling Library" name="fulfillment_lib" reporter:datatype="org_unit"/>
+ <field reporter:label="Fulfilling Staff" name="fulfillment_staff" />
+ <field reporter:label="Fulfillment Date/Time" name="fulfillment_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Hold Type" name="hold_type" reporter:datatype="text"/>
+ <field reporter:label="Holdable Formats (for M-type hold)" name="holdable_formats" reporter:datatype="text"/>
+ <field reporter:label="Hold ID" name="id" reporter:datatype="id" />
+ <field reporter:label="Notify by Phone?" name="phone_notify" reporter:datatype="bool"/>
+ <field reporter:label="Notify by SMS?" name="sms_notify" reporter:datatype="bool"/>
+ <field reporter:label="Pickup Library" name="pickup_lib" reporter:datatype="org_unit"/>
+ <field reporter:label="Last Targeting Date/Time" name="prev_check_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Requesting Library" name="request_lib" reporter:datatype="org_unit"/>
+ <field reporter:label="Request Date/Time" name="request_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Patron ZIP" name="usr_post_code" reporter:datatype="text"/>
+ <field reporter:label="Patron Home Library" name="usr_home_ou" reporter:datatype="link"/>
+ <field reporter:label="Patron Profile Group" name="usr_profile" reporter:datatype="link"/>
+ <field reporter:label="Patron Birth Year" name="usr_birth_year" reporter:datatype="int"/>
+ <field reporter:label="Staff Placed?" name="staff_placed" reporter:datatype="bool"/>
+ <field reporter:label="Item Selection Depth" name="selection_depth" />
+ <field reporter:label="Selection Locus" name="selection_ou" reporter:datatype="org_unit"/>
+ <field reporter:label="Target Object ID" name="target" reporter:datatype="link"/>
+ <field reporter:label="Hold Cancel Date/Time" name="cancel_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Bib Record link" name="bib_rec" oils_persist:virtual="true" reporter:datatype="link"/>
+ <field reporter:label="Currently Frozen" name="frozen" reporter:datatype="bool"/>
+ <field reporter:label="Thaw Date (if frozen)" name="thaw_date" reporter:datatype="timestamp"/>
+ <field reporter:label="Shelf Time" name="shelf_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Cancelation cause" name="cancel_cause" reporter:datatype="link" />
+ <field reporter:label="Cancelation note" name="cancel_note" reporter:datatype="text" />
+ <field reporter:label="Top of Queue" name="cut_in_line" reporter:datatype="bool" />
+ <field reporter:label="Is Mint Condition" name="mint_condition" reporter:datatype="bool" />
+ <field reporter:label="Shelf Expire Time" name="shelf_expire_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Current Shelf Lib" name="current_shelf_lib" reporter:datatype="org_unit"/>
+ </fields>
+ <links>
+ <link field="fulfillment_lib" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="fulfillment_staff" reltype="has_a" key="id" map="" class="au"/>
+ <link field="pickup_lib" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="selection_ou" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="current_copy" reltype="has_a" key="id" map="" class="acp"/>
+ <link field="request_lib" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="usr_home_ou" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="bib_rec" reltype="might_have" key="id" map="" class="rhrr"/>
+ <link field="cancel_cause" reltype="might_have" key="id" map="" class="ahrcc"/>
+ <link field="current_shelf_lib" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="usr_profile" reltype="has_a" key="id" map="" class="pgt"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <retrieve permission="VIEW_HOLD" context_field="pickup_lib" />
+ </actions>
+ </permacrud>
+ </class>
+ <class id="aahr" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action::aged_hold_request" oils_persist:tablename="action.aged_hold_request" reporter:core="true" reporter:label="Aged Hold Request">
+ <fields oils_persist:primary="id" oils_persist:sequence="action.hold_request_id_seq">
+ <field reporter:label="Status" name="status" oils_persist:virtual="true" />
+ <field reporter:label="Capture Date/Time" name="capture_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Currently Targeted Copy" name="current_copy" />
+ <field reporter:label="Notify by Email?" name="email_notify" reporter:datatype="bool"/>
+ <field reporter:label="Hold Expire Date/Time" name="expire_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Fulfilling Library" name="fulfillment_lib" reporter:datatype="org_unit"/>
+ <field reporter:label="Fulfilling Staff" name="fulfillment_staff" />
+ <field reporter:label="Fulfillment Date/Time" name="fulfillment_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Hold Type" name="hold_type" reporter:datatype="text"/>
+ <field reporter:label="Holdable Formats (for M-type hold)" name="holdable_formats" reporter:datatype="text"/>
+ <field reporter:label="Hold ID" name="id" reporter:datatype="id" />
+ <field reporter:label="Notify by Phone?" name="phone_notify" reporter:datatype="bool"/>
+ <field reporter:label="Notify by SMS?" name="sms_notify" reporter:datatype="bool"/>
+ <field reporter:label="Pickup Library" name="pickup_lib" reporter:datatype="org_unit"/>
+ <field reporter:label="Last Targeting Date/Time" name="prev_check_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Requesting Library" name="request_lib" reporter:datatype="org_unit"/>
+ <field reporter:label="Request Date/Time" name="request_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Patron ZIP" name="usr_post_code" reporter:datatype="text"/>
+ <field reporter:label="Patron Home Library" name="usr_home_ou" reporter:datatype="link"/>
+ <field reporter:label="Patron Profile Group" name="usr_profile" reporter:datatype="link"/>
+ <field reporter:label="Patron Birth Year" name="usr_birth_year" reporter:datatype="int"/>
+ <field reporter:label="Staff Placed?" name="staff_placed" reporter:datatype="bool"/>
+ <field reporter:label="Item Selection Depth" name="selection_depth" />
+ <field reporter:label="Selection Locus" name="selection_ou" reporter:datatype="org_unit"/>
+ <field reporter:label="Target Object ID" name="target" reporter:datatype="link"/>
+ <field reporter:label="Hold Cancel Date/Time" name="cancel_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Bib Record link" name="bib_rec" oils_persist:virtual="true" reporter:datatype="link"/>
+ <field reporter:label="Currently Frozen" name="frozen" reporter:datatype="bool"/>
+ <field reporter:label="Thaw Date (if frozen)" name="thaw_date" reporter:datatype="timestamp"/>
+ <field reporter:label="Shelf Time" name="shelf_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Cancelation cause" name="cancel_cause" reporter:datatype="link" />
+ <field reporter:label="Cancelation note" name="cancel_note" reporter:datatype="text" />
+ <field reporter:label="Top of Queue" name="cut_in_line" reporter:datatype="bool" />
+ <field reporter:label="Is Mint Condition" name="mint_condition" reporter:datatype="bool" />
+ <field reporter:label="Shelf Expire Time" name="shelf_expire_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Current Shelf Lib" name="current_shelf_lib" reporter:datatype="org_unit"/>
+ </fields>
+ <links>
+ <link field="fulfillment_lib" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="fulfillment_staff" reltype="has_a" key="id" map="" class="au"/>
+ <link field="pickup_lib" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="selection_ou" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="current_copy" reltype="has_a" key="id" map="" class="acp"/>
+ <link field="request_lib" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="usr_home_ou" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="bib_rec" reltype="might_have" key="id" map="" class="rhrr"/>
+ <link field="cancel_cause" reltype="might_have" key="id" map="" class="ahrcc"/>
+ <link field="current_shelf_lib" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="usr_profile" reltype="has_a" key="id" map="" class="pgt"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <retrieve permission="VIEW_HOLD" context_field="pickup_lib" />
+ </actions>
+ </permacrud>
+ </class>
+
<class id="aou" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::org_unit" oils_persist:tablename="actor.org_unit" reporter:label="Organizational Unit" oils_persist:field_safe="true">
<fields oils_persist:primary="id" oils_persist:sequence="actor.org_unit_id_seq">
<field reporter:label="Subordinate Organizational Units" name="children" oils_persist:virtual="true" reporter:datatype="org_unit"/>
diff --git a/Open-ILS/src/sql/Pg/090.schema.action.sql b/Open-ILS/src/sql/Pg/090.schema.action.sql
index d07f07f..bb5c66d 100644
--- a/Open-ILS/src/sql/Pg/090.schema.action.sql
+++ b/Open-ILS/src/sql/Pg/090.schema.action.sql
@@ -524,6 +524,210 @@ CREATE VIEW action.unfulfilled_hold_max_loop AS
GROUP BY 1;
+CREATE TABLE action.aged_hold_request (
+ usr_post_code TEXT,
+ usr_home_ou INT NOT NULL,
+ usr_profile INT NOT NULL,
+ usr_birth_year INT,
+ staff_placed BOOLEAN NOT NULL,
+ LIKE action.hold_request
+);
+ALTER TABLE action.aged_hold_request
+ ADD PRIMARY KEY (id),
+ DROP COLUMN usr,
+ DROP COLUMN requestor,
+ DROP COLUMN sms_carrier,
+ ALTER COLUMN phone_notify TYPE BOOLEAN
+ USING CASE WHEN phone_notify IS NULL OR phone_notify = '' THEN FALSE ELSE TRUE END,
+ ALTER COLUMN sms_notify TYPE BOOLEAN
+ USING CASE WHEN sms_notify IS NULL OR sms_notify = '' THEN FALSE ELSE TRUE END,
+ ALTER COLUMN phone_notify SET NOT NULL,
+ ALTER COLUMN sms_notify SET NOT NULL;
+CREATE INDEX aged_hold_request_target_idx ON action.aged_hold_request (target);
+CREATE INDEX aged_hold_request_pickup_lib_idx ON action.aged_hold_request (pickup_lib);
+CREATE INDEX aged_hold_request_current_copy_idx ON action.aged_hold_request (current_copy);
+CREATE INDEX aged_hold_request_fulfillment_staff_idx ON action.aged_hold_request ( fulfillment_staff );
+
+CREATE OR REPLACE VIEW action.all_hold_request AS
+ SELECT DISTINCT
+ COALESCE(a.post_code, b.post_code) AS usr_post_code,
+ p.home_ou AS usr_home_ou,
+ p.profile AS usr_profile,
+ EXTRACT(YEAR FROM p.dob)::INT AS usr_birth_year,
+ CAST(ahr.requestor <> ahr.usr AS BOOLEAN) AS staff_placed,
+ ahr.id,
+ ahr.request_time,
+ ahr.capture_time,
+ ahr.fulfillment_time,
+ ahr.checkin_time,
+ ahr.return_time,
+ ahr.prev_check_time,
+ ahr.expire_time,
+ ahr.cancel_time,
+ ahr.cancel_cause,
+ ahr.cancel_note,
+ ahr.target,
+ ahr.current_copy,
+ ahr.fulfillment_staff,
+ ahr.fulfillment_lib,
+ ahr.request_lib,
+ ahr.selection_ou,
+ ahr.selection_depth,
+ ahr.pickup_lib,
+ ahr.hold_type,
+ ahr.holdable_formats,
+ CASE
+ WHEN ahr.phone_notify IS NULL THEN FALSE
+ WHEN ahr.phone_notify = '' THEN FALSE
+ ELSE TRUE
+ END AS phone_notify,
+ ahr.email_notify,
+ CASE
+ WHEN ahr.sms_notify IS NULL THEN FALSE
+ WHEN ahr.sms_notify = '' THEN FALSE
+ ELSE TRUE
+ END AS sms_notify,
+ ahr.frozen,
+ ahr.thaw_date,
+ ahr.shelf_time,
+ ahr.cut_in_line,
+ ahr.mint_condition,
+ ahr.shelf_expire_time,
+ ahr.current_shelf_lib
+ FROM action.hold_request ahr
+ JOIN actor.usr p ON (ahr.usr = p.id)
+ LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
+ LEFT JOIN actor.usr_address b ON (p.billing_address = b.id)
+ UNION ALL
+ SELECT
+ usr_post_code,
+ usr_home_ou,
+ usr_profile,
+ usr_birth_year,
+ staff_placed,
+ id,
+ request_time,
+ capture_time,
+ fulfillment_time,
+ checkin_time,
+ return_time,
+ prev_check_time,
+ expire_time,
+ cancel_time,
+ cancel_cause,
+ cancel_note,
+ target,
+ current_copy,
+ fulfillment_staff,
+ fulfillment_lib,
+ request_lib,
+ selection_ou,
+ selection_depth,
+ pickup_lib,
+ hold_type,
+ holdable_formats,
+ phone_notify,
+ email_notify,
+ sms_notify,
+ frozen,
+ thaw_date,
+ shelf_time,
+ cut_in_line,
+ mint_condition,
+ shelf_expire_time,
+ current_shelf_lib
+ FROM action.aged_hold_request;
+
+CREATE OR REPLACE FUNCTION action.age_hold_on_delete () RETURNS TRIGGER AS $$
+DECLARE
+BEGIN
+ -- Archive a copy of the old row to action.aged_hold_request
+
+ INSERT INTO action.aged_hold_request
+ (usr_post_code,
+ usr_home_ou,
+ usr_profile,
+ usr_birth_year,
+ staff_placed,
+ id,
+ request_time,
+ capture_time,
+ fulfillment_time,
+ checkin_time,
+ return_time,
+ prev_check_time,
+ expire_time,
+ cancel_time,
+ cancel_cause,
+ cancel_note,
+ target,
+ current_copy,
+ fulfillment_staff,
+ fulfillment_lib,
+ request_lib,
+ selection_ou,
+ selection_depth,
+ pickup_lib,
+ hold_type,
+ holdable_formats,
+ phone_notify,
+ email_notify,
+ sms_notify,
+ frozen,
+ thaw_date,
+ shelf_time,
+ cut_in_line,
+ mint_condition,
+ shelf_expire_time,
+ current_shelf_lib)
+ SELECT
+ usr_post_code,
+ usr_home_ou,
+ usr_profile,
+ usr_birth_year,
+ staff_placed,
+ id,
+ request_time,
+ capture_time,
+ fulfillment_time,
+ checkin_time,
+ return_time,
+ prev_check_time,
+ expire_time,
+ cancel_time,
+ cancel_cause,
+ cancel_note,
+ target,
+ current_copy,
+ fulfillment_staff,
+ fulfillment_lib,
+ request_lib,
+ selection_ou,
+ selection_depth,
+ pickup_lib,
+ hold_type,
+ holdable_formats,
+ phone_notify,
+ email_notify,
+ sms_notify,
+ frozen,
+ thaw_date,
+ shelf_time,
+ cut_in_line,
+ mint_condition,
+ shelf_expire_time,
+ current_shelf_lib
+ FROM action.all_hold_request WHERE id = OLD.id;
+
+ RETURN OLD;
+END;
+$$ LANGUAGE 'plpgsql';
+
+CREATE TRIGGER action_hold_request_aging_tgr
+ BEFORE DELETE ON action.hold_request
+ FOR EACH ROW
+ EXECUTE PROCEDURE action.age_hold_on_delete ();
+
CREATE TABLE action.fieldset (
id SERIAL PRIMARY KEY,
owner INT NOT NULL REFERENCES actor.usr (id)
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
deleted file mode 100644
index 693167a..0000000
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
+++ /dev/null
@@ -1,163 +0,0 @@
-
--- New global flags for the purge function
-INSERT INTO config.global_flag (name, label, enabled)
- VALUES (
- 'history.hold.retention_age',
- oils_i18n_gettext('history.hold.retention_age', 'Historical Hold Retention Age', 'cgf', 'label'),
- TRUE
- ),(
- 'history.hold.retention_age_fulfilled',
- oils_i18n_gettext('history.hold.retention_age_fulfilled', 'Historical Hold Retention Age - Fulfilled', 'cgf', 'label'),
- FALSE
- ),(
- 'history.hold.retention_age_canceled',
- oils_i18n_gettext('history.hold.retention_age_canceled', 'Historical Hold Retention Age - Canceled (Default)', 'cgf', 'label'),
- FALSE
- ),(
- 'history.hold.retention_age_canceled_1',
- oils_i18n_gettext('history.hold.retention_age_canceled_1', 'Historical Hold Retention Age - Canceled (Untarged expiration)', 'cgf', 'label'),
- FALSE
- ),(
- 'history.hold.retention_age_canceled_2',
- oils_i18n_gettext('history.hold.retention_age_canceled_2', 'Historical Hold Retention Age - Canceled (Hold Shelf expiration)', 'cgf', 'label'),
- FALSE
- ),(
- 'history.hold.retention_age_canceled_3',
- oils_i18n_gettext('history.hold.retention_age_canceled_3', 'Historical Hold Retention Age - Canceled (Patron via phone)', 'cgf', 'label'),
- TRUE
- ),(
- 'history.hold.retention_age_canceled_4',
- oils_i18n_gettext('history.hold.retention_age_canceled_4', 'Historical Hold Retention Age - Canceled (Patron in person)', 'cgf', 'label'),
- TRUE
- ),(
- 'history.hold.retention_age_canceled_5',
- oils_i18n_gettext('history.hold.retention_age_canceled_5', 'Historical Hold Retention Age - Canceled (Staff forced)', 'cgf', 'label'),
- TRUE
- ),(
- 'history.hold.retention_age_canceled_6',
- oils_i18n_gettext('history.hold.retention_age_canceled_6', 'Historical Hold Retention Age - Canceled (Patron via OPAC)', 'cgf', 'label'),
- FALSE
- );
-
-CREATE OR REPLACE FUNCTION action.purge_holds() RETURNS INT AS $func$
-DECLARE
- current_hold RECORD;
- purged_holds INT;
- cgf_d INTERVAL;
- cgf_f INTERVAL;
- cgf_c INTERVAL;
- prev_usr INT;
- user_start TIMESTAMPTZ;
- user_age INTERVAL;
- user_count INT;
-BEGIN
- purged_holds := 0;
- SELECT INTO cgf_d value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age' AND enabled;
- SELECT INTO cgf_f value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_fulfilled' AND enabled;
- SELECT INTO cgf_c value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_canceled' AND enabled;
- FOR current_hold IN
- SELECT
- rank() OVER (PARTITION BY usr ORDER BY COALESCE(fulfillment_time, cancel_time) DESC),
- cgf_cs.value::INTERVAL as cgf_cs,
- ahr.*
- FROM
- action.hold_request ahr
- LEFT JOIN config.global_flag cgf_cs ON (ahr.cancel_cause IS NOT NULL AND cgf_cs.name = 'history.hold.retention_age_canceled_' || ahr.cancel_cause AND cgf_cs.enabled)
- WHERE
- (fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL)
- LOOP
- IF prev_usr IS NULL OR prev_usr != current_hold.usr THEN
- prev_usr := current_hold.usr;
- SELECT INTO user_start oils_json_to_text(value)::TIMESTAMPTZ FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_start';
- SELECT INTO user_age oils_json_to_text(value)::INTERVAL FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_age';
- SELECT INTO user_count oils_json_to_text(value)::INT FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_count';
- IF user_start IS NOT NULL THEN
- user_age := LEAST(user_age, AGE(NOW(), user_start));
- END IF;
- IF user_count IS NULL THEN
- user_count := 1000; -- Assumption based on the user visible holds routine
- END IF;
- END IF;
- -- Library keep age trumps user keep anything, for purposes of being able to hold on to things when staff canceled and such.
- IF current_hold.fulfillment_time IS NOT NULL AND current_hold.fulfillment_time > NOW() - COALESCE(cgf_f, cgf_d) THEN
- CONTINUE;
- END IF;
- IF current_hold.cancel_time IS NOT NULL AND current_hold.cancel_time > NOW() - COALESCE(current_hold.cgf_cs, cgf_c, cgf_d) THEN
- CONTINUE;
- END IF;
-
- -- User keep age needs combining with count. If too old AND within the count, keep!
- IF user_start IS NOT NULL AND COALESCE(current_hold.fulfillment_time, current_hold.cancel_time) > NOW() - user_age AND current_hold.rank <= user_count THEN
- CONTINUE;
- END IF;
-
- -- All checks should have passed, delete!
- DELETE FROM action.hold_request WHERE id = current_hold.id;
- purged_holds := purged_holds + 1;
- END LOOP;
- RETURN purged_holds;
-END;
-$func$ LANGUAGE plpgsql;
-
-CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
-DECLARE
- h action.hold_request%ROWTYPE;
- view_age INTERVAL;
- view_count INT;
- usr_view_count actor.usr_setting%ROWTYPE;
- usr_view_age actor.usr_setting%ROWTYPE;
- usr_view_start actor.usr_setting%ROWTYPE;
-BEGIN
- SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
- SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
- SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
-
- FOR h IN
- SELECT *
- FROM action.hold_request
- WHERE usr = usr_id
- AND fulfillment_time IS NULL
- AND cancel_time IS NULL
- ORDER BY request_time DESC
- LOOP
- RETURN NEXT h;
- END LOOP;
-
- IF usr_view_start.value IS NULL THEN
- RETURN;
- END IF;
-
- IF usr_view_age.value IS NOT NULL THEN
- -- User opted in and supplied a retention age
- IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
- view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
- ELSE
- view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
- END IF;
- ELSE
- -- User opted in
- view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
- END IF;
-
- IF usr_view_count.value IS NOT NULL THEN
- view_count := oils_json_to_text(usr_view_count.value)::INT;
- ELSE
- view_count := 1000;
- END IF;
-
- -- show some fulfilled/canceled holds
- FOR h IN
- SELECT *
- FROM action.hold_request
- WHERE usr = usr_id
- AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
- AND COALESCE(fulfillment_time, cancel_time) > NOW() - view_age
- ORDER BY COALESCE(fulfillment_time, cancel_time) DESC
- LIMIT view_count
- LOOP
- RETURN NEXT h;
- END LOOP;
-
- RETURN;
-END;
-$func$ LANGUAGE PLPGSQL;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.action.purge_holds.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.action.purge_holds.sql
new file mode 100644
index 0000000..5389856
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.action.purge_holds.sql
@@ -0,0 +1,368 @@
+
+-- New global flags for the purge function
+INSERT INTO config.global_flag (name, label, enabled)
+ VALUES (
+ 'history.hold.retention_age',
+ oils_i18n_gettext('history.hold.retention_age', 'Historical Hold Retention Age', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_fulfilled',
+ oils_i18n_gettext('history.hold.retention_age_fulfilled', 'Historical Hold Retention Age - Fulfilled', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled',
+ oils_i18n_gettext('history.hold.retention_age_canceled', 'Historical Hold Retention Age - Canceled (Default)', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled_1',
+ oils_i18n_gettext('history.hold.retention_age_canceled_1', 'Historical Hold Retention Age - Canceled (Untarged expiration)', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled_2',
+ oils_i18n_gettext('history.hold.retention_age_canceled_2', 'Historical Hold Retention Age - Canceled (Hold Shelf expiration)', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled_3',
+ oils_i18n_gettext('history.hold.retention_age_canceled_3', 'Historical Hold Retention Age - Canceled (Patron via phone)', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_canceled_4',
+ oils_i18n_gettext('history.hold.retention_age_canceled_4', 'Historical Hold Retention Age - Canceled (Patron in person)', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_canceled_5',
+ oils_i18n_gettext('history.hold.retention_age_canceled_5', 'Historical Hold Retention Age - Canceled (Staff forced)', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_canceled_6',
+ oils_i18n_gettext('history.hold.retention_age_canceled_6', 'Historical Hold Retention Age - Canceled (Patron via OPAC)', 'cgf', 'label'),
+ FALSE
+ );
+
+CREATE OR REPLACE FUNCTION action.purge_holds() RETURNS INT AS $func$
+DECLARE
+ current_hold RECORD;
+ purged_holds INT;
+ cgf_d INTERVAL;
+ cgf_f INTERVAL;
+ cgf_c INTERVAL;
+ prev_usr INT;
+ user_start TIMESTAMPTZ;
+ user_age INTERVAL;
+ user_count INT;
+BEGIN
+ purged_holds := 0;
+ SELECT INTO cgf_d value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age' AND enabled;
+ SELECT INTO cgf_f value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_fulfilled' AND enabled;
+ SELECT INTO cgf_c value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_canceled' AND enabled;
+ FOR current_hold IN
+ SELECT
+ rank() OVER (PARTITION BY usr ORDER BY COALESCE(fulfillment_time, cancel_time) DESC),
+ cgf_cs.value::INTERVAL as cgf_cs,
+ ahr.*
+ FROM
+ action.hold_request ahr
+ LEFT JOIN config.global_flag cgf_cs ON (ahr.cancel_cause IS NOT NULL AND cgf_cs.name = 'history.hold.retention_age_canceled_' || ahr.cancel_cause AND cgf_cs.enabled)
+ WHERE
+ (fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL)
+ LOOP
+ IF prev_usr IS NULL OR prev_usr != current_hold.usr THEN
+ prev_usr := current_hold.usr;
+ SELECT INTO user_start oils_json_to_text(value)::TIMESTAMPTZ FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_start';
+ SELECT INTO user_age oils_json_to_text(value)::INTERVAL FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_age';
+ SELECT INTO user_count oils_json_to_text(value)::INT FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_count';
+ IF user_start IS NOT NULL THEN
+ user_age := LEAST(user_age, AGE(NOW(), user_start));
+ END IF;
+ IF user_count IS NULL THEN
+ user_count := 1000; -- Assumption based on the user visible holds routine
+ END IF;
+ END IF;
+ -- Library keep age trumps user keep anything, for purposes of being able to hold on to things when staff canceled and such.
+ IF current_hold.fulfillment_time IS NOT NULL AND current_hold.fulfillment_time > NOW() - COALESCE(cgf_f, cgf_d) THEN
+ CONTINUE;
+ END IF;
+ IF current_hold.cancel_time IS NOT NULL AND current_hold.cancel_time > NOW() - COALESCE(current_hold.cgf_cs, cgf_c, cgf_d) THEN
+ CONTINUE;
+ END IF;
+
+ -- User keep age needs combining with count. If too old AND within the count, keep!
+ IF user_start IS NOT NULL AND COALESCE(current_hold.fulfillment_time, current_hold.cancel_time) > NOW() - user_age AND current_hold.rank <= user_count THEN
+ CONTINUE;
+ END IF;
+
+ -- All checks should have passed, delete!
+ DELETE FROM action.hold_request WHERE id = current_hold.id;
+ purged_holds := purged_holds + 1;
+ END LOOP;
+ RETURN purged_holds;
+END;
+$func$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
+DECLARE
+ h action.hold_request%ROWTYPE;
+ view_age INTERVAL;
+ view_count INT;
+ usr_view_count actor.usr_setting%ROWTYPE;
+ usr_view_age actor.usr_setting%ROWTYPE;
+ usr_view_start actor.usr_setting%ROWTYPE;
+BEGIN
+ SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
+ SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
+ SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
+
+ FOR h IN
+ SELECT *
+ FROM action.hold_request
+ WHERE usr = usr_id
+ AND fulfillment_time IS NULL
+ AND cancel_time IS NULL
+ ORDER BY request_time DESC
+ LOOP
+ RETURN NEXT h;
+ END LOOP;
+
+ IF usr_view_start.value IS NULL THEN
+ RETURN;
+ END IF;
+
+ IF usr_view_age.value IS NOT NULL THEN
+ -- User opted in and supplied a retention age
+ IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
+ view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
+ ELSE
+ view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
+ END IF;
+ ELSE
+ -- User opted in
+ view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
+ END IF;
+
+ IF usr_view_count.value IS NOT NULL THEN
+ view_count := oils_json_to_text(usr_view_count.value)::INT;
+ ELSE
+ view_count := 1000;
+ END IF;
+
+ -- show some fulfilled/canceled holds
+ FOR h IN
+ SELECT *
+ FROM action.hold_request
+ WHERE usr = usr_id
+ AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
+ AND COALESCE(fulfillment_time, cancel_time) > NOW() - view_age
+ ORDER BY COALESCE(fulfillment_time, cancel_time) DESC
+ LIMIT view_count
+ LOOP
+ RETURN NEXT h;
+ END LOOP;
+
+ RETURN;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+CREATE TABLE action.aged_hold_request (
+ usr_post_code TEXT,
+ usr_home_ou INT NOT NULL,
+ usr_profile INT NOT NULL,
+ usr_birth_year INT,
+ staff_placed BOOLEAN NOT NULL,
+ LIKE action.hold_request
+);
+ALTER TABLE action.aged_hold_request
+ ADD PRIMARY KEY (id),
+ DROP COLUMN usr,
+ DROP COLUMN requestor,
+ DROP COLUMN sms_carrier,
+ ALTER COLUMN phone_notify TYPE BOOLEAN
+ USING CASE WHEN phone_notify IS NULL OR phone_notify = '' THEN FALSE ELSE TRUE END,
+ ALTER COLUMN sms_notify TYPE BOOLEAN
+ USING CASE WHEN sms_notify IS NULL OR sms_notify = '' THEN FALSE ELSE TRUE END,
+ ALTER COLUMN phone_notify SET NOT NULL,
+ ALTER COLUMN sms_notify SET NOT NULL;
+CREATE INDEX aged_hold_request_target_idx ON action.aged_hold_request (target);
+CREATE INDEX aged_hold_request_pickup_lib_idx ON action.aged_hold_request (pickup_lib);
+CREATE INDEX aged_hold_request_current_copy_idx ON action.aged_hold_request (current_copy);
+CREATE INDEX aged_hold_request_fulfillment_staff_idx ON action.aged_hold_request ( fulfillment_staff );
+
+CREATE OR REPLACE VIEW action.all_hold_request AS
+ SELECT DISTINCT
+ COALESCE(a.post_code, b.post_code) AS usr_post_code,
+ p.home_ou AS usr_home_ou,
+ p.profile AS usr_profile,
+ EXTRACT(YEAR FROM p.dob)::INT AS usr_birth_year,
+ CAST(ahr.requestor <> ahr.usr AS BOOLEAN) AS staff_placed,
+ ahr.id,
+ ahr.request_time,
+ ahr.capture_time,
+ ahr.fulfillment_time,
+ ahr.checkin_time,
+ ahr.return_time,
+ ahr.prev_check_time,
+ ahr.expire_time,
+ ahr.cancel_time,
+ ahr.cancel_cause,
+ ahr.cancel_note,
+ ahr.target,
+ ahr.current_copy,
+ ahr.fulfillment_staff,
+ ahr.fulfillment_lib,
+ ahr.request_lib,
+ ahr.selection_ou,
+ ahr.selection_depth,
+ ahr.pickup_lib,
+ ahr.hold_type,
+ ahr.holdable_formats,
+ CASE
+ WHEN ahr.phone_notify IS NULL THEN FALSE
+ WHEN ahr.phone_notify = '' THEN FALSE
+ ELSE TRUE
+ END AS phone_notify,
+ ahr.email_notify,
+ CASE
+ WHEN ahr.sms_notify IS NULL THEN FALSE
+ WHEN ahr.sms_notify = '' THEN FALSE
+ ELSE TRUE
+ END AS sms_notify,
+ ahr.frozen,
+ ahr.thaw_date,
+ ahr.shelf_time,
+ ahr.cut_in_line,
+ ahr.mint_condition,
+ ahr.shelf_expire_time,
+ ahr.current_shelf_lib
+ FROM action.hold_request ahr
+ JOIN actor.usr p ON (ahr.usr = p.id)
+ LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
+ LEFT JOIN actor.usr_address b ON (p.billing_address = b.id)
+ UNION ALL
+ SELECT
+ usr_post_code,
+ usr_home_ou,
+ usr_profile,
+ usr_birth_year,
+ staff_placed,
+ id,
+ request_time,
+ capture_time,
+ fulfillment_time,
+ checkin_time,
+ return_time,
+ prev_check_time,
+ expire_time,
+ cancel_time,
+ cancel_cause,
+ cancel_note,
+ target,
+ current_copy,
+ fulfillment_staff,
+ fulfillment_lib,
+ request_lib,
+ selection_ou,
+ selection_depth,
+ pickup_lib,
+ hold_type,
+ holdable_formats,
+ phone_notify,
+ email_notify,
+ sms_notify,
+ frozen,
+ thaw_date,
+ shelf_time,
+ cut_in_line,
+ mint_condition,
+ shelf_expire_time,
+ current_shelf_lib
+ FROM action.aged_hold_request;
+
+CREATE OR REPLACE FUNCTION action.age_hold_on_delete () RETURNS TRIGGER AS $$
+DECLARE
+BEGIN
+ -- Archive a copy of the old row to action.aged_hold_request
+
+ INSERT INTO action.aged_hold_request
+ (usr_post_code,
+ usr_home_ou,
+ usr_profile,
+ usr_birth_year,
+ staff_placed,
+ id,
+ request_time,
+ capture_time,
+ fulfillment_time,
+ checkin_time,
+ return_time,
+ prev_check_time,
+ expire_time,
+ cancel_time,
+ cancel_cause,
+ cancel_note,
+ target,
+ current_copy,
+ fulfillment_staff,
+ fulfillment_lib,
+ request_lib,
+ selection_ou,
+ selection_depth,
+ pickup_lib,
+ hold_type,
+ holdable_formats,
+ phone_notify,
+ email_notify,
+ sms_notify,
+ frozen,
+ thaw_date,
+ shelf_time,
+ cut_in_line,
+ mint_condition,
+ shelf_expire_time,
+ current_shelf_lib)
+ SELECT
+ usr_post_code,
+ usr_home_ou,
+ usr_profile,
+ usr_birth_year,
+ staff_placed,
+ id,
+ request_time,
+ capture_time,
+ fulfillment_time,
+ checkin_time,
+ return_time,
+ prev_check_time,
+ expire_time,
+ cancel_time,
+ cancel_cause,
+ cancel_note,
+ target,
+ current_copy,
+ fulfillment_staff,
+ fulfillment_lib,
+ request_lib,
+ selection_ou,
+ selection_depth,
+ pickup_lib,
+ hold_type,
+ holdable_formats,
+ phone_notify,
+ email_notify,
+ sms_notify,
+ frozen,
+ thaw_date,
+ shelf_time,
+ cut_in_line,
+ mint_condition,
+ shelf_expire_time,
+ current_shelf_lib
+ FROM action.all_hold_request WHERE id = OLD.id;
+
+ RETURN OLD;
+END;
+$$ LANGUAGE 'plpgsql';
+
+CREATE TRIGGER action_hold_request_aging_tgr
+ BEFORE DELETE ON action.hold_request
+ FOR EACH ROW
+ EXECUTE PROCEDURE action.age_hold_on_delete ();
+
commit dc40c1c748cfbb0c4b14d94b5fe72825cb110a08
Author: Jason Stephenson <jstephenson at mvlc.org>
Date: Mon Apr 1 16:59:59 2013 -0400
Add a purge_holds.srfsh script so it can be run from cron.
Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
Signed-off-by: Dan Wells <dbw2 at calvin.edu>
diff --git a/Open-ILS/src/Makefile.am b/Open-ILS/src/Makefile.am
index 0363c4b..d96d1bd 100644
--- a/Open-ILS/src/Makefile.am
+++ b/Open-ILS/src/Makefile.am
@@ -72,6 +72,7 @@ core_scripts = $(examples)/oils_ctl.sh \
$(supportscr)/thaw_expired_frozen_holds.srfsh \
$(supportscr)/long-overdue-status-update.pl \
$(supportscr)/action_trigger_runner.pl \
+ $(supportscr)/purge_holds.srfsh \
$(srcdir)/extras/eg_config \
$(srcdir)/extras/openurl_map.pl \
$(srcdir)/extras/import/marc_add_ids
diff --git a/Open-ILS/src/support-scripts/purge_holds.srfsh b/Open-ILS/src/support-scripts/purge_holds.srfsh
new file mode 100755
index 0000000..bce78e2
--- /dev/null
+++ b/Open-ILS/src/support-scripts/purge_holds.srfsh
@@ -0,0 +1,7 @@
+#!/openils/bin/srfsh
+open open-ils.cstore
+request open-ils.cstore open-ils.cstore.transaction.begin
+request open-ils.cstore open-ils.cstore.json_query {"from":["action.purge_holds"]}
+request open-ils.cstore open-ils.cstore.transaction.commit
+close open-ils.cstore
+
commit 765aea78207f879e81eeb9f3d7614699399e585f
Author: Thomas Berezansky <tsbere at mvlc.org>
Date: Tue Apr 2 10:18:55 2013 -0400
Fix typo in purge holds function
Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>
Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
diff --git a/Open-ILS/src/sql/Pg/090.schema.action.sql b/Open-ILS/src/sql/Pg/090.schema.action.sql
index 639becd..d07f07f 100644
--- a/Open-ILS/src/sql/Pg/090.schema.action.sql
+++ b/Open-ILS/src/sql/Pg/090.schema.action.sql
@@ -879,7 +879,7 @@ BEGIN
ahr.*
FROM
action.hold_request ahr
- LEFT JOIN config.global_flag cgf_cs ON (ahr.cancel_cause IS NOT NULL AND cgf_cs.name = 'history.hold.retenetion_age_canceled_' || ahr.cancel_cause AND cgf_cs.enabled)
+ LEFT JOIN config.global_flag cgf_cs ON (ahr.cancel_cause IS NOT NULL AND cgf_cs.name = 'history.hold.retention_age_canceled_' || ahr.cancel_cause AND cgf_cs.enabled)
WHERE
(fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL)
LOOP
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
index 3a6714c..693167a 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
@@ -62,7 +62,7 @@ BEGIN
ahr.*
FROM
action.hold_request ahr
- LEFT JOIN config.global_flag cgf_cs ON (ahr.cancel_cause IS NOT NULL AND cgf_cs.name = 'history.hold.retenetion_age_canceled_' || ahr.cancel_cause AND cgf_cs.enabled)
+ LEFT JOIN config.global_flag cgf_cs ON (ahr.cancel_cause IS NOT NULL AND cgf_cs.name = 'history.hold.retention_age_canceled_' || ahr.cancel_cause AND cgf_cs.enabled)
WHERE
(fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL)
LOOP
commit d43e92f682f6e06a61d508944cf340093168e77a
Author: Thomas Berezansky <tsbere at mvlc.org>
Date: Wed Feb 13 16:08:01 2013 -0500
Release notes for Purge Holds routine
Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>
Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
diff --git a/docs/RELEASE_NOTES_NEXT/purge_holds.txt b/docs/RELEASE_NOTES_NEXT/purge_holds.txt
new file mode 100644
index 0000000..f015f6d
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/purge_holds.txt
@@ -0,0 +1,7 @@
+New Feature: "Purge Holds"
+==========================
+Similar to purging circulations one may wish to purge old (filled or canceled) hold information. This feature adds a database function and settings for doing so.
+
+The settings allow for a default retention age as well as filled, canceled, and canceled by cancel cause ages. The most specific one wins unless a patron is retaining their hold history. In the latter case the patron's holds are retained either way.
+
+Note that the function still needs to be called, which could be set up as a cron job or done more manually, say after statistics collection.
commit 86995bbb791f3ba0771e92921f756f6680a22571
Author: Thomas Berezansky <tsbere at mvlc.org>
Date: Wed Feb 13 15:41:44 2013 -0500
Fix seed values and upgrade script
enabled and label were out of order.
Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>
Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
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 e5ae671..43bc29a 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -9004,7 +9004,7 @@ INSERT INTO config.global_flag (name,label,enabled)
TRUE
);
-INSERT INTO config.global_flag (name, enabled, label)
+INSERT INTO config.global_flag (name, label, enabled)
VALUES (
'history.hold.retention_age',
oils_i18n_gettext('history.hold.retention_age', 'Historical Hold Retention Age', 'cgf', 'label'),
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
index 69ae1df..3a6714c 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
@@ -1,6 +1,6 @@
-- New global flags for the purge function
-INSERT INTO config.global_flag (name, enabled, label)
+INSERT INTO config.global_flag (name, label, enabled)
VALUES (
'history.hold.retention_age',
oils_i18n_gettext('history.hold.retention_age', 'Historical Hold Retention Age', 'cgf', 'label'),
commit 0771f7c6ea249a336ec7e68bd6e1949311ce3856
Author: Thomas Berezansky <tsbere at mvlc.org>
Date: Thu Jun 21 09:55:05 2012 -0400
Make user visible holds based on fill/cancel time
Instead of request time. That way holds don't vanish after filling when
keeping history was turned on after hold placement.
Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>
Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
diff --git a/Open-ILS/src/sql/Pg/090.schema.action.sql b/Open-ILS/src/sql/Pg/090.schema.action.sql
index f309498..639becd 100644
--- a/Open-ILS/src/sql/Pg/090.schema.action.sql
+++ b/Open-ILS/src/sql/Pg/090.schema.action.sql
@@ -757,8 +757,8 @@ BEGIN
FROM action.hold_request
WHERE usr = usr_id
AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
- AND request_time > NOW() - view_age
- ORDER BY request_time DESC
+ AND COALESCE(fulfillment_time, cancel_time) > NOW() - view_age
+ ORDER BY COALESCE(fulfillment_time, cancel_time) DESC
LIMIT view_count
LOOP
RETURN NEXT h;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
index 7db1607..69ae1df 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
@@ -98,3 +98,66 @@ BEGIN
RETURN purged_holds;
END;
$func$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
+DECLARE
+ h action.hold_request%ROWTYPE;
+ view_age INTERVAL;
+ view_count INT;
+ usr_view_count actor.usr_setting%ROWTYPE;
+ usr_view_age actor.usr_setting%ROWTYPE;
+ usr_view_start actor.usr_setting%ROWTYPE;
+BEGIN
+ SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
+ SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
+ SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
+
+ FOR h IN
+ SELECT *
+ FROM action.hold_request
+ WHERE usr = usr_id
+ AND fulfillment_time IS NULL
+ AND cancel_time IS NULL
+ ORDER BY request_time DESC
+ LOOP
+ RETURN NEXT h;
+ END LOOP;
+
+ IF usr_view_start.value IS NULL THEN
+ RETURN;
+ END IF;
+
+ IF usr_view_age.value IS NOT NULL THEN
+ -- User opted in and supplied a retention age
+ IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
+ view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
+ ELSE
+ view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
+ END IF;
+ ELSE
+ -- User opted in
+ view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
+ END IF;
+
+ IF usr_view_count.value IS NOT NULL THEN
+ view_count := oils_json_to_text(usr_view_count.value)::INT;
+ ELSE
+ view_count := 1000;
+ END IF;
+
+ -- show some fulfilled/canceled holds
+ FOR h IN
+ SELECT *
+ FROM action.hold_request
+ WHERE usr = usr_id
+ AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
+ AND COALESCE(fulfillment_time, cancel_time) > NOW() - view_age
+ ORDER BY COALESCE(fulfillment_time, cancel_time) DESC
+ LIMIT view_count
+ LOOP
+ RETURN NEXT h;
+ END LOOP;
+
+ RETURN;
+END;
+$func$ LANGUAGE PLPGSQL;
commit 1523a8b6c8512cac58d8ecbe5a5456265eb6fcf3
Author: Thomas Berezansky <tsbere at mvlc.org>
Date: Thu Jun 21 09:44:11 2012 -0400
Add purge_holds DB function
This allows removing of holds based on patron and library preferences.
Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>
Signed-off-by: Jason Stephenson <jstephenson at mvlc.org>
diff --git a/Open-ILS/src/sql/Pg/090.schema.action.sql b/Open-ILS/src/sql/Pg/090.schema.action.sql
index 9dec15f..f309498 100644
--- a/Open-ILS/src/sql/Pg/090.schema.action.sql
+++ b/Open-ILS/src/sql/Pg/090.schema.action.sql
@@ -856,6 +856,65 @@ BEGIN
END;
$func$ LANGUAGE PLPGSQL;
+CREATE OR REPLACE FUNCTION action.purge_holds() RETURNS INT AS $func$
+DECLARE
+ current_hold RECORD;
+ purged_holds INT;
+ cgf_d INTERVAL;
+ cgf_f INTERVAL;
+ cgf_c INTERVAL;
+ prev_usr INT;
+ user_start TIMESTAMPTZ;
+ user_age INTERVAL;
+ user_count INT;
+BEGIN
+ purged_holds := 0;
+ SELECT INTO cgf_d value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age' AND enabled;
+ SELECT INTO cgf_f value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_fulfilled' AND enabled;
+ SELECT INTO cgf_c value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_canceled' AND enabled;
+ FOR current_hold IN
+ SELECT
+ rank() OVER (PARTITION BY usr ORDER BY COALESCE(fulfillment_time, cancel_time) DESC),
+ cgf_cs.value::INTERVAL as cgf_cs,
+ ahr.*
+ FROM
+ action.hold_request ahr
+ LEFT JOIN config.global_flag cgf_cs ON (ahr.cancel_cause IS NOT NULL AND cgf_cs.name = 'history.hold.retenetion_age_canceled_' || ahr.cancel_cause AND cgf_cs.enabled)
+ WHERE
+ (fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL)
+ LOOP
+ IF prev_usr IS NULL OR prev_usr != current_hold.usr THEN
+ prev_usr := current_hold.usr;
+ SELECT INTO user_start oils_json_to_text(value)::TIMESTAMPTZ FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_start';
+ SELECT INTO user_age oils_json_to_text(value)::INTERVAL FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_age';
+ SELECT INTO user_count oils_json_to_text(value)::INT FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_count';
+ IF user_start IS NOT NULL THEN
+ user_age := LEAST(user_age, AGE(NOW(), user_start));
+ END IF;
+ IF user_count IS NULL THEN
+ user_count := 1000; -- Assumption based on the user visible holds routine
+ END IF;
+ END IF;
+ -- Library keep age trumps user keep anything, for purposes of being able to hold on to things when staff canceled and such.
+ IF current_hold.fulfillment_time IS NOT NULL AND current_hold.fulfillment_time > NOW() - COALESCE(cgf_f, cgf_d) THEN
+ CONTINUE;
+ END IF;
+ IF current_hold.cancel_time IS NOT NULL AND current_hold.cancel_time > NOW() - COALESCE(current_hold.cgf_cs, cgf_c, cgf_d) THEN
+ CONTINUE;
+ END IF;
+
+ -- User keep age needs combining with count. If too old AND within the count, keep!
+ IF user_start IS NOT NULL AND COALESCE(current_hold.fulfillment_time, current_hold.cancel_time) > NOW() - user_age AND current_hold.rank <= user_count THEN
+ CONTINUE;
+ END IF;
+
+ -- All checks should have passed, delete!
+ DELETE FROM action.hold_request WHERE id = current_hold.id;
+ purged_holds := purged_holds + 1;
+ END LOOP;
+ RETURN purged_holds;
+END;
+$func$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION action.apply_fieldset(
fieldset_id IN INT, -- id from action.fieldset
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 125a4b2..e5ae671 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -9004,6 +9004,45 @@ INSERT INTO config.global_flag (name,label,enabled)
TRUE
);
+INSERT INTO config.global_flag (name, enabled, label)
+ VALUES (
+ 'history.hold.retention_age',
+ oils_i18n_gettext('history.hold.retention_age', 'Historical Hold Retention Age', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_fulfilled',
+ oils_i18n_gettext('history.hold.retention_age_fulfilled', 'Historical Hold Retention Age - Fulfilled', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled',
+ oils_i18n_gettext('history.hold.retention_age_canceled', 'Historical Hold Retention Age - Canceled (Default)', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled_1',
+ oils_i18n_gettext('history.hold.retention_age_canceled_1', 'Historical Hold Retention Age - Canceled (Untarged expiration)', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled_2',
+ oils_i18n_gettext('history.hold.retention_age_canceled_2', 'Historical Hold Retention Age - Canceled (Hold Shelf expiration)', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled_3',
+ oils_i18n_gettext('history.hold.retention_age_canceled_3', 'Historical Hold Retention Age - Canceled (Patron via phone)', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_canceled_4',
+ oils_i18n_gettext('history.hold.retention_age_canceled_4', 'Historical Hold Retention Age - Canceled (Patron in person)', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_canceled_5',
+ oils_i18n_gettext('history.hold.retention_age_canceled_5', 'Historical Hold Retention Age - Canceled (Staff forced)', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_canceled_6',
+ oils_i18n_gettext('history.hold.retention_age_canceled_6', 'Historical Hold Retention Age - Canceled (Patron via OPAC)', 'cgf', 'label'),
+ FALSE
+ );
+
INSERT INTO config.global_flag (name, label, enabled)
VALUES (
'cat.maintain_control_numbers',
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
new file mode 100644
index 0000000..7db1607
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.purge_holds.sql
@@ -0,0 +1,100 @@
+
+-- New global flags for the purge function
+INSERT INTO config.global_flag (name, enabled, label)
+ VALUES (
+ 'history.hold.retention_age',
+ oils_i18n_gettext('history.hold.retention_age', 'Historical Hold Retention Age', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_fulfilled',
+ oils_i18n_gettext('history.hold.retention_age_fulfilled', 'Historical Hold Retention Age - Fulfilled', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled',
+ oils_i18n_gettext('history.hold.retention_age_canceled', 'Historical Hold Retention Age - Canceled (Default)', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled_1',
+ oils_i18n_gettext('history.hold.retention_age_canceled_1', 'Historical Hold Retention Age - Canceled (Untarged expiration)', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled_2',
+ oils_i18n_gettext('history.hold.retention_age_canceled_2', 'Historical Hold Retention Age - Canceled (Hold Shelf expiration)', 'cgf', 'label'),
+ FALSE
+ ),(
+ 'history.hold.retention_age_canceled_3',
+ oils_i18n_gettext('history.hold.retention_age_canceled_3', 'Historical Hold Retention Age - Canceled (Patron via phone)', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_canceled_4',
+ oils_i18n_gettext('history.hold.retention_age_canceled_4', 'Historical Hold Retention Age - Canceled (Patron in person)', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_canceled_5',
+ oils_i18n_gettext('history.hold.retention_age_canceled_5', 'Historical Hold Retention Age - Canceled (Staff forced)', 'cgf', 'label'),
+ TRUE
+ ),(
+ 'history.hold.retention_age_canceled_6',
+ oils_i18n_gettext('history.hold.retention_age_canceled_6', 'Historical Hold Retention Age - Canceled (Patron via OPAC)', 'cgf', 'label'),
+ FALSE
+ );
+
+CREATE OR REPLACE FUNCTION action.purge_holds() RETURNS INT AS $func$
+DECLARE
+ current_hold RECORD;
+ purged_holds INT;
+ cgf_d INTERVAL;
+ cgf_f INTERVAL;
+ cgf_c INTERVAL;
+ prev_usr INT;
+ user_start TIMESTAMPTZ;
+ user_age INTERVAL;
+ user_count INT;
+BEGIN
+ purged_holds := 0;
+ SELECT INTO cgf_d value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age' AND enabled;
+ SELECT INTO cgf_f value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_fulfilled' AND enabled;
+ SELECT INTO cgf_c value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_canceled' AND enabled;
+ FOR current_hold IN
+ SELECT
+ rank() OVER (PARTITION BY usr ORDER BY COALESCE(fulfillment_time, cancel_time) DESC),
+ cgf_cs.value::INTERVAL as cgf_cs,
+ ahr.*
+ FROM
+ action.hold_request ahr
+ LEFT JOIN config.global_flag cgf_cs ON (ahr.cancel_cause IS NOT NULL AND cgf_cs.name = 'history.hold.retenetion_age_canceled_' || ahr.cancel_cause AND cgf_cs.enabled)
+ WHERE
+ (fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL)
+ LOOP
+ IF prev_usr IS NULL OR prev_usr != current_hold.usr THEN
+ prev_usr := current_hold.usr;
+ SELECT INTO user_start oils_json_to_text(value)::TIMESTAMPTZ FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_start';
+ SELECT INTO user_age oils_json_to_text(value)::INTERVAL FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_age';
+ SELECT INTO user_count oils_json_to_text(value)::INT FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_count';
+ IF user_start IS NOT NULL THEN
+ user_age := LEAST(user_age, AGE(NOW(), user_start));
+ END IF;
+ IF user_count IS NULL THEN
+ user_count := 1000; -- Assumption based on the user visible holds routine
+ END IF;
+ END IF;
+ -- Library keep age trumps user keep anything, for purposes of being able to hold on to things when staff canceled and such.
+ IF current_hold.fulfillment_time IS NOT NULL AND current_hold.fulfillment_time > NOW() - COALESCE(cgf_f, cgf_d) THEN
+ CONTINUE;
+ END IF;
+ IF current_hold.cancel_time IS NOT NULL AND current_hold.cancel_time > NOW() - COALESCE(current_hold.cgf_cs, cgf_c, cgf_d) THEN
+ CONTINUE;
+ END IF;
+
+ -- User keep age needs combining with count. If too old AND within the count, keep!
+ IF user_start IS NOT NULL AND COALESCE(current_hold.fulfillment_time, current_hold.cancel_time) > NOW() - user_age AND current_hold.rank <= user_count THEN
+ CONTINUE;
+ END IF;
+
+ -- All checks should have passed, delete!
+ DELETE FROM action.hold_request WHERE id = current_hold.id;
+ purged_holds := purged_holds + 1;
+ END LOOP;
+ RETURN purged_holds;
+END;
+$func$ LANGUAGE plpgsql;
-----------------------------------------------------------------------
Summary of changes:
Open-ILS/examples/fm_IDL.xml | 117 ++++++
Open-ILS/src/Makefile.am | 1 +
Open-ILS/src/sql/Pg/002.schema.config.sql | 2 +-
Open-ILS/src/sql/Pg/090.schema.action.sql | 267 ++++++++++++++-
Open-ILS/src/sql/Pg/950.data.seed-values.sql | 39 ++
.../Pg/upgrade/0797.schema.action.purge_holds.sql | 372 ++++++++++++++++++++
...date_hard_due_dates.srfsh => purge_holds.srfsh} | 2 +-
docs/RELEASE_NOTES_NEXT/purge_holds.txt | 9 +
8 files changed, 805 insertions(+), 4 deletions(-)
create mode 100644 Open-ILS/src/sql/Pg/upgrade/0797.schema.action.purge_holds.sql
copy Open-ILS/src/support-scripts/{update_hard_due_dates.srfsh => purge_holds.srfsh} (66%)
create mode 100644 docs/RELEASE_NOTES_NEXT/purge_holds.txt
hooks/post-receive
--
Evergreen ILS
More information about the open-ils-commits
mailing list