[open-ils-commits] [GIT] Evergreen ILS branch rel_3_2 updated. b14f00f1da0eaedf72d74b2bb623b2033001d32d
Evergreen Git
git at git.evergreen-ils.org
Tue Oct 23 16:32:35 EDT 2018
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, rel_3_2 has been updated
via b14f00f1da0eaedf72d74b2bb623b2033001d32d (commit)
from 20c9ab2b82187ff2631e778d8b9e503362d1c19c (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 b14f00f1da0eaedf72d74b2bb623b2033001d32d
Author: Jason Stephenson <jason at sigio.com>
Date: Tue Oct 23 16:32:17 2018 -0400
Forward port 3.1.5-3.2.0-upgrade-db.sql.
Signed-off-by: Jason Stephenson <jason at sigio.com>
diff --git a/Open-ILS/src/sql/Pg/version-upgrade/3.1.5-3.2.0-upgrade-db.sql b/Open-ILS/src/sql/Pg/version-upgrade/3.1.5-3.2.0-upgrade-db.sql
new file mode 100644
index 0000000..bc276b0
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/version-upgrade/3.1.5-3.2.0-upgrade-db.sql
@@ -0,0 +1,4904 @@
+--Upgrade Script for 3.1.5 to 3.2.0
+\set eg_version '''3.2.0'''
+BEGIN;
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.2.0', :eg_version);
+
+SELECT evergreen.upgrade_deps_block_check('1115', :eg_version);
+
+INSERT INTO permission.perm_list (id,code,description) VALUES ( 607, 'EMERGENCY_CLOSING', 'Create and manage Emergency Closings');
+
+INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('checkout.due.emergency_closing','aecc','Circulation due date was adjusted by the Emergency Closing handler');
+INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('hold.shelf_expire.emergency_closing','aech','Hold shelf expire time was adjusted by the Emergency Closing handler');
+INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('booking.due.emergency_closing','aecr','Booking reservation return date was adjusted by the Emergency Closing handler');
+
+CREATE TABLE action.emergency_closing (
+ id SERIAL PRIMARY KEY,
+ creator INT NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ create_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ process_start_time TIMESTAMPTZ,
+ process_end_time TIMESTAMPTZ,
+ last_update_time TIMESTAMPTZ
+);
+
+ALTER TABLE actor.org_unit_closed
+ ADD COLUMN emergency_closing INT
+ REFERENCES action.emergency_closing (id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
+
+CREATE TABLE action.emergency_closing_circulation (
+ id BIGSERIAL PRIMARY KEY,
+ emergency_closing INT NOT NULL REFERENCES action.emergency_closing (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ circulation INT NOT NULL REFERENCES action.circulation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ original_due_date TIMESTAMPTZ,
+ process_time TIMESTAMPTZ
+);
+CREATE INDEX emergency_closing_circulation_emergency_closing_idx ON action.emergency_closing_circulation (emergency_closing);
+CREATE INDEX emergency_closing_circulation_circulation_idx ON action.emergency_closing_circulation (circulation);
+
+CREATE TABLE action.emergency_closing_reservation (
+ id BIGSERIAL PRIMARY KEY,
+ emergency_closing INT NOT NULL REFERENCES action.emergency_closing (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ reservation INT NOT NULL REFERENCES booking.reservation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ original_end_time TIMESTAMPTZ,
+ process_time TIMESTAMPTZ
+);
+CREATE INDEX emergency_closing_reservation_emergency_closing_idx ON action.emergency_closing_reservation (emergency_closing);
+CREATE INDEX emergency_closing_reservation_reservation_idx ON action.emergency_closing_reservation (reservation);
+
+CREATE TABLE action.emergency_closing_hold (
+ id BIGSERIAL PRIMARY KEY,
+ emergency_closing INT NOT NULL REFERENCES action.emergency_closing (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ hold INT NOT NULL REFERENCES action.hold_request (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ original_shelf_expire_time TIMESTAMPTZ,
+ process_time TIMESTAMPTZ
+);
+CREATE INDEX emergency_closing_hold_emergency_closing_idx ON action.emergency_closing_hold (emergency_closing);
+CREATE INDEX emergency_closing_hold_hold_idx ON action.emergency_closing_hold (hold);
+
+CREATE OR REPLACE VIEW action.emergency_closing_status AS
+ SELECT e.*,
+ COALESCE(c.count, 0) AS circulations,
+ COALESCE(c.completed, 0) AS circulations_complete,
+ COALESCE(b.count, 0) AS reservations,
+ COALESCE(b.completed, 0) AS reservations_complete,
+ COALESCE(h.count, 0) AS holds,
+ COALESCE(h.completed, 0) AS holds_complete
+ FROM action.emergency_closing e
+ LEFT JOIN (SELECT emergency_closing, count(*) count, SUM((process_time IS NOT NULL)::INT) completed FROM action.emergency_closing_circulation GROUP BY 1) c ON (c.emergency_closing = e.id)
+ LEFT JOIN (SELECT emergency_closing, count(*) count, SUM((process_time IS NOT NULL)::INT) completed FROM action.emergency_closing_reservation GROUP BY 1) b ON (b.emergency_closing = e.id)
+ LEFT JOIN (SELECT emergency_closing, count(*) count, SUM((process_time IS NOT NULL)::INT) completed FROM action.emergency_closing_hold GROUP BY 1) h ON (h.emergency_closing = e.id)
+;
+
+CREATE OR REPLACE FUNCTION evergreen.find_next_open_time ( circ_lib INT, initial TIMESTAMPTZ, hourly BOOL DEFAULT FALSE, initial_time TIME DEFAULT NULL, dow_count INT DEFAULT 0 )
+ RETURNS TIMESTAMPTZ AS $$
+DECLARE
+ day_number INT;
+ plus_days INT;
+ final_time TEXT;
+ time_adjusted BOOL;
+ hoo_open TIME WITHOUT TIME ZONE;
+ hoo_close TIME WITHOUT TIME ZONE;
+ adjacent actor.org_unit_closed%ROWTYPE;
+ breakout INT := 0;
+BEGIN
+
+ IF dow_count > 6 THEN
+ RETURN initial;
+ END IF;
+
+ IF initial_time IS NULL THEN
+ initial_time := initial::TIME;
+ END IF;
+
+ final_time := (initial + '1 second'::INTERVAL)::TEXT;
+ LOOP
+ breakout := breakout + 1;
+
+ time_adjusted := FALSE;
+
+ IF dow_count > 0 THEN -- we're recursing, so check for HOO closing
+ day_number := EXTRACT(ISODOW FROM final_time::TIMESTAMPTZ) - 1;
+ plus_days := 0;
+ FOR i IN 1..7 LOOP
+ EXECUTE 'SELECT dow_' || day_number || '_open, dow_' || day_number || '_close FROM actor.hours_of_operation WHERE id = $1'
+ INTO hoo_open, hoo_close
+ USING circ_lib;
+
+ -- RAISE NOTICE 'initial time: %; dow: %; close: %',initial_time,day_number,hoo_close;
+
+ IF hoo_close = '00:00:00' THEN -- bah ... I guess we'll check the next day
+ day_number := (day_number + 1) % 7;
+ plus_days := plus_days + 1;
+ time_adjusted := TRUE;
+ CONTINUE;
+ END IF;
+
+ IF hoo_close IS NULL THEN -- no hours of operation ... assume no closing?
+ hoo_close := '23:59:59';
+ END IF;
+
+ EXIT;
+ END LOOP;
+
+ final_time := DATE(final_time::TIMESTAMPTZ + (plus_days || ' days')::INTERVAL)::TEXT;
+ IF hoo_close <> '00:00:00' AND hourly THEN -- Not a day-granular circ
+ final_time := final_time||' '|| hoo_close;
+ ELSE
+ final_time := final_time||' 23:59:59';
+ END IF;
+ END IF;
+
+ -- Loop through other closings
+ LOOP
+ SELECT * INTO adjacent FROM actor.org_unit_closed WHERE org_unit = circ_lib AND final_time::TIMESTAMPTZ between close_start AND close_end;
+ EXIT WHEN adjacent.id IS NULL;
+ time_adjusted := TRUE;
+ -- RAISE NOTICE 'recursing for closings with final_time: %',final_time;
+ final_time := evergreen.find_next_open_time(circ_lib, adjacent.close_end::TIMESTAMPTZ, hourly, initial_time, dow_count + 1)::TEXT;
+ END LOOP;
+
+ EXIT WHEN breakout > 100;
+ EXIT WHEN NOT time_adjusted;
+
+ END LOOP;
+
+ RETURN final_time;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE TYPE action.emergency_closing_stage_1_count AS (circulations INT, reservations INT, holds INT);
+CREATE OR REPLACE FUNCTION action.emergency_closing_stage_1 ( e_closing INT )
+ RETURNS SETOF action.emergency_closing_stage_1_count AS $$
+DECLARE
+ tmp INT;
+ touched action.emergency_closing_stage_1_count%ROWTYPE;
+BEGIN
+ -- First, gather circs
+ INSERT INTO action.emergency_closing_circulation (emergency_closing, circulation)
+ SELECT e_closing,
+ circ.id
+ FROM actor.org_unit_closed closing
+ JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing)
+ JOIN action.circulation circ ON (
+ circ.circ_lib = closing.org_unit
+ AND circ.due_date BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL)
+ AND circ.xact_finish IS NULL
+ )
+ WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_circulation t WHERE t.emergency_closing = e_closing AND t.circulation = circ.id);
+
+ GET DIAGNOSTICS tmp = ROW_COUNT;
+ touched.circulations := tmp;
+
+ INSERT INTO action.emergency_closing_reservation (emergency_closing, reservation)
+ SELECT e_closing,
+ res.id
+ FROM actor.org_unit_closed closing
+ JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing)
+ JOIN booking.reservation res ON (
+ res.pickup_lib = closing.org_unit
+ AND res.end_time BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL)
+ )
+ WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_reservation t WHERE t.emergency_closing = e_closing AND t.reservation = res.id);
+
+ GET DIAGNOSTICS tmp = ROW_COUNT;
+ touched.reservations := tmp;
+
+ INSERT INTO action.emergency_closing_hold (emergency_closing, hold)
+ SELECT e_closing,
+ hold.id
+ FROM actor.org_unit_closed closing
+ JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing)
+ JOIN action.hold_request hold ON (
+ pickup_lib = closing.org_unit
+ AND hold.shelf_expire_time BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL)
+ AND hold.fulfillment_time IS NULL
+ AND hold.cancel_time IS NULL
+ )
+ WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_hold t WHERE t.emergency_closing = e_closing AND t.hold = hold.id);
+
+ GET DIAGNOSTICS tmp = ROW_COUNT;
+ touched.holds := tmp;
+
+ UPDATE action.emergency_closing
+ SET process_start_time = NOW(),
+ last_update_time = NOW()
+ WHERE id = e_closing;
+
+ RETURN NEXT touched;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION action.emergency_closing_stage_2_hold ( hold_closing_entry INT )
+ RETURNS BOOL AS $$
+DECLARE
+ hold action.hold_request%ROWTYPE;
+ e_closing action.emergency_closing%ROWTYPE;
+ e_c_hold action.emergency_closing_hold%ROWTYPE;
+ closing actor.org_unit_closed%ROWTYPE;
+ day_number INT;
+ hoo_close TIME WITHOUT TIME ZONE;
+ plus_days INT;
+BEGIN
+ -- Gather objects involved
+ SELECT * INTO e_c_hold
+ FROM action.emergency_closing_hold
+ WHERE id = hold_closing_entry;
+
+ IF e_c_hold.process_time IS NOT NULL THEN
+ -- Already processed ... moving on
+ RETURN FALSE;
+ END IF;
+
+ SELECT * INTO e_closing
+ FROM action.emergency_closing
+ WHERE id = e_c_hold.emergency_closing;
+
+ IF e_closing.process_start_time IS NULL THEN
+ -- Huh... that's odd. And wrong.
+ RETURN FALSE;
+ END IF;
+
+ SELECT * INTO closing
+ FROM actor.org_unit_closed
+ WHERE emergency_closing = e_closing.id;
+
+ SELECT * INTO hold
+ FROM action.hold_request h
+ WHERE id = e_c_hold.hold;
+
+ -- Record the processing
+ UPDATE action.emergency_closing_hold
+ SET original_shelf_expire_time = hold.shelf_expire_time,
+ process_time = NOW()
+ WHERE id = hold_closing_entry;
+
+ UPDATE action.emergency_closing
+ SET last_update_time = NOW()
+ WHERE id = e_closing.id;
+
+ UPDATE action.hold_request
+ SET shelf_expire_time = evergreen.find_next_open_time(closing.org_unit, hold.shelf_expire_time, TRUE)
+ WHERE id = hold.id;
+
+ RETURN TRUE;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION action.emergency_closing_stage_2_circ ( circ_closing_entry INT )
+ RETURNS BOOL AS $$
+DECLARE
+ circ action.circulation%ROWTYPE;
+ e_closing action.emergency_closing%ROWTYPE;
+ e_c_circ action.emergency_closing_circulation%ROWTYPE;
+ closing actor.org_unit_closed%ROWTYPE;
+ adjacent actor.org_unit_closed%ROWTYPE;
+ bill money.billing%ROWTYPE;
+ last_bill money.billing%ROWTYPE;
+ day_number INT;
+ hoo_close TIME WITHOUT TIME ZONE;
+ plus_days INT;
+ avoid_negative BOOL;
+ extend_grace BOOL;
+ new_due_date TEXT;
+BEGIN
+ -- Gather objects involved
+ SELECT * INTO e_c_circ
+ FROM action.emergency_closing_circulation
+ WHERE id = circ_closing_entry;
+
+ IF e_c_circ.process_time IS NOT NULL THEN
+ -- Already processed ... moving on
+ RETURN FALSE;
+ END IF;
+
+ SELECT * INTO e_closing
+ FROM action.emergency_closing
+ WHERE id = e_c_circ.emergency_closing;
+
+ IF e_closing.process_start_time IS NULL THEN
+ -- Huh... that's odd. And wrong.
+ RETURN FALSE;
+ END IF;
+
+ SELECT * INTO closing
+ FROM actor.org_unit_closed
+ WHERE emergency_closing = e_closing.id;
+
+ SELECT * INTO circ
+ FROM action.circulation
+ WHERE id = e_c_circ.circulation;
+
+ -- Record the processing
+ UPDATE action.emergency_closing_circulation
+ SET original_due_date = circ.due_date,
+ process_time = NOW()
+ WHERE id = circ_closing_entry;
+
+ UPDATE action.emergency_closing
+ SET last_update_time = NOW()
+ WHERE id = e_closing.id;
+
+ SELECT value::BOOL INTO avoid_negative FROM actor.org_unit_ancestor_setting('bill.prohibit_negative_balance_on_overdues', circ.circ_lib);
+ SELECT value::BOOL INTO extend_grace FROM actor.org_unit_ancestor_setting('circ.grace.extend', circ.circ_lib);
+
+ new_due_date := evergreen.find_next_open_time( closing.org_unit, circ.due_date, EXTRACT(EPOCH FROM circ.duration)::INT % 86400 > 0 )::TEXT;
+ UPDATE action.circulation SET due_date = new_due_date::TIMESTAMPTZ WHERE id = circ.id;
+
+ -- Now, see if we need to get rid of some fines
+ SELECT * INTO last_bill
+ FROM money.billing b
+ WHERE b.xact = circ.id
+ AND NOT b.voided
+ AND b.btype = 1
+ ORDER BY billing_ts DESC
+ LIMIT 1;
+
+ FOR bill IN
+ SELECT *
+ FROM money.billing b
+ WHERE b.xact = circ.id
+ AND b.btype = 1
+ AND NOT b.voided
+ AND (
+ b.billing_ts BETWEEN closing.close_start AND new_due_date::TIMESTAMPTZ
+ OR (extend_grace AND last_bill.billing_ts <= new_due_date::TIMESTAMPTZ + circ.grace_period)
+ )
+ AND NOT EXISTS (SELECT 1 FROM money.account_adjustment a WHERE a.billing = b.id)
+ ORDER BY billing_ts
+ LOOP
+ IF avoid_negative THEN
+ PERFORM FROM money.materialized_billable_xact_summary WHERE id = circ.id AND balanced_owd < bill.amount;
+ EXIT WHEN FOUND; -- We can't go negative, and voiding this bill would do that...
+ END IF;
+
+ UPDATE money.billing
+ SET voided = TRUE,
+ void_time = NOW(),
+ note = COALESCE(note,'') || ' :: Voided by emergency closing handler'
+ WHERE id = bill.id;
+ END LOOP;
+
+ RETURN TRUE;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION action.emergency_closing_stage_2_reservation ( res_closing_entry INT )
+ RETURNS BOOL AS $$
+DECLARE
+ res booking.reservation%ROWTYPE;
+ e_closing action.emergency_closing%ROWTYPE;
+ e_c_res action.emergency_closing_reservation%ROWTYPE;
+ closing actor.org_unit_closed%ROWTYPE;
+ adjacent actor.org_unit_closed%ROWTYPE;
+ bill money.billing%ROWTYPE;
+ day_number INT;
+ hoo_close TIME WITHOUT TIME ZONE;
+ plus_days INT;
+ avoid_negative BOOL;
+ new_due_date TEXT;
+BEGIN
+ -- Gather objects involved
+ SELECT * INTO e_c_res
+ FROM action.emergency_closing_reservation
+ WHERE id = res_closing_entry;
+
+ IF e_c_res.process_time IS NOT NULL THEN
+ -- Already processed ... moving on
+ RETURN FALSE;
+ END IF;
+
+ SELECT * INTO e_closing
+ FROM action.emergency_closing
+ WHERE id = e_c_res.emergency_closing;
+
+ IF e_closing.process_start_time IS NULL THEN
+ -- Huh... that's odd. And wrong.
+ RETURN FALSE;
+ END IF;
+
+ SELECT * INTO closing
+ FROM actor.org_unit_closed
+ WHERE emergency_closing = e_closing.id;
+
+ SELECT * INTO res
+ FROM booking.reservation
+ WHERE id = e_c_res.reservation;
+
+ IF res.pickup_lib IS NULL THEN -- Need to be far enough along to have a pickup lib
+ RETURN FALSE;
+ END IF;
+
+ -- Record the processing
+ UPDATE action.emergency_closing_reservation
+ SET original_end_time = res.end_time,
+ process_time = NOW()
+ WHERE id = res_closing_entry;
+
+ UPDATE action.emergency_closing
+ SET last_update_time = NOW()
+ WHERE id = e_closing.id;
+
+ SELECT value::BOOL INTO avoid_negative FROM actor.org_unit_ancestor_setting('bill.prohibit_negative_balance_on_overdues', res.pickup_lib);
+
+ new_due_date := evergreen.find_next_open_time( closing.org_unit, res.end_time, EXTRACT(EPOCH FROM res.booking_interval)::INT % 86400 > 0 )::TEXT;
+ UPDATE booking.reservation SET end_time = new_due_date::TIMESTAMPTZ WHERE id = res.id;
+
+ -- Now, see if we need to get rid of some fines
+ FOR bill IN
+ SELECT *
+ FROM money.billing b
+ WHERE b.xact = res.id
+ AND b.btype = 1
+ AND NOT b.voided
+ AND b.billing_ts BETWEEN closing.close_start AND new_due_date::TIMESTAMPTZ
+ AND NOT EXISTS (SELECT 1 FROM money.account_adjustment a WHERE a.billing = b.id)
+ LOOP
+ IF avoid_negative THEN
+ PERFORM FROM money.materialized_billable_xact_summary WHERE id = res.id AND balanced_owd < bill.amount;
+ EXIT WHEN FOUND; -- We can't go negative, and voiding this bill would do that...
+ END IF;
+
+ UPDATE money.billing
+ SET voided = TRUE,
+ void_time = NOW(),
+ note = COALESCE(note,'') || ' :: Voided by emergency closing handler'
+ WHERE id = bill.id;
+ END LOOP;
+
+ RETURN TRUE;
+END;
+$$ LANGUAGE PLPGSQL;
+
+
+
+CREATE TYPE actor.cascade_setting_summary AS (
+ name TEXT,
+ value JSON,
+ has_org_setting BOOLEAN,
+ has_user_setting BOOLEAN,
+ has_workstation_setting BOOLEAN
+);
+
+SELECT evergreen.upgrade_deps_block_check('1116', :eg_version);
+
+CREATE TABLE config.workstation_setting_type (
+ name TEXT PRIMARY KEY,
+ label TEXT UNIQUE NOT NULL,
+ grp TEXT REFERENCES config.settings_group (name),
+ description TEXT,
+ datatype TEXT NOT NULL DEFAULT 'string',
+ fm_class TEXT,
+ --
+ -- define valid datatypes
+ --
+ CONSTRAINT cwst_valid_datatype CHECK ( datatype IN
+ ( 'bool', 'integer', 'float', 'currency', 'interval',
+ 'date', 'string', 'object', 'array', 'link' ) ),
+ --
+ -- fm_class is meaningful only for 'link' datatype
+ --
+ CONSTRAINT cwst_no_empty_link CHECK
+ ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
+ ( datatype <> 'link' AND fm_class IS NULL ) )
+);
+
+CREATE TABLE actor.workstation_setting (
+ id SERIAL PRIMARY KEY,
+ workstation INT NOT NULL REFERENCES actor.workstation (id)
+ ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ name TEXT NOT NULL REFERENCES config.workstation_setting_type (name)
+ ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ value JSON NOT NULL
+);
+
+
+CREATE INDEX actor_workstation_setting_workstation_idx
+ ON actor.workstation_setting (workstation);
+
+CREATE OR REPLACE FUNCTION config.setting_is_user_or_ws()
+RETURNS TRIGGER AS $FUNC$
+BEGIN
+
+ IF TG_TABLE_NAME = 'usr_setting_type' THEN
+ PERFORM TRUE FROM config.workstation_setting_type cwst
+ WHERE cwst.name = NEW.name;
+ IF NOT FOUND THEN
+ RETURN NULL;
+ END IF;
+ END IF;
+
+ IF TG_TABLE_NAME = 'workstation_setting_type' THEN
+ PERFORM TRUE FROM config.usr_setting_type cust
+ WHERE cust.name = NEW.name;
+ IF NOT FOUND THEN
+ RETURN NULL;
+ END IF;
+ END IF;
+
+ RAISE EXCEPTION
+ '% Cannot be used as both a user setting and a workstation setting.',
+ NEW.name;
+END;
+$FUNC$ LANGUAGE PLPGSQL STABLE;
+
+CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws
+ AFTER INSERT OR UPDATE ON config.usr_setting_type
+ FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws();
+
+CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws
+ AFTER INSERT OR UPDATE ON config.workstation_setting_type
+ FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws();
+
+CREATE OR REPLACE FUNCTION actor.get_cascade_setting(
+ setting_name TEXT, org_id INT, user_id INT, workstation_id INT)
+ RETURNS actor.cascade_setting_summary AS
+$FUNC$
+DECLARE
+ setting_value JSON;
+ summary actor.cascade_setting_summary;
+ org_setting_type config.org_unit_setting_type%ROWTYPE;
+BEGIN
+
+ summary.name := setting_name;
+
+ -- Collect the org setting type status first in case we exit early.
+ -- The existance of an org setting type is not considered
+ -- privileged information.
+ SELECT INTO org_setting_type *
+ FROM config.org_unit_setting_type WHERE name = setting_name;
+ IF FOUND THEN
+ summary.has_org_setting := TRUE;
+ ELSE
+ summary.has_org_setting := FALSE;
+ END IF;
+
+ -- User and workstation settings have the same priority.
+ -- Start with user settings since that's the simplest code path.
+ -- The workstation_id is ignored if no user_id is provided.
+ IF user_id IS NOT NULL THEN
+
+ SELECT INTO summary.value value FROM actor.usr_setting
+ WHERE usr = user_id AND name = setting_name;
+
+ IF FOUND THEN
+ -- if we have a value, we have a setting type
+ summary.has_user_setting := TRUE;
+
+ IF workstation_id IS NOT NULL THEN
+ -- Only inform the caller about the workstation
+ -- setting type disposition when a workstation id is
+ -- provided. Otherwise, it's NULL to indicate UNKNOWN.
+ summary.has_workstation_setting := FALSE;
+ END IF;
+
+ RETURN summary;
+ END IF;
+
+ -- no user setting value, but a setting type may exist
+ SELECT INTO summary.has_user_setting EXISTS (
+ SELECT TRUE FROM config.usr_setting_type
+ WHERE name = setting_name
+ );
+
+ IF workstation_id IS NOT NULL THEN
+
+ IF NOT summary.has_user_setting THEN
+ -- A workstation setting type may only exist when a user
+ -- setting type does not.
+
+ SELECT INTO summary.value value
+ FROM actor.workstation_setting
+ WHERE workstation = workstation_id AND name = setting_name;
+
+ IF FOUND THEN
+ -- if we have a value, we have a setting type
+ summary.has_workstation_setting := TRUE;
+ RETURN summary;
+ END IF;
+
+ -- no value, but a setting type may exist
+ SELECT INTO summary.has_workstation_setting EXISTS (
+ SELECT TRUE FROM config.workstation_setting_type
+ WHERE name = setting_name
+ );
+ END IF;
+
+ -- Finally make use of the workstation to determine the org
+ -- unit if none is provided.
+ IF org_id IS NULL AND summary.has_org_setting THEN
+ SELECT INTO org_id owning_lib
+ FROM actor.workstation WHERE id = workstation_id;
+ END IF;
+ END IF;
+ END IF;
+
+ -- Some org unit settings are protected by a view permission.
+ -- First see if we have any data that needs protecting, then
+ -- check the permission if needed.
+
+ IF NOT summary.has_org_setting THEN
+ RETURN summary;
+ END IF;
+
+ -- avoid putting the value into the summary until we confirm
+ -- the value should be visible to the caller.
+ SELECT INTO setting_value value
+ FROM actor.org_unit_ancestor_setting(setting_name, org_id);
+
+ IF NOT FOUND THEN
+ -- No value found -- perm check is irrelevant.
+ RETURN summary;
+ END IF;
+
+ IF org_setting_type.view_perm IS NOT NULL THEN
+
+ IF user_id IS NULL THEN
+ RAISE NOTICE 'Perm check required but no user_id provided';
+ RETURN summary;
+ END IF;
+
+ IF NOT permission.usr_has_perm(
+ user_id, (SELECT code FROM permission.perm_list
+ WHERE id = org_setting_type.view_perm), org_id)
+ THEN
+ RAISE NOTICE 'Perm check failed for user % on %',
+ user_id, org_setting_type.view_perm;
+ RETURN summary;
+ END IF;
+ END IF;
+
+ -- Perm check succeeded or was not necessary.
+ summary.value := setting_value;
+ RETURN summary;
+END;
+$FUNC$ LANGUAGE PLPGSQL;
+
+
+CREATE OR REPLACE FUNCTION actor.get_cascade_setting_batch(
+ setting_names TEXT[], org_id INT, user_id INT, workstation_id INT)
+ RETURNS SETOF actor.cascade_setting_summary AS
+$FUNC$
+-- Returns a row per setting matching the setting name order. If no
+-- value is applied, NULL is returned to retain name-response ordering.
+DECLARE
+ setting_name TEXT;
+ summary actor.cascade_setting_summary;
+BEGIN
+ FOREACH setting_name IN ARRAY setting_names LOOP
+ SELECT INTO summary * FROM actor.get_cascade_setting(
+ setting_Name, org_id, user_id, workstation_id);
+ RETURN NEXT summary;
+ END LOOP;
+END;
+$FUNC$ LANGUAGE PLPGSQL;
+
+
+
+
+
+SELECT evergreen.upgrade_deps_block_check('1117', :eg_version);
+
+INSERT INTO permission.perm_list (id, code, description) VALUES
+ (608, 'APPLY_WORKSTATION_SETTING',
+ oils_i18n_gettext(608, 'APPLY_WORKSTATION_SETTING', 'ppl', 'description'));
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+ 'eg.circ.checkin.no_precat_alert', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.checkin.no_precat_alert',
+ 'Checkin: Ignore Precataloged Items',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.checkin.noop', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.checkin.noop',
+ 'Checkin: Suppress Holds and Transits',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.checkin.void_overdues', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.checkin.void_overdues',
+ 'Checkin: Amnesty Mode',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.checkin.auto_print_holds_transits', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.checkin.auto_print_holds_transits',
+ 'Checkin: Auto-Print Holds and Transits',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.checkin.clear_expired', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.checkin.clear_expired',
+ 'Checkin: Clear Holds Shelf',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.checkin.retarget_holds', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.checkin.retarget_holds',
+ 'Checkin: Retarget Local Holds',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.checkin.retarget_holds_all', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.checkin.retarget_holds_all',
+ 'Checkin: Retarget All Statuses',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.checkin.hold_as_transit', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.checkin.hold_as_transit',
+ 'Checkin: Capture Local Holds as Transits',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.checkin.manual_float', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.checkin.manual_float',
+ 'Checkin: Manual Floating Active',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.patron.summary.collapse', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.patron.summary.collapse',
+ 'Collaps Patron Summary Display',
+ 'cwst', 'label'
+ )
+), (
+ 'circ.bills.receiptonpay', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'circ.bills.receiptonpay',
+ 'Print Receipt On Payment',
+ 'cwst', 'label'
+ )
+), (
+ 'circ.renew.strict_barcode', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'circ.renew.strict_barcode',
+ 'Renew: Strict Barcode',
+ 'cwst', 'label'
+ )
+), (
+ 'circ.checkin.strict_barcode', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'circ.checkin.strict_barcode',
+ 'Checkin: Strict Barcode',
+ 'cwst', 'label'
+ )
+), (
+ 'circ.checkout.strict_barcode', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'circ.checkout.strict_barcode',
+ 'Checkout: Strict Barcode',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.holdings_show_copies', 'cat', 'bool',
+ oils_i18n_gettext(
+ 'cat.holdings_show_copies',
+ 'Holdings View Show Copies',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.holdings_show_empty', 'cat', 'bool',
+ oils_i18n_gettext(
+ 'cat.holdings_show_empty',
+ 'Holdings View Show Empty Volumes',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.holdings_show_empty_org', 'cat', 'bool',
+ oils_i18n_gettext(
+ 'cat.holdings_show_empty_org',
+ 'Holdings View Show Empty Orgs',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.holdings_show_vols', 'cat', 'bool',
+ oils_i18n_gettext(
+ 'cat.holdings_show_vols',
+ 'Holdings View Show Volumes',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.copy.defaults', 'cat', 'object',
+ oils_i18n_gettext(
+ 'cat.copy.defaults',
+ 'Copy Edit Default Values',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.printlabels.default_template', 'cat', 'string',
+ oils_i18n_gettext(
+ 'cat.printlabels.default_template',
+ 'Print Label Default Template',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.printlabels.templates', 'cat', 'object',
+ oils_i18n_gettext(
+ 'cat.printlabels.templates',
+ 'Print Label Templates',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.patron.search.include_inactive', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.patron.search.include_inactive',
+ 'Patron Search Include Inactive',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.circ.patron.search.show_extras', 'circ', 'bool',
+ oils_i18n_gettext(
+ 'eg.circ.patron.search.show_extras',
+ 'Patron Search Show Extra Search Options',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.checkin.checkin', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.checkin.checkin',
+ 'Grid Config: circ.checkin.checkin',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.checkin.capture', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.checkin.capture',
+ 'Grid Config: circ.checkin.capture',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.server.config.copy_tag_type', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.server.config.copy_tag_type',
+ 'Grid Config: admin.server.config.copy_tag_type',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.server.config.metabib_field_virtual_map.grid', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.server.config.metabib_field_virtual_map.grid',
+ 'Grid Config: admin.server.config.metabib_field_virtual_map.grid',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.server.config.metabib_field.grid', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.server.config.metabib_field.grid',
+ 'Grid Config: admin.server.config.metabib_field.grid',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.server.config.marc_field', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.server.config.marc_field',
+ 'Grid Config: admin.server.config.marc_field',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.server.asset.copy_tag', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.server.asset.copy_tag',
+ 'Grid Config: admin.server.asset.copy_tag',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.local.circ.neg_balance_users', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.local.circ.neg_balance_users',
+ 'Grid Config: admin.local.circ.neg_balance_users',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.local.rating.badge', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.local.rating.badge',
+ 'Grid Config: admin.local.rating.badge',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.workstation.work_log', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.workstation.work_log',
+ 'Grid Config: admin.workstation.work_log',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.workstation.patron_log', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.workstation.patron_log',
+ 'Grid Config: admin.workstation.patron_log',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.serials.pattern_template', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.admin.serials.pattern_template',
+ 'Grid Config: admin.serials.pattern_template',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.serials.copy_templates', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.serials.copy_templates',
+ 'Grid Config: serials.copy_templates',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.record_overlay.holdings', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.record_overlay.holdings',
+ 'Grid Config: cat.record_overlay.holdings',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.bucket.record.search', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.bucket.record.search',
+ 'Grid Config: cat.bucket.record.search',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.bucket.record.view', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.bucket.record.view',
+ 'Grid Config: cat.bucket.record.view',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.bucket.record.pending', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.bucket.record.pending',
+ 'Grid Config: cat.bucket.record.pending',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.bucket.copy.view', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.bucket.copy.view',
+ 'Grid Config: cat.bucket.copy.view',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.bucket.copy.pending', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.bucket.copy.pending',
+ 'Grid Config: cat.bucket.copy.pending',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.items', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.items',
+ 'Grid Config: cat.items',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.volcopy.copies', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.volcopy.copies',
+ 'Grid Config: cat.volcopy.copies',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.volcopy.copies.complete', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.volcopy.copies.complete',
+ 'Grid Config: cat.volcopy.copies.complete',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.peer_bibs', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.peer_bibs',
+ 'Grid Config: cat.peer_bibs',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.catalog.holds', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.catalog.holds',
+ 'Grid Config: cat.catalog.holds',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.holdings', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.holdings',
+ 'Grid Config: cat.holdings',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.z3950_results', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.z3950_results',
+ 'Grid Config: cat.z3950_results',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.holds.shelf', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.holds.shelf',
+ 'Grid Config: circ.holds.shelf',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.holds.pull', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.holds.pull',
+ 'Grid Config: circ.holds.pull',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.in_house_use', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.in_house_use',
+ 'Grid Config: circ.in_house_use',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.renew', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.renew',
+ 'Grid Config: circ.renew',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.transits.list', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.transits.list',
+ 'Grid Config: circ.transits.list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.patron.holds', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.patron.holds',
+ 'Grid Config: circ.patron.holds',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.pending_patrons.list', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.pending_patrons.list',
+ 'Grid Config: circ.pending_patrons.list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.patron.items_out.noncat', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.patron.items_out.noncat',
+ 'Grid Config: circ.patron.items_out.noncat',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.patron.items_out', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.patron.items_out',
+ 'Grid Config: circ.patron.items_out',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.patron.billhistory_payments', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.patron.billhistory_payments',
+ 'Grid Config: circ.patron.billhistory_payments',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.user.bucket.view', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.user.bucket.view',
+ 'Grid Config: user.bucket.view',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.user.bucket.pending', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.user.bucket.pending',
+ 'Grid Config: user.bucket.pending',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.patron.staff_messages', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.patron.staff_messages',
+ 'Grid Config: circ.patron.staff_messages',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.patron.archived_messages', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.patron.archived_messages',
+ 'Grid Config: circ.patron.archived_messages',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.patron.bills', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.patron.bills',
+ 'Grid Config: circ.patron.bills',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.patron.checkout', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.patron.checkout',
+ 'Grid Config: circ.patron.checkout',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.serials.mfhd_grid', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.serials.mfhd_grid',
+ 'Grid Config: serials.mfhd_grid',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.serials.view_item_grid', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.serials.view_item_grid',
+ 'Grid Config: serials.view_item_grid',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.serials.dist_stream_grid', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.serials.dist_stream_grid',
+ 'Grid Config: serials.dist_stream_grid',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.circ.patron.search', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.circ.patron.search',
+ 'Grid Config: circ.patron.search',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.cat.record.summary.collapse', 'gui', 'bool',
+ oils_i18n_gettext(
+ 'eg.cat.record.summary.collapse',
+ 'Collapse Bib Record Summary',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.marcedit.flateditor', 'gui', 'bool',
+ oils_i18n_gettext(
+ 'cat.marcedit.flateditor',
+ 'Use Flat MARC Editor',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.marcedit.stack_subfields', 'gui', 'bool',
+ oils_i18n_gettext(
+ 'cat.marcedit.stack_subfields',
+ 'MARC Editor Stack Subfields',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.offline.print_receipt', 'gui', 'bool',
+ oils_i18n_gettext(
+ 'eg.offline.print_receipt',
+ 'Offline Print Receipt',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.offline.strict_barcode', 'gui', 'bool',
+ oils_i18n_gettext(
+ 'eg.offline.strict_barcode',
+ 'Offline Use Strict Barcode',
+ 'cwst', 'label'
+ )
+), (
+ 'cat.default_bib_marc_template', 'gui', 'string',
+ oils_i18n_gettext(
+ 'cat.default_bib_marc_template',
+ 'Default MARC Template',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.audio.disable', 'gui', 'bool',
+ oils_i18n_gettext(
+ 'eg.audio.disable',
+ 'Disable Staff Client Notification Audio',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.search.adv_pane', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.search.adv_pane',
+ 'Catalog Advanced Search Default Pane',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.bills_current', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.bills_current',
+ 'Print Template Context: bills_current',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.bills_current', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.bills_current',
+ 'Print Template: bills_current',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.bills_historical', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.bills_historical',
+ 'Print Template Context: bills_historical',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.bills_historical', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.bills_historical',
+ 'Print Template: bills_historical',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.bill_payment', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.bill_payment',
+ 'Print Template Context: bill_payment',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.bill_payment', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.bill_payment',
+ 'Print Template: bill_payment',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.checkin', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.checkin',
+ 'Print Template Context: checkin',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.checkin', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.checkin',
+ 'Print Template: checkin',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.checkout', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.checkout',
+ 'Print Template Context: checkout',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.checkout', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.checkout',
+ 'Print Template: checkout',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.hold_transit_slip', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.hold_transit_slip',
+ 'Print Template Context: hold_transit_slip',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.hold_transit_slip', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.hold_transit_slip',
+ 'Print Template: hold_transit_slip',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.hold_shelf_slip', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.hold_shelf_slip',
+ 'Print Template Context: hold_shelf_slip',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.hold_shelf_slip', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.hold_shelf_slip',
+ 'Print Template: hold_shelf_slip',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.holds_for_bib', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.holds_for_bib',
+ 'Print Template Context: holds_for_bib',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.holds_for_bib', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.holds_for_bib',
+ 'Print Template: holds_for_bib',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.holds_for_patron', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.holds_for_patron',
+ 'Print Template Context: holds_for_patron',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.holds_for_patron', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.holds_for_patron',
+ 'Print Template: holds_for_patron',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.hold_pull_list', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.hold_pull_list',
+ 'Print Template Context: hold_pull_list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.hold_pull_list', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.hold_pull_list',
+ 'Print Template: hold_pull_list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.hold_shelf_list', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.hold_shelf_list',
+ 'Print Template Context: hold_shelf_list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.hold_shelf_list', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.hold_shelf_list',
+ 'Print Template: hold_shelf_list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.in_house_use_list', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.in_house_use_list',
+ 'Print Template Context: in_house_use_list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.in_house_use_list', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.in_house_use_list',
+ 'Print Template: in_house_use_list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.item_status', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.item_status',
+ 'Print Template Context: item_status',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.item_status', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.item_status',
+ 'Print Template: item_status',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.items_out', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.items_out',
+ 'Print Template Context: items_out',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.items_out', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.items_out',
+ 'Print Template: items_out',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.patron_address', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.patron_address',
+ 'Print Template Context: patron_address',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.patron_address', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.patron_address',
+ 'Print Template: patron_address',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.patron_data', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.patron_data',
+ 'Print Template Context: patron_data',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.patron_data', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.patron_data',
+ 'Print Template: patron_data',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.patron_note', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.patron_note',
+ 'Print Template Context: patron_note',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.patron_note', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.patron_note',
+ 'Print Template: patron_note',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.renew', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.renew',
+ 'Print Template Context: renew',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.renew', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.renew',
+ 'Print Template: renew',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.transit_list', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.transit_list',
+ 'Print Template Context: transit_list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.transit_list', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.transit_list',
+ 'Print Template: transit_list',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.transit_slip', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.transit_slip',
+ 'Print Template Context: transit_slip',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.transit_slip', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.transit_slip',
+ 'Print Template: transit_slip',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.offline_checkout', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.offline_checkout',
+ 'Print Template Context: offline_checkout',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.offline_checkout', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.offline_checkout',
+ 'Print Template: offline_checkout',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.offline_renew', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.offline_renew',
+ 'Print Template Context: offline_renew',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.offline_renew', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.offline_renew',
+ 'Print Template: offline_renew',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.offline_checkin', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.offline_checkin',
+ 'Print Template Context: offline_checkin',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.offline_checkin', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.offline_checkin',
+ 'Print Template: offline_checkin',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template_context.offline_in_house_use', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template_context.offline_in_house_use',
+ 'Print Template Context: offline_in_house_use',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.print.template.offline_in_house_use', 'gui', 'string',
+ oils_i18n_gettext(
+ 'eg.print.template.offline_in_house_use',
+ 'Print Template: offline_in_house_use',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.serials.stream_names', 'gui', 'array',
+ oils_i18n_gettext(
+ 'eg.serials.stream_names',
+ 'Serials Local Stream Names',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.serials.items.do_print_routing_lists', 'gui', 'bool',
+ oils_i18n_gettext(
+ 'eg.serials.items.do_print_routing_lists',
+ 'Serials Print Routing Lists',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.serials.items.receive_and_barcode', 'gui', 'bool',
+ oils_i18n_gettext(
+ 'eg.serials.items.receive_and_barcode',
+ 'Serials Barcode On Receive',
+ 'cwst', 'label'
+ )
+);
+
+
+-- More values with fm_class'es
+INSERT INTO config.workstation_setting_type (name, grp, datatype, fm_class, label)
+VALUES (
+ 'eg.search.search_lib', 'gui', 'link', 'aou',
+ oils_i18n_gettext(
+ 'eg.search.search_lib',
+ 'Staff Catalog Default Search Library',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.search.pref_lib', 'gui', 'link', 'aou',
+ oils_i18n_gettext(
+ 'eg.search.pref_lib',
+ 'Staff Catalog Preferred Library',
+ 'cwst', 'label'
+ )
+);
+
+
+
+
+
+SELECT evergreen.upgrade_deps_block_check('1118', :eg_version);
+
+UPDATE action_trigger.event_definition
+SET template =
+$$
+[%- USE date -%]
+[%- SET user = target.0.owner -%]
+To: [%- params.recipient_email || user.email %]
+From: [%- params.sender_email || default_sender %]
+Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %]
+Subject: Bibliographic Records
+Auto-Submitted: auto-generated
+
+[% FOR cbreb IN target %]
+[% FOR item IN cbreb.items;
+ bre_id = item.target_biblio_record_entry;
+
+ bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
+ title = '';
+ FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
+ title = title _ part.textContent;
+ END;
+
+ author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
+ item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
+ publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
+ pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
+ isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
+ issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
+ upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
+%]
+
+[% loop.count %]/[% loop.size %]. Bib ID# [% bre_id %]
+[% IF isbn %]ISBN: [% isbn _ "\n" %][% END -%]
+[% IF issn %]ISSN: [% issn _ "\n" %][% END -%]
+[% IF upc %]UPC: [% upc _ "\n" %] [% END -%]
+Title: [% title %]
+Author: [% author %]
+Publication Info: [% publisher %] [% pubdate %]
+Item Type: [% item_type %]
+
+[% END %]
+[% END %]
+$$
+WHERE hook = 'biblio.format.record_entry.email'
+-- from previous stock definition
+AND MD5(template) = 'ee4e6c1b3049086c570c7a77413d46c1';
+
+UPDATE action_trigger.event_definition
+SET template =
+$$
+<div>
+ <style> li { padding: 8px; margin 5px; }</style>
+ <ol>
+ [% FOR cbreb IN target %]
+ [% FOR item IN cbreb.items;
+ bre_id = item.target_biblio_record_entry;
+
+ bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
+ title = '';
+ FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
+ title = title _ part.textContent;
+ END;
+
+ author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
+ item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
+ publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
+ pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
+ isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
+ %]
+
+ <li>
+ Bib ID# [% bre_id %] ISBN: [% isbn %]<br />
+ Title: [% title %]<br />
+ Author: [% author %]<br />
+ Publication Info: [% publisher %] [% pubdate %]<br/>
+ Item Type: [% item_type %]
+ </li>
+ [% END %]
+ [% END %]
+ </ol>
+</div>
+$$
+WHERE hook = 'biblio.format.record_entry.print'
+-- from previous stock definition
+AND MD5(template) = '9ada7ea8417cb23f89d0dc8f15ec68d0';
+
+
+SELECT evergreen.upgrade_deps_block_check('1120', :eg_version);
+
+--Only insert if the attributes are not already present
+
+INSERT INTO config.z3950_attr (source, name, label, code, format, truncation)
+SELECT 'oclc','upc','UPC','1007','6','0'
+WHERE NOT EXISTS (SELECT name FROM config.z3950_attr WHERE source = 'oclc' AND name = 'upc');
+
+INSERT INTO config.z3950_attr (source, name, label, code, format, truncation)
+SELECT 'loc','upc','UPC','1007','1','1'
+WHERE NOT EXISTS (SELECT name FROM config.z3950_attr WHERE source = 'loc' AND name = 'upc');
+
+SELECT evergreen.upgrade_deps_block_check('1121', :eg_version);
+
+CREATE TABLE permission.grp_tree_display_entry (
+ id SERIAL PRIMARY KEY,
+ position INTEGER NOT NULL,
+ org INTEGER NOT NULL REFERENCES actor.org_unit (id)
+ DEFERRABLE INITIALLY DEFERRED,
+ grp INTEGER NOT NULL REFERENCES permission.grp_tree (id)
+ DEFERRABLE INITIALLY DEFERRED,
+ CONSTRAINT pgtde_once_per_org UNIQUE (org, grp)
+);
+
+ALTER TABLE permission.grp_tree_display_entry
+ ADD COLUMN parent integer REFERENCES permission.grp_tree_display_entry (id)
+ DEFERRABLE INITIALLY DEFERRED;
+
+INSERT INTO permission.perm_list (id, code, description)
+VALUES (609, 'MANAGE_CUSTOM_PERM_GRP_TREE', oils_i18n_gettext( 609,
+ 'Allows a user to manage custom permission group lists.', 'ppl', 'description' ));
+
+
+SELECT evergreen.upgrade_deps_block_check('1122', :eg_version);
+
+ALTER TABLE actor.usr
+ ADD COLUMN pref_prefix TEXT,
+ ADD COLUMN pref_first_given_name TEXT,
+ ADD COLUMN pref_second_given_name TEXT,
+ ADD COLUMN pref_family_name TEXT,
+ ADD COLUMN pref_suffix TEXT,
+ ADD COLUMN name_keywords TEXT,
+ ADD COLUMN name_kw_tsvector TSVECTOR;
+
+ALTER TABLE staging.user_stage
+ ADD COLUMN pref_first_given_name TEXT,
+ ADD COLUMN pref_second_given_name TEXT,
+ ADD COLUMN pref_family_name TEXT;
+
+CREATE INDEX actor_usr_pref_first_given_name_idx
+ ON actor.usr (evergreen.lowercase(pref_first_given_name));
+CREATE INDEX actor_usr_pref_second_given_name_idx
+ ON actor.usr (evergreen.lowercase(pref_second_given_name));
+CREATE INDEX actor_usr_pref_family_name_idx
+ ON actor.usr (evergreen.lowercase(pref_family_name));
+CREATE INDEX actor_usr_pref_first_given_name_unaccent_idx
+ ON actor.usr (evergreen.unaccent_and_squash(pref_first_given_name));
+CREATE INDEX actor_usr_pref_second_given_name_unaccent_idx
+ ON actor.usr (evergreen.unaccent_and_squash(pref_second_given_name));
+CREATE INDEX actor_usr_pref_family_name_unaccent_idx
+ ON actor.usr (evergreen.unaccent_and_squash(pref_family_name));
+
+-- Update keyword indexes for existing patrons
+
+UPDATE actor.usr SET name_kw_tsvector =
+ TO_TSVECTOR(
+ COALESCE(prefix, '') || ' ' ||
+ COALESCE(first_given_name, '') || ' ' ||
+ COALESCE(evergreen.unaccent_and_squash(first_given_name), '') || ' ' ||
+ COALESCE(second_given_name, '') || ' ' ||
+ COALESCE(evergreen.unaccent_and_squash(second_given_name), '') || ' ' ||
+ COALESCE(family_name, '') || ' ' ||
+ COALESCE(evergreen.unaccent_and_squash(family_name), '') || ' ' ||
+ COALESCE(suffix, '')
+ );
+
+CREATE OR REPLACE FUNCTION actor.user_ingest_name_keywords()
+ RETURNS TRIGGER AS $func$
+BEGIN
+ NEW.name_kw_tsvector := TO_TSVECTOR(
+ COALESCE(NEW.prefix, '') || ' ' ||
+ COALESCE(NEW.first_given_name, '') || ' ' ||
+ COALESCE(evergreen.unaccent_and_squash(NEW.first_given_name), '') || ' ' ||
+ COALESCE(NEW.second_given_name, '') || ' ' ||
+ COALESCE(evergreen.unaccent_and_squash(NEW.second_given_name), '') || ' ' ||
+ COALESCE(NEW.family_name, '') || ' ' ||
+ COALESCE(evergreen.unaccent_and_squash(NEW.family_name), '') || ' ' ||
+ COALESCE(NEW.suffix, '') || ' ' ||
+ COALESCE(NEW.pref_prefix, '') || ' ' ||
+ COALESCE(NEW.pref_first_given_name, '') || ' ' ||
+ COALESCE(evergreen.unaccent_and_squash(NEW.pref_first_given_name), '') || ' ' ||
+ COALESCE(NEW.pref_second_given_name, '') || ' ' ||
+ COALESCE(evergreen.unaccent_and_squash(NEW.pref_second_given_name), '') || ' ' ||
+ COALESCE(NEW.pref_family_name, '') || ' ' ||
+ COALESCE(evergreen.unaccent_and_squash(NEW.pref_family_name), '') || ' ' ||
+ COALESCE(NEW.pref_suffix, '') || ' ' ||
+ COALESCE(NEW.name_keywords, '')
+ );
+ RETURN NEW;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+-- Add after the batch upate above to avoid duplicate updates.
+CREATE TRIGGER user_ingest_name_keywords_tgr
+ BEFORE INSERT OR UPDATE ON actor.usr
+ FOR EACH ROW EXECUTE PROCEDURE actor.user_ingest_name_keywords();
+
+
+-- merge pref names from source user to target user, except when
+-- clobbering existing pref names.
+CREATE OR REPLACE FUNCTION actor.usr_merge(src_usr INT, dest_usr INT,
+ del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN )
+ RETURNS VOID AS $$
+DECLARE
+ suffix TEXT;
+ bucket_row RECORD;
+ picklist_row RECORD;
+ queue_row RECORD;
+ folder_row RECORD;
+BEGIN
+
+ -- do some initial cleanup
+ UPDATE actor.usr SET card = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
+
+ -- actor.*
+ IF del_cards THEN
+ DELETE FROM actor.card where usr = src_usr;
+ ELSE
+ IF deactivate_cards THEN
+ UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
+ END IF;
+ UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+
+ IF del_addrs THEN
+ DELETE FROM actor.usr_address WHERE usr = src_usr;
+ ELSE
+ UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+ UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
+ -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
+ UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
+ PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
+
+ -- permission.*
+ PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
+
+
+ -- container.*
+
+ -- For each *_bucket table: transfer every bucket belonging to src_usr
+ -- into the custody of dest_usr.
+ --
+ -- In order to avoid colliding with an existing bucket owned by
+ -- the destination user, append the source user's id (in parenthesese)
+ -- to the name. If you still get a collision, add successive
+ -- spaces to the name and keep trying until you succeed.
+ --
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.biblio_record_entry_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.biblio_record_entry_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.call_number_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.call_number_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.copy_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.copy_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.user_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.user_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
+
+ -- vandelay.*
+ -- transfer queues the same way we transfer buckets (see above)
+ FOR queue_row in
+ SELECT id, name
+ FROM vandelay.queue
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE vandelay.queue
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = queue_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ -- money.*
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
+ UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
+ UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
+
+ -- action.*
+ UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
+ UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
+ UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
+
+ UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
+ UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
+ UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
+
+ UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
+ UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
+
+ -- acq.*
+ UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
+ UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
+
+ -- transfer picklists the same way we transfer buckets (see above)
+ FOR picklist_row in
+ SELECT id, name
+ FROM acq.picklist
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE acq.picklist
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = picklist_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
+ UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
+
+ -- asset.*
+ UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
+
+ -- serial.*
+ UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
+
+ -- reporter.*
+ -- It's not uncommon to define the reporter schema in a replica
+ -- DB only, so don't assume these tables exist in the write DB.
+ BEGIN
+ UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.template_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.template_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.report_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.report_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.output_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.output_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- propagate preferred name values from the source user to the
+ -- destination user, but only when values are not being replaced.
+ WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
+ UPDATE actor.usr SET
+ pref_prefix =
+ COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
+ pref_first_given_name =
+ COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
+ pref_second_given_name =
+ COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
+ pref_family_name =
+ COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
+ pref_suffix =
+ COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
+ WHERE id = dest_usr;
+
+ -- Copy and deduplicate name keywords
+ -- String -> array -> rows -> DISTINCT -> array -> string
+ WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
+ dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
+ UPDATE actor.usr SET name_keywords = (
+ WITH keywords AS (
+ SELECT DISTINCT UNNEST(
+ REGEXP_SPLIT_TO_ARRAY(
+ COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
+ COALESCE((SELECT name_keywords FROM dusr), ''), E'\\s+'
+ )
+ ) AS parts
+ ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
+ ) WHERE id = dest_usr;
+
+ -- Finally, delete the source user
+ DELETE FROM actor.usr WHERE id = src_usr;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+
+
+SELECT evergreen.upgrade_deps_block_check('1123', :eg_version);
+
+ ALTER TABLE config.rule_circ_duration
+ ADD column max_auto_renewals INTEGER;
+
+ ALTER TABLE action.circulation
+ ADD column auto_renewal BOOLEAN;
+
+ ALTER TABLE action.circulation
+ ADD column auto_renewal_remaining INTEGER;
+
+ ALTER TABLE action.aged_circulation
+ ADD column auto_renewal BOOLEAN;
+
+ ALTER TABLE action.aged_circulation
+ ADD column auto_renewal_remaining INTEGER;
+
+ INSERT INTO action_trigger.validator values('CircIsAutoRenewable', 'Checks whether the circulation is able to be autorenewed.');
+ INSERT INTO action_trigger.reactor values('Circ::AutoRenew', 'Auto-Renews a circulation.');
+ INSERT INTO action_trigger.hook(key, core_type, description) values('autorenewal', 'circ', 'Item was auto-renewed to patron.');
+
+ -- AutoRenewer A/T Def:
+ INSERT INTO action_trigger.event_definition(active, owner, name, hook, validator, reactor, delay, max_delay, delay_field, group_field)
+ values (false, 1, 'Autorenew', 'checkout.due', 'CircIsOpen', 'Circ::AutoRenew', '-23 hours'::interval,'-1 minute'::interval, 'due_date', 'usr');
+
+ -- AutoRenewal outcome Email notifier A/T Def:
+ INSERT INTO action_trigger.event_definition(active, owner, name, hook, validator, reactor, group_field, template)
+ values (false, 1, 'AutorenewNotify', 'autorenewal', 'NOOP_True', 'SendEmail', 'usr',
+$$
+[%- USE date -%]
+[%- user = target.0.usr -%]
+To: [%- params.recipient_email || user.email %]
+From: [%- params.sender_email || default_sender %]
+Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %]
+Subject: Items Out Auto-Renewal Notification
+Auto-Submitted: auto-generated
+
+Dear [% user.family_name %], [% user.first_given_name %]
+An automatic renewal attempt was made for the following items:
+
+[% FOR circ IN target %]
+ [%- SET idx = loop.count - 1; SET udata = user_data.$idx -%]
+ [%- SET cid = circ.target_copy || udata.copy -%]
+ [%- SET copy_details = helpers.get_copy_bib_basics(cid) -%]
+ Item# [% loop.count %]
+ Title: [% copy_details.title %]
+ Author: [% copy_details.author %]
+ [%- IF udata.is_renewed %]
+ Status: Loan Renewed
+ New Due Date: [% date.format(helpers.format_date(udata.new_due_date), '%Y-%m-%d') %]
+ [%- ELSE %]
+ Status: Not Renewed
+ Reason: [% udata.reason %]
+ Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
+ [% END %]
+[% END %]
+$$
+ );
+
+ INSERT INTO action_trigger.environment (event_def, path ) VALUES
+ ( currval('action_trigger.event_definition_id_seq'), 'usr' ),
+ ( currval('action_trigger.event_definition_id_seq'), 'circ_lib' );
+
+
+DROP VIEW action.all_circulation;
+CREATE OR REPLACE VIEW action.all_circulation AS
+ SELECT id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
+ copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
+ circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
+ stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
+ max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
+ max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ,
+ auto_renewal, auto_renewal_remaining, NULL AS usr
+ FROM action.aged_circulation
+ UNION ALL
+ SELECT DISTINCT circ.id,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,
+ cp.call_number AS copy_call_number, circ.copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib,
+ cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
+ circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
+ circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
+ circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
+ circ.parent_circ, circ.auto_renewal, circ.auto_renewal_remaining, circ.usr
+ FROM action.circulation circ
+ JOIN asset.copy cp ON (circ.target_copy = cp.id)
+ JOIN asset.call_number cn ON (cp.call_number = cn.id)
+ JOIN actor.usr p ON (circ.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);
+
+
+DROP FUNCTION action.summarize_all_circ_chain (INTEGER);
+DROP FUNCTION action.all_circ_chain (INTEGER);
+
+-- rebuild slim circ view
+DROP VIEW action.all_circulation_slim;
+CREATE OR REPLACE VIEW action.all_circulation_slim AS
+ SELECT
+ id,
+ usr,
+ xact_start,
+ xact_finish,
+ unrecovered,
+ target_copy,
+ circ_lib,
+ circ_staff,
+ checkin_staff,
+ checkin_lib,
+ renewal_remaining,
+ grace_period,
+ due_date,
+ stop_fines_time,
+ checkin_time,
+ create_time,
+ duration,
+ fine_interval,
+ recurring_fine,
+ max_fine,
+ phone_renewal,
+ desk_renewal,
+ opac_renewal,
+ duration_rule,
+ recurring_fine_rule,
+ max_fine_rule,
+ stop_fines,
+ workstation,
+ checkin_workstation,
+ copy_location,
+ checkin_scan_time,
+ auto_renewal,
+ auto_renewal_remaining,
+ parent_circ
+ FROM action.circulation
+UNION ALL
+ SELECT
+ id,
+ NULL AS usr,
+ xact_start,
+ xact_finish,
+ unrecovered,
+ target_copy,
+ circ_lib,
+ circ_staff,
+ checkin_staff,
+ checkin_lib,
+ renewal_remaining,
+ grace_period,
+ due_date,
+ stop_fines_time,
+ checkin_time,
+ create_time,
+ duration,
+ fine_interval,
+ recurring_fine,
+ max_fine,
+ phone_renewal,
+ desk_renewal,
+ opac_renewal,
+ duration_rule,
+ recurring_fine_rule,
+ max_fine_rule,
+ stop_fines,
+ workstation,
+ checkin_workstation,
+ copy_location,
+ checkin_scan_time,
+ auto_renewal,
+ auto_renewal_remaining,
+ parent_circ
+ FROM action.aged_circulation
+;
+
+CREATE OR REPLACE FUNCTION action.all_circ_chain (ctx_circ_id INTEGER)
+ RETURNS SETOF action.all_circulation_slim AS $$
+DECLARE
+ tmp_circ action.all_circulation_slim%ROWTYPE;
+ circ_0 action.all_circulation_slim%ROWTYPE;
+BEGIN
+
+ SELECT INTO tmp_circ * FROM action.all_circulation_slim WHERE id = ctx_circ_id;
+
+ IF tmp_circ IS NULL THEN
+ RETURN NEXT tmp_circ;
+ END IF;
+ circ_0 := tmp_circ;
+
+ -- find the front of the chain
+ WHILE TRUE LOOP
+ SELECT INTO tmp_circ * FROM action.all_circulation_slim
+ WHERE id = tmp_circ.parent_circ;
+ IF tmp_circ IS NULL THEN
+ EXIT;
+ END IF;
+ circ_0 := tmp_circ;
+ END LOOP;
+
+ -- now send the circs to the caller, oldest to newest
+ tmp_circ := circ_0;
+ WHILE TRUE LOOP
+ IF tmp_circ IS NULL THEN
+ EXIT;
+ END IF;
+ RETURN NEXT tmp_circ;
+ SELECT INTO tmp_circ * FROM action.all_circulation_slim
+ WHERE parent_circ = tmp_circ.id;
+ END LOOP;
+
+END;
+$$ LANGUAGE 'plpgsql';
+
+-- same as action.summarize_circ_chain, but returns data collected
+-- from action.all_circulation, which may include aged circulations.
+CREATE OR REPLACE FUNCTION action.summarize_all_circ_chain
+ (ctx_circ_id INTEGER) RETURNS action.circ_chain_summary AS $$
+
+DECLARE
+
+ -- first circ in the chain
+ circ_0 action.all_circulation_slim%ROWTYPE;
+
+ -- last circ in the chain
+ circ_n action.all_circulation_slim%ROWTYPE;
+
+ -- circ chain under construction
+ chain action.circ_chain_summary;
+ tmp_circ action.all_circulation_slim%ROWTYPE;
+
+BEGIN
+
+ chain.num_circs := 0;
+ FOR tmp_circ IN SELECT * FROM action.all_circ_chain(ctx_circ_id) LOOP
+
+ IF chain.num_circs = 0 THEN
+ circ_0 := tmp_circ;
+ END IF;
+
+ chain.num_circs := chain.num_circs + 1;
+ circ_n := tmp_circ;
+ END LOOP;
+
+ chain.start_time := circ_0.xact_start;
+ chain.last_stop_fines := circ_n.stop_fines;
+ chain.last_stop_fines_time := circ_n.stop_fines_time;
+ chain.last_checkin_time := circ_n.checkin_time;
+ chain.last_checkin_scan_time := circ_n.checkin_scan_time;
+ SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
+ SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
+
+ IF chain.num_circs > 1 THEN
+ chain.last_renewal_time := circ_n.xact_start;
+ SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
+ END IF;
+
+ RETURN chain;
+
+END;
+$$ LANGUAGE 'plpgsql';
+
+
+
+SELECT evergreen.upgrade_deps_block_check('1124', :eg_version);
+
+INSERT into config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+ 'eg.grid.circ.wide_holds.shelf', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.circ.wide_holds.shelf',
+ 'Grid Config: circ.wide_holds.shelf',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.cat.catalog.wide_holds', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.catalog.wide_holds',
+ 'Grid Config: cat.catalog.wide_holds',
+ 'cwst', 'label'
+ )
+);
+
+DELETE from config.workstation_setting_type
+WHERE name = 'eg.grid.cat.catalog.holds' OR name = 'eg.grid.circ.holds.shelf';
+
+
+SELECT evergreen.upgrade_deps_block_check('1125', :eg_version);
+
+CREATE TABLE asset.latest_inventory (
+ id SERIAL PRIMARY KEY,
+ inventory_workstation INTEGER REFERENCES actor.workstation (id) DEFERRABLE INITIALLY DEFERRED,
+ inventory_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ copy BIGINT NOT NULL
+);
+CREATE INDEX latest_inventory_copy_idx ON asset.latest_inventory (copy);
+
+CREATE OR REPLACE FUNCTION evergreen.asset_latest_inventory_copy_inh_fkey() RETURNS TRIGGER AS $f$
+BEGIN
+ PERFORM 1 FROM asset.copy WHERE id = NEW.copy;
+ IF NOT FOUND THEN
+ RAISE foreign_key_violation USING MESSAGE = FORMAT(
+ $$Referenced asset.copy id not found, copy:%s$$, NEW.copy
+ );
+ END IF;
+ RETURN NEW;
+END;
+$f$ LANGUAGE PLPGSQL VOLATILE COST 50;
+
+CREATE CONSTRAINT TRIGGER inherit_asset_latest_inventory_copy_fkey
+ AFTER UPDATE OR INSERT ON asset.latest_inventory
+ DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_latest_inventory_copy_inh_fkey();
+
+INSERT into config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+ 'eg.circ.checkin.do_inventory_update', 'circ', 'bool',
+ oils_i18n_gettext (
+ 'eg.circ.checkin.do_inventory_update',
+ 'Checkin: Update Inventory',
+ 'cwst', 'label'
+ )
+);
+
+
+SELECT evergreen.upgrade_deps_block_check('1126', :eg_version);
+
+CREATE TABLE vandelay.session_tracker (
+ id BIGSERIAL PRIMARY KEY,
+
+ -- string of characters (e.g. md5) used for linking trackers
+ -- of different actions into a series. There can be multiple
+ -- session_keys of each action type, creating the opportunity
+ -- to link multiple action trackers into a single session.
+ session_key TEXT NOT NULL,
+
+ -- optional user-supplied name
+ name TEXT NOT NULL,
+
+ usr INTEGER NOT NULL REFERENCES actor.usr(id)
+ DEFERRABLE INITIALLY DEFERRED,
+
+ -- org unit can be derived from WS
+ workstation INTEGER NOT NULL REFERENCES actor.workstation(id)
+ ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+
+ -- bib/auth
+ record_type vandelay.bib_queue_queue_type NOT NULL DEFAULT 'bib',
+
+ -- Queue defines the source of the data, it does not necessarily
+ -- mean that an action is being performed against an entire queue.
+ -- E.g. some imports are misc. lists of record IDs, but they always
+ -- come from one queue.
+ -- No foreign key -- could be auth or bib queue.
+ queue BIGINT NOT NULL,
+
+ create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ update_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+
+ state TEXT NOT NULL DEFAULT 'active',
+
+ action_type TEXT NOT NULL DEFAULT 'enqueue', -- import
+
+ -- total number of tasks to perform / loosely defined
+ -- could be # of recs to import or # of recs + # of copies
+ -- depending on the import context
+ total_actions INTEGER NOT NULL DEFAULT 0,
+
+ -- total number of tasked performed so far
+ actions_performed INTEGER NOT NULL DEFAULT 0,
+
+ CONSTRAINT vand_tracker_valid_state
+ CHECK (state IN ('active','error','complete')),
+
+ CONSTRAINT vand_tracker_valid_action_type
+ CHECK (action_type IN ('upload', 'enqueue', 'import'))
+);
+
+
+CREATE OR REPLACE FUNCTION actor.usr_merge( src_usr INT, dest_usr INT, del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) RETURNS VOID AS $$
+DECLARE
+ suffix TEXT;
+ bucket_row RECORD;
+ picklist_row RECORD;
+ queue_row RECORD;
+ folder_row RECORD;
+BEGIN
+
+ -- do some initial cleanup
+ UPDATE actor.usr SET card = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
+
+ -- actor.*
+ IF del_cards THEN
+ DELETE FROM actor.card where usr = src_usr;
+ ELSE
+ IF deactivate_cards THEN
+ UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
+ END IF;
+ UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+
+ IF del_addrs THEN
+ DELETE FROM actor.usr_address WHERE usr = src_usr;
+ ELSE
+ UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+ UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
+ -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
+ UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
+ PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
+
+ -- permission.*
+ PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
+
+
+ -- container.*
+
+ -- For each *_bucket table: transfer every bucket belonging to src_usr
+ -- into the custody of dest_usr.
+ --
+ -- In order to avoid colliding with an existing bucket owned by
+ -- the destination user, append the source user's id (in parenthesese)
+ -- to the name. If you still get a collision, add successive
+ -- spaces to the name and keep trying until you succeed.
+ --
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.biblio_record_entry_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.biblio_record_entry_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.call_number_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.call_number_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.copy_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.copy_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.user_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.user_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
+
+ -- vandelay.*
+ -- transfer queues the same way we transfer buckets (see above)
+ FOR queue_row in
+ SELECT id, name
+ FROM vandelay.queue
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE vandelay.queue
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = queue_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
+
+ -- money.*
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
+ UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
+ UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
+
+ -- action.*
+ UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
+ UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
+ UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
+
+ UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
+ UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
+ UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
+
+ UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
+ UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
+
+ -- acq.*
+ UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
+ UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
+
+ -- transfer picklists the same way we transfer buckets (see above)
+ FOR picklist_row in
+ SELECT id, name
+ FROM acq.picklist
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE acq.picklist
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = picklist_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
+ UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
+
+ -- asset.*
+ UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
+
+ -- serial.*
+ UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
+
+ -- reporter.*
+ -- It's not uncommon to define the reporter schema in a replica
+ -- DB only, so don't assume these tables exist in the write DB.
+ BEGIN
+ UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.template_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.template_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.report_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.report_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.output_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.output_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- propagate preferred name values from the source user to the
+ -- destination user, but only when values are not being replaced.
+ WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
+ UPDATE actor.usr SET
+ pref_prefix =
+ COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
+ pref_first_given_name =
+ COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
+ pref_second_given_name =
+ COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
+ pref_family_name =
+ COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
+ pref_suffix =
+ COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
+ WHERE id = dest_usr;
+
+ -- Copy and deduplicate name keywords
+ -- String -> array -> rows -> DISTINCT -> array -> string
+ WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
+ dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
+ UPDATE actor.usr SET name_keywords = (
+ WITH keywords AS (
+ SELECT DISTINCT UNNEST(
+ REGEXP_SPLIT_TO_ARRAY(
+ COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
+ COALESCE((SELECT name_keywords FROM dusr), ''), E'\\s+'
+ )
+ ) AS parts
+ ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
+ ) WHERE id = dest_usr;
+
+ -- Finally, delete the source user
+ DELETE FROM actor.usr WHERE id = src_usr;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+CREATE OR REPLACE FUNCTION actor.usr_purge_data(
+ src_usr IN INTEGER,
+ specified_dest_usr IN INTEGER
+) RETURNS VOID AS $$
+DECLARE
+ suffix TEXT;
+ renamable_row RECORD;
+ dest_usr INTEGER;
+BEGIN
+
+ IF specified_dest_usr IS NULL THEN
+ dest_usr := 1; -- Admin user on stock installs
+ ELSE
+ dest_usr := specified_dest_usr;
+ END IF;
+
+ -- acq.*
+ UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
+ UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
+ UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
+ DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
+
+ -- Update with a rename to avoid collisions
+ FOR renamable_row in
+ SELECT id, name
+ FROM acq.picklist
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE acq.picklist
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
+ UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
+
+ -- action.*
+ DELETE FROM action.circulation WHERE usr = src_usr;
+ UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
+ UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
+ UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
+ UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
+ UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
+ DELETE FROM action.hold_request WHERE usr = src_usr;
+ UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
+ UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
+ DELETE FROM action.survey_response WHERE usr = src_usr;
+ UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
+ DELETE FROM action.usr_circ_history WHERE usr = src_usr;
+
+ -- actor.*
+ DELETE FROM actor.card WHERE usr = src_usr;
+ DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
+
+ -- The following update is intended to avoid transient violations of a foreign
+ -- key constraint, whereby actor.usr_address references itself. It may not be
+ -- necessary, but it does no harm.
+ UPDATE actor.usr_address SET replaces = NULL
+ WHERE usr = src_usr AND replaces IS NOT NULL;
+ DELETE FROM actor.usr_address WHERE usr = src_usr;
+ DELETE FROM actor.usr_note WHERE usr = src_usr;
+ UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
+ DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
+ UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
+ DELETE FROM actor.usr_setting WHERE usr = src_usr;
+ DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
+ UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
+
+ -- asset.*
+ UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
+
+ -- auditor.*
+ DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
+ DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
+ UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE auditor.asset_call_number_history SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE auditor.asset_copy_history SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE auditor.biblio_record_entry_history SET editor = dest_usr WHERE editor = src_usr;
+
+ -- biblio.*
+ UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
+
+ -- container.*
+ -- Update buckets with a rename to avoid collisions
+ FOR renamable_row in
+ SELECT id, name
+ FROM container.biblio_record_entry_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.biblio_record_entry_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR renamable_row in
+ SELECT id, name
+ FROM container.call_number_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.call_number_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR renamable_row in
+ SELECT id, name
+ FROM container.copy_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.copy_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR renamable_row in
+ SELECT id, name
+ FROM container.user_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.user_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
+
+ -- money.*
+ DELETE FROM money.billable_xact WHERE usr = src_usr;
+ DELETE FROM money.collections_tracker WHERE usr = src_usr;
+ UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
+
+ -- permission.*
+ DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
+ DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
+ DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
+ DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
+
+ -- reporter.*
+ -- Update with a rename to avoid collisions
+ BEGIN
+ FOR renamable_row in
+ SELECT id, name
+ FROM reporter.output_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.output_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ BEGIN
+ UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- Update with a rename to avoid collisions
+ BEGIN
+ FOR renamable_row in
+ SELECT id, name
+ FROM reporter.report_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.report_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ BEGIN
+ UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ BEGIN
+ UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- Update with a rename to avoid collisions
+ BEGIN
+ FOR renamable_row in
+ SELECT id, name
+ FROM reporter.template_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.template_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- vandelay.*
+ -- Update with a rename to avoid collisions
+ FOR renamable_row in
+ SELECT id, name
+ FROM vandelay.queue
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE vandelay.queue
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
+
+ -- NULL-ify addresses last so other cleanup (e.g. circ anonymization)
+ -- can access the information before deletion.
+ UPDATE actor.usr SET
+ active = FALSE,
+ card = NULL,
+ mailing_address = NULL,
+ billing_address = NULL
+ WHERE id = src_usr;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+
+SELECT evergreen.upgrade_deps_block_check('1127', :eg_version);
+
+ALTER TABLE acq.user_request ADD COLUMN cancel_time TIMESTAMPTZ;
+ALTER TABLE acq.user_request ADD COLUMN upc TEXT;
+ALTER TABLE action.hold_request ADD COLUMN acq_request INT REFERENCES acq.user_request (id);
+
+UPDATE
+ config.org_unit_setting_type
+SET
+ label = oils_i18n_gettext(
+ 'circ.holds.canceled.display_age',
+ 'Canceled holds/requests display age',
+ 'coust', 'label'),
+ description = oils_i18n_gettext(
+ 'circ.holds.canceled.display_age',
+ 'Show all canceled entries in patron holds and patron acquisition requests interfaces that were canceled within this amount of time',
+ 'coust', 'description')
+WHERE
+ name = 'circ.holds.canceled.display_age'
+;
+
+UPDATE
+ config.org_unit_setting_type
+SET
+ label = oils_i18n_gettext(
+ 'circ.holds.canceled.display_count',
+ 'Canceled holds/requests display count',
+ 'coust', 'label'),
+ description = oils_i18n_gettext(
+ 'circ.holds.canceled.display_count',
+ 'How many canceled entries to show in patron holds and patron acquisition requests interfaces',
+ 'coust', 'description')
+WHERE
+ name = 'circ.holds.canceled.display_count'
+;
+
+INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description)
+ VALUES (
+ 1, 'f', 1015,
+ oils_i18n_gettext(1015, 'Canceled: Fulfilled', 'acqcr', 'label'),
+ oils_i18n_gettext(1015, 'This acquisition request has been fulfilled.', 'acqcr', 'description')
+ )
+;
+
+UPDATE
+ acq.user_request_type
+SET
+ label = oils_i18n_gettext('2', 'Articles', 'aurt', 'label')
+WHERE
+ id = 2
+;
+
+INSERT INTO acq.user_request_type (id,label)
+ SELECT 6, oils_i18n_gettext('6', 'Other', 'aurt', 'label');
+
+SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, (SELECT MAX(id)+1 FROM acq.user_request_type));
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES
+ ( 610, 'CLEAR_PURCHASE_REQUEST', oils_i18n_gettext(610,
+ 'Clear Completed User Purchase Requests', 'ppl', 'description'))
+;
+
+CREATE TABLE acq.user_request_status_type (
+ id SERIAL PRIMARY KEY
+ ,label TEXT
+);
+
+INSERT INTO acq.user_request_status_type (id,label) VALUES
+ (0,oils_i18n_gettext(0,'Error','aurst','label'))
+ ,(1,oils_i18n_gettext(1,'New','aurst','label'))
+ ,(2,oils_i18n_gettext(2,'Pending','aurst','label'))
+ ,(3,oils_i18n_gettext(3,'Ordered, Hold Not Placed','aurst','label'))
+ ,(4,oils_i18n_gettext(4,'Ordered, Hold Placed','aurst','label'))
+ ,(5,oils_i18n_gettext(5,'Received','aurst','label'))
+ ,(6,oils_i18n_gettext(6,'Fulfilled','aurst','label'))
+ ,(7,oils_i18n_gettext(7,'Canceled','aurst','label'))
+;
+
+SELECT SETVAL('acq.user_request_status_type_id_seq'::TEXT, 100);
+
+-- not used
+DELETE FROM actor.org_unit_setting WHERE name = 'acq.holds.allow_holds_from_purchase_request';
+DELETE FROM config.org_unit_setting_type_log WHERE field_name = 'acq.holds.allow_holds_from_purchase_request';
+DELETE FROM config.org_unit_setting_type WHERE name = 'acq.holds.allow_holds_from_purchase_request';
+
+
+SELECT evergreen.upgrade_deps_block_check('1128', :eg_version);
+
+DROP VIEW auditor.acq_invoice_lifecycle;
+
+ALTER TABLE acq.invoice
+ ADD COLUMN close_date TIMESTAMPTZ,
+ ADD COLUMN closed_by INTEGER
+ REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED;
+
+-- duplicate steps for auditor table
+ALTER TABLE auditor.acq_invoice_history
+ ADD COLUMN close_date TIMESTAMPTZ,
+ ADD COLUMN closed_by INTEGER;
+
+UPDATE acq.invoice SET close_date = NOW() WHERE complete;
+UPDATE auditor.acq_invoice_history SET close_date = NOW() WHERE complete;
+
+ALTER TABLE acq.invoice DROP COLUMN complete;
+ALTER TABLE auditor.acq_invoice_history DROP COLUMN complete;
+
+-- this recreates auditor.acq_invoice_lifecycle;
+SELECT auditor.update_auditors();
+
+CREATE OR REPLACE FUNCTION actor.usr_merge( src_usr INT, dest_usr INT, del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) RETURNS VOID AS $$
+DECLARE
+ suffix TEXT;
+ bucket_row RECORD;
+ picklist_row RECORD;
+ queue_row RECORD;
+ folder_row RECORD;
+BEGIN
+
+ -- do some initial cleanup
+ UPDATE actor.usr SET card = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
+
+ -- actor.*
+ IF del_cards THEN
+ DELETE FROM actor.card where usr = src_usr;
+ ELSE
+ IF deactivate_cards THEN
+ UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
+ END IF;
+ UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+
+ IF del_addrs THEN
+ DELETE FROM actor.usr_address WHERE usr = src_usr;
+ ELSE
+ UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+ UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
+ -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
+ UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
+ PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
+
+ -- permission.*
+ PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
+
+
+ -- container.*
+
+ -- For each *_bucket table: transfer every bucket belonging to src_usr
+ -- into the custody of dest_usr.
+ --
+ -- In order to avoid colliding with an existing bucket owned by
+ -- the destination user, append the source user's id (in parenthesese)
+ -- to the name. If you still get a collision, add successive
+ -- spaces to the name and keep trying until you succeed.
+ --
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.biblio_record_entry_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.biblio_record_entry_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.call_number_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.call_number_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.copy_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.copy_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.user_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.user_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
+
+ -- vandelay.*
+ -- transfer queues the same way we transfer buckets (see above)
+ FOR queue_row in
+ SELECT id, name
+ FROM vandelay.queue
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE vandelay.queue
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = queue_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ -- money.*
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
+ UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
+ UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
+
+ -- action.*
+ UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
+ UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
+ UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
+
+ UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
+ UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
+ UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
+
+ UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
+ UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
+
+ -- acq.*
+ UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
+ UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
+ UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
+
+ -- transfer picklists the same way we transfer buckets (see above)
+ FOR picklist_row in
+ SELECT id, name
+ FROM acq.picklist
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE acq.picklist
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = picklist_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
+ UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
+
+ -- asset.*
+ UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
+
+ -- serial.*
+ UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
+
+ -- reporter.*
+ -- It's not uncommon to define the reporter schema in a replica
+ -- DB only, so don't assume these tables exist in the write DB.
+ BEGIN
+ UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.template_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.template_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.report_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.report_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.output_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.output_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- Finally, delete the source user
+ DELETE FROM actor.usr WHERE id = src_usr;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+CREATE OR REPLACE FUNCTION actor.usr_purge_data(
+ src_usr IN INTEGER,
+ specified_dest_usr IN INTEGER
+) RETURNS VOID AS $$
+DECLARE
+ suffix TEXT;
+ renamable_row RECORD;
+ dest_usr INTEGER;
+BEGIN
+
+ IF specified_dest_usr IS NULL THEN
+ dest_usr := 1; -- Admin user on stock installs
+ ELSE
+ dest_usr := specified_dest_usr;
+ END IF;
+
+ -- acq.*
+ UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
+ UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
+ UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
+ DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
+
+ -- Update with a rename to avoid collisions
+ FOR renamable_row in
+ SELECT id, name
+ FROM acq.picklist
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE acq.picklist
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
+ UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
+
+ -- action.*
+ DELETE FROM action.circulation WHERE usr = src_usr;
+ UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
+ UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
+ UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
+ UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
+ UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
+ DELETE FROM action.hold_request WHERE usr = src_usr;
+ UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
+ UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
+ DELETE FROM action.survey_response WHERE usr = src_usr;
+ UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
+ DELETE FROM action.usr_circ_history WHERE usr = src_usr;
+
+ -- actor.*
+ DELETE FROM actor.card WHERE usr = src_usr;
+ DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
+
+ -- The following update is intended to avoid transient violations of a foreign
+ -- key constraint, whereby actor.usr_address references itself. It may not be
+ -- necessary, but it does no harm.
+ UPDATE actor.usr_address SET replaces = NULL
+ WHERE usr = src_usr AND replaces IS NOT NULL;
+ DELETE FROM actor.usr_address WHERE usr = src_usr;
+ DELETE FROM actor.usr_note WHERE usr = src_usr;
+ UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
+ DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
+ UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
+ DELETE FROM actor.usr_setting WHERE usr = src_usr;
+ DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
+ UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
+
+ -- asset.*
+ UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
+
+ -- auditor.*
+ DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
+ DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
+ UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE auditor.asset_call_number_history SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE auditor.asset_copy_history SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE auditor.biblio_record_entry_history SET editor = dest_usr WHERE editor = src_usr;
+
+ -- biblio.*
+ UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
+
+ -- container.*
+ -- Update buckets with a rename to avoid collisions
+ FOR renamable_row in
+ SELECT id, name
+ FROM container.biblio_record_entry_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.biblio_record_entry_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR renamable_row in
+ SELECT id, name
+ FROM container.call_number_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.call_number_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR renamable_row in
+ SELECT id, name
+ FROM container.copy_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.copy_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR renamable_row in
+ SELECT id, name
+ FROM container.user_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.user_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
+
+ -- money.*
+ DELETE FROM money.billable_xact WHERE usr = src_usr;
+ DELETE FROM money.collections_tracker WHERE usr = src_usr;
+ UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
+
+ -- permission.*
+ DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
+ DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
+ DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
+ DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
+
+ -- reporter.*
+ -- Update with a rename to avoid collisions
+ BEGIN
+ FOR renamable_row in
+ SELECT id, name
+ FROM reporter.output_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.output_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ BEGIN
+ UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- Update with a rename to avoid collisions
+ BEGIN
+ FOR renamable_row in
+ SELECT id, name
+ FROM reporter.report_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.report_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ BEGIN
+ UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ BEGIN
+ UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- Update with a rename to avoid collisions
+ BEGIN
+ FOR renamable_row in
+ SELECT id, name
+ FROM reporter.template_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.template_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- vandelay.*
+ -- Update with a rename to avoid collisions
+ FOR renamable_row in
+ SELECT id, name
+ FROM vandelay.queue
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE vandelay.queue
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = renamable_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ -- NULL-ify addresses last so other cleanup (e.g. circ anonymization)
+ -- can access the information before deletion.
+ UPDATE actor.usr SET
+ active = FALSE,
+ card = NULL,
+ mailing_address = NULL,
+ billing_address = NULL
+ WHERE id = src_usr;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+
+
+
+-- UNDO (minus user purge/merge changes)
+/*
+
+DROP VIEW auditor.acq_invoice_lifecycle;
+ALTER TABLE acq.invoice ADD COLUMN complete BOOLEAN NOT NULL DEFAULT FALSE;
+ALTER TABLE auditor.acq_invoice_history
+ ADD COLUMN complete BOOLEAN NOT NULL DEFAULT FALSE;
+UPDATE acq.invoice SET complete = TRUE where close_date IS NOT NULL;
+UPDATE auditor.acq_invoice_history
+ SET complete = TRUE where close_date IS NOT NULL;
+SET CONSTRAINTS ALL IMMEDIATE; -- or get pending triggers error.
+ALTER TABLE acq.invoice DROP COLUMN close_date, DROP COLUMN closed_by;
+ALTER TABLE auditor.acq_invoice_history
+ DROP COLUMN close_date, DROP COLUMN closed_by;
+SELECT auditor.update_auditors();
+
+*/
+
+
+SELECT evergreen.upgrade_deps_block_check('1129', :eg_version);
+
+INSERT into config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+ 'eg.grid.admin.acq.cancel_reason', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.cancel_reason',
+ 'Grid Config: admin.acq.cancel_reason',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.claim_event_type', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.claim_event_type',
+ 'Grid Config: admin.acq.claim_event_type',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.claim_policy', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.claim_policy',
+ 'Grid Config: admin.acq.claim_policy',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.claim_policy_action', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.claim_policy_action',
+ 'Grid Config: admin.acq.claim_policy_action',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.claim_type', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.claim_type',
+ 'Grid Config: admin.acq.claim_type',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.currency_type', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.currency_type',
+ 'Grid Config: admin.acq.currency_type',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.edi_account', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.edi_account',
+ 'Grid Config: admin.acq.edi_account',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.edi_message', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.edi_message',
+ 'Grid Config: admin.acq.edi_message',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.exchange_rate', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.exchange_rate',
+ 'Grid Config: admin.acq.exchange_rate',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.fund_tag', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.fund_tag',
+ 'Grid Config: admin.acq.fund_tag',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.invoice_item_type', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.invoice_item_type',
+ 'Grid Config: admin.acq.invoice_item_type',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.invoice_payment_method', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.invoice_payment_method',
+ 'Grid Config: admin.acq.invoice_payment_method',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.lineitem_alert_text', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.lineitem_alert_text',
+ 'Grid Config: admin.acq.lineitem_alert_text',
+ 'cwst', 'label'
+ )
+), (
+ 'eg.grid.admin.acq.lineitem_marc_attr_definition', 'gui', 'object',
+ oils_i18n_gettext (
+ 'eg.grid.admin.acq.lineitem_marc_attr_definition',
+ 'Grid Config: admin.acq.lineitem_marc_attr_definition',
+ 'cwst', 'label'
+ )
+);
+
+
+SELECT evergreen.upgrade_deps_block_check('1130', :eg_version);
+
+CREATE OR REPLACE FUNCTION actor.usr_merge( src_usr INT, dest_usr INT, del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) RETURNS VOID AS $$
+DECLARE
+ suffix TEXT;
+ bucket_row RECORD;
+ picklist_row RECORD;
+ queue_row RECORD;
+ folder_row RECORD;
+BEGIN
+
+ -- Bail if src_usr equals dest_usr because the result of merging a
+ -- user with itself is not what you want.
+ IF src_usr = dest_usr THEN
+ RETURN;
+ END IF;
+
+ -- do some initial cleanup
+ UPDATE actor.usr SET card = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
+
+ -- actor.*
+ IF del_cards THEN
+ DELETE FROM actor.card where usr = src_usr;
+ ELSE
+ IF deactivate_cards THEN
+ UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
+ END IF;
+ UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+
+ IF del_addrs THEN
+ DELETE FROM actor.usr_address WHERE usr = src_usr;
+ ELSE
+ UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+ UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
+ -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
+ UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
+ PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
+
+ -- permission.*
+ PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
+
+
+ -- container.*
+
+ -- For each *_bucket table: transfer every bucket belonging to src_usr
+ -- into the custody of dest_usr.
+ --
+ -- In order to avoid colliding with an existing bucket owned by
+ -- the destination user, append the source user's id (in parenthesese)
+ -- to the name. If you still get a collision, add successive
+ -- spaces to the name and keep trying until you succeed.
+ --
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.biblio_record_entry_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.biblio_record_entry_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.call_number_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.call_number_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.copy_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.copy_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.user_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.user_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
+
+ -- vandelay.*
+ -- transfer queues the same way we transfer buckets (see above)
+ FOR queue_row in
+ SELECT id, name
+ FROM vandelay.queue
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE vandelay.queue
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = queue_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
+
+ -- money.*
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
+ UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
+ UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
+
+ -- action.*
+ UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
+ UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
+ UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
+
+ UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
+ UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
+ UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
+
+ UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
+ UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
+
+ -- acq.*
+ UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
+ UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
+ UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
+
+ -- transfer picklists the same way we transfer buckets (see above)
+ FOR picklist_row in
+ SELECT id, name
+ FROM acq.picklist
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE acq.picklist
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = picklist_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
+ UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
+
+ -- asset.*
+ UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
+
+ -- serial.*
+ UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
+
+ -- reporter.*
+ -- It's not uncommon to define the reporter schema in a replica
+ -- DB only, so don't assume these tables exist in the write DB.
+ BEGIN
+ UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.template_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.template_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.report_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.report_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.output_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.output_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- propagate preferred name values from the source user to the
+ -- destination user, but only when values are not being replaced.
+ WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
+ UPDATE actor.usr SET
+ pref_prefix =
+ COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
+ pref_first_given_name =
+ COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
+ pref_second_given_name =
+ COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
+ pref_family_name =
+ COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
+ pref_suffix =
+ COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
+ WHERE id = dest_usr;
+
+ -- Copy and deduplicate name keywords
+ -- String -> array -> rows -> DISTINCT -> array -> string
+ WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
+ dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
+ UPDATE actor.usr SET name_keywords = (
+ WITH keywords AS (
+ SELECT DISTINCT UNNEST(
+ REGEXP_SPLIT_TO_ARRAY(
+ COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
+ COALESCE((SELECT name_keywords FROM dusr), ''), E'\\s+'
+ )
+ ) AS parts
+ ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
+ ) WHERE id = dest_usr;
+
+ -- Finally, delete the source user
+ DELETE FROM actor.usr WHERE id = src_usr;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+SELECT evergreen.upgrade_deps_block_check('1131', :eg_version);
+
+CREATE OR REPLACE FUNCTION actor.usr_merge( src_usr INT, dest_usr INT, del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) RETURNS VOID AS $$
+DECLARE
+ suffix TEXT;
+ bucket_row RECORD;
+ picklist_row RECORD;
+ queue_row RECORD;
+ folder_row RECORD;
+BEGIN
+
+ -- Bail if src_usr equals dest_usr because the result of merging a
+ -- user with itself is not what you want.
+ IF src_usr = dest_usr THEN
+ RETURN;
+ END IF;
+
+ -- do some initial cleanup
+ UPDATE actor.usr SET card = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
+ UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
+
+ -- actor.*
+ IF del_cards THEN
+ DELETE FROM actor.card where usr = src_usr;
+ ELSE
+ IF deactivate_cards THEN
+ UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
+ END IF;
+ UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+
+ IF del_addrs THEN
+ DELETE FROM actor.usr_address WHERE usr = src_usr;
+ ELSE
+ UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
+ END IF;
+
+ UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
+ -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
+ UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
+ PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
+
+ -- permission.*
+ PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
+
+
+ -- container.*
+
+ -- For each *_bucket table: transfer every bucket belonging to src_usr
+ -- into the custody of dest_usr.
+ --
+ -- In order to avoid colliding with an existing bucket owned by
+ -- the destination user, append the source user's id (in parenthesese)
+ -- to the name. If you still get a collision, add successive
+ -- spaces to the name and keep trying until you succeed.
+ --
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.biblio_record_entry_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.biblio_record_entry_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.call_number_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.call_number_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.copy_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.copy_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ FOR bucket_row in
+ SELECT id, name
+ FROM container.user_bucket
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE container.user_bucket
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = bucket_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
+
+ -- vandelay.*
+ -- transfer queues the same way we transfer buckets (see above)
+ FOR queue_row in
+ SELECT id, name
+ FROM vandelay.queue
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE vandelay.queue
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = queue_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
+
+ -- money.*
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
+ PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
+ UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
+ UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
+
+ -- action.*
+ UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
+ UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
+ UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
+
+ UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
+ UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
+ UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
+ UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
+
+ UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
+ UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
+ UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
+
+ -- acq.*
+ UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
+ UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
+ UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
+
+ -- transfer picklists the same way we transfer buckets (see above)
+ FOR picklist_row in
+ SELECT id, name
+ FROM acq.picklist
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE acq.picklist
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = picklist_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+
+ UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
+ UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
+
+ -- asset.*
+ UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
+ UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
+
+ -- serial.*
+ UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
+ UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
+
+ -- reporter.*
+ -- It's not uncommon to define the reporter schema in a replica
+ -- DB only, so don't assume these tables exist in the write DB.
+ BEGIN
+ UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.template_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.template_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.report_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.report_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+ BEGIN
+ -- transfer folders the same way we transfer buckets (see above)
+ FOR folder_row in
+ SELECT id, name
+ FROM reporter.output_folder
+ WHERE owner = src_usr
+ LOOP
+ suffix := ' (' || src_usr || ')';
+ LOOP
+ BEGIN
+ UPDATE reporter.output_folder
+ SET owner = dest_usr, name = name || suffix
+ WHERE id = folder_row.id;
+ EXCEPTION WHEN unique_violation THEN
+ suffix := suffix || ' ';
+ CONTINUE;
+ END;
+ EXIT;
+ END LOOP;
+ END LOOP;
+ EXCEPTION WHEN undefined_table THEN
+ -- do nothing
+ END;
+
+ -- propagate preferred name values from the source user to the
+ -- destination user, but only when values are not being replaced.
+ WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
+ UPDATE actor.usr SET
+ pref_prefix =
+ COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
+ pref_first_given_name =
+ COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
+ pref_second_given_name =
+ COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
+ pref_family_name =
+ COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
+ pref_suffix =
+ COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
+ WHERE id = dest_usr;
+
+ -- Copy and deduplicate name keywords
+ -- String -> array -> rows -> DISTINCT -> array -> string
+ WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
+ dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
+ UPDATE actor.usr SET name_keywords = (
+ WITH keywords AS (
+ SELECT DISTINCT UNNEST(
+ REGEXP_SPLIT_TO_ARRAY(
+ COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
+ COALESCE((SELECT name_keywords FROM dusr), ''), E'\\s+'
+ )
+ ) AS parts
+ ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
+ ) WHERE id = dest_usr;
+
+ -- Finally, delete the source user
+ DELETE FROM actor.usr WHERE id = src_usr;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('1132', :eg_version); -- remingtron/csharp
+
+-- fix two typo/pasto's in setting descriptions
+UPDATE config.org_unit_setting_type
+SET description = oils_i18n_gettext(
+ 'circ.copy_alerts.forgive_fines_on_long_overdue_checkin',
+ 'Controls whether fines are automatically forgiven when checking out an '||
+ 'item that has been marked as long-overdue, and the corresponding copy alert has been '||
+ 'suppressed.',
+ 'coust', 'description'
+)
+WHERE NAME = 'circ.copy_alerts.forgive_fines_on_long_overdue_checkin';
+
+UPDATE config.org_unit_setting_type
+SET description = oils_i18n_gettext(
+ 'circ.longoverdue.xact_open_on_zero',
+ 'Leave transaction open when long-overdue balance equals zero. ' ||
+ 'This leaves the long-overdue copy on the patron record when it is paid',
+ 'coust', 'description'
+)
+WHERE NAME = 'circ.longoverdue.xact_open_on_zero';
+
+
+
+SELECT evergreen.upgrade_deps_block_check('1133', :eg_version);
+
+/*
+Unique indexes are not inherited by child tables, so they will not prevent
+duplicate inserts on action.transit_copy and action.hold_transit_copy,
+for example. Use check constraints instead to enforce unique-per-copy
+transits accross all transit types.
+*/
+
+-- Create an index for speedy check constraint lookups.
+CREATE INDEX active_transit_for_copy
+ ON action.transit_copy (target_copy)
+ WHERE dest_recv_time IS NULL AND cancel_time IS NULL;
+
+-- Check for duplicate transits across all transit types
+CREATE OR REPLACE FUNCTION action.copy_transit_is_unique()
+ RETURNS TRIGGER AS $func$
+BEGIN
+ PERFORM * FROM action.transit_copy
+ WHERE target_copy = NEW.target_copy
+ AND dest_recv_time IS NULL
+ AND cancel_time IS NULL;
+ IF FOUND THEN
+ RAISE EXCEPTION 'Copy id=% is already in transit', NEW.target_copy;
+ END IF;
+ RETURN NULL;
+END;
+$func$ LANGUAGE PLPGSQL STABLE;
+
+-- Apply constraint to all transit tables
+CREATE CONSTRAINT TRIGGER transit_copy_is_unique_check
+ AFTER INSERT ON action.transit_copy
+ FOR EACH ROW EXECUTE PROCEDURE action.copy_transit_is_unique();
+
+CREATE CONSTRAINT TRIGGER hold_transit_copy_is_unique_check
+ AFTER INSERT ON action.hold_transit_copy
+ FOR EACH ROW EXECUTE PROCEDURE action.copy_transit_is_unique();
+
+CREATE CONSTRAINT TRIGGER reservation_transit_copy_is_unique_check
+ AFTER INSERT ON action.reservation_transit_copy
+ FOR EACH ROW EXECUTE PROCEDURE action.copy_transit_is_unique();
+
+/*
+-- UNDO
+DROP TRIGGER transit_copy_is_unique_check ON action.transit_copy;
+DROP TRIGGER hold_transit_copy_is_unique_check ON action.hold_transit_copy;
+DROP TRIGGER reservation_transit_copy_is_unique_check ON action.reservation_transit_copy;
+DROP INDEX action.active_transit_for_copy;
+*/
+
+
+COMMIT;
+
+\qecho A unique constraint was applied to action.transit_copy. This will
+\qecho only effect newly created transits. Admins are encouraged to manually
+\qecho remove any existing duplicate transits by applying values for cancel_time
+\qecho or dest_recv_time, or by deleting the offending transits. Below is a
+\qecho query to locate duplicate transits. Note dupes may exist accross
+\qecho parent (action.transit_copy) and child tables (action.hold_transit_copy,
+\qecho action.reservation_transit_copy)
+\qecho
+\qecho WITH dupe_transits AS (
+\qecho SELECT COUNT(*), target_copy FROM action.transit_copy
+\qecho WHERE dest_recv_time IS NULL AND cancel_time IS NULL
+\qecho GROUP BY 2 HAVING COUNT(*) > 1
+\qecho ) SELECT atc.*
+\qecho FROM dupe_transits
+\qecho JOIN action.transit_copy atc USING (target_copy)
+\qecho WHERE dest_recv_time IS NULL AND cancel_time IS NULL;
+\qecho
-----------------------------------------------------------------------
Summary of changes:
.../Pg/version-upgrade/3.1.5-3.2.0-upgrade-db.sql | 4904 ++++++++++++++++++++
1 files changed, 4904 insertions(+), 0 deletions(-)
create mode 100644 Open-ILS/src/sql/Pg/version-upgrade/3.1.5-3.2.0-upgrade-db.sql
hooks/post-receive
--
Evergreen ILS
More information about the open-ils-commits
mailing list