]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/version-upgrade/3.1.5-3.2.0-upgrade-db.sql
Forward port 3.1.5-3.2.0-upgrade-db.sql.
[working/Evergreen.git] / Open-ILS / src / sql / Pg / version-upgrade / 3.1.5-3.2.0-upgrade-db.sql
1 --Upgrade Script for 3.1.5 to 3.2.0
2 \set eg_version '''3.2.0'''
3 BEGIN;
4 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('3.2.0', :eg_version);
5
6 SELECT evergreen.upgrade_deps_block_check('1115', :eg_version);
7
8 INSERT INTO permission.perm_list (id,code,description) VALUES ( 607, 'EMERGENCY_CLOSING', 'Create and manage Emergency Closings');
9
10 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');
11 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');
12 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');
13
14 CREATE TABLE action.emergency_closing (
15     id                  SERIAL      PRIMARY KEY,
16     creator             INT         NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
17     create_time         TIMESTAMPTZ NOT NULL DEFAULT NOW(),
18     process_start_time  TIMESTAMPTZ,
19     process_end_time    TIMESTAMPTZ,
20     last_update_time    TIMESTAMPTZ
21 );
22
23 ALTER TABLE actor.org_unit_closed
24     ADD COLUMN emergency_closing INT
25         REFERENCES action.emergency_closing (id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
26
27 CREATE TABLE action.emergency_closing_circulation (
28     id                  BIGSERIAL   PRIMARY KEY,
29     emergency_closing   INT         NOT NULL REFERENCES action.emergency_closing (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
30     circulation         INT         NOT NULL REFERENCES action.circulation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
31     original_due_date   TIMESTAMPTZ,
32     process_time        TIMESTAMPTZ
33 );
34 CREATE INDEX emergency_closing_circulation_emergency_closing_idx ON action.emergency_closing_circulation (emergency_closing);
35 CREATE INDEX emergency_closing_circulation_circulation_idx ON action.emergency_closing_circulation (circulation);
36
37 CREATE TABLE action.emergency_closing_reservation (
38     id                  BIGSERIAL   PRIMARY KEY,
39     emergency_closing   INT         NOT NULL REFERENCES action.emergency_closing (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
40     reservation         INT         NOT NULL REFERENCES booking.reservation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
41     original_end_time   TIMESTAMPTZ,
42     process_time        TIMESTAMPTZ
43 );
44 CREATE INDEX emergency_closing_reservation_emergency_closing_idx ON action.emergency_closing_reservation (emergency_closing);
45 CREATE INDEX emergency_closing_reservation_reservation_idx ON action.emergency_closing_reservation (reservation);
46
47 CREATE TABLE action.emergency_closing_hold (
48     id                  BIGSERIAL   PRIMARY KEY,
49     emergency_closing   INT         NOT NULL REFERENCES action.emergency_closing (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
50     hold                INT         NOT NULL REFERENCES action.hold_request (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
51     original_shelf_expire_time   TIMESTAMPTZ,
52     process_time        TIMESTAMPTZ
53 );
54 CREATE INDEX emergency_closing_hold_emergency_closing_idx ON action.emergency_closing_hold (emergency_closing);
55 CREATE INDEX emergency_closing_hold_hold_idx ON action.emergency_closing_hold (hold);
56
57 CREATE OR REPLACE VIEW action.emergency_closing_status AS
58     SELECT  e.*,
59             COALESCE(c.count, 0) AS circulations,
60             COALESCE(c.completed, 0) AS circulations_complete,
61             COALESCE(b.count, 0) AS reservations,
62             COALESCE(b.completed, 0) AS reservations_complete,
63             COALESCE(h.count, 0) AS holds,
64             COALESCE(h.completed, 0) AS holds_complete
65       FROM  action.emergency_closing e
66             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)
67             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)
68             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)
69 ;
70
71 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 )
72     RETURNS TIMESTAMPTZ AS $$
73 DECLARE
74     day_number      INT;
75     plus_days       INT;
76     final_time      TEXT;
77     time_adjusted   BOOL;
78     hoo_open        TIME WITHOUT TIME ZONE;
79     hoo_close       TIME WITHOUT TIME ZONE;
80     adjacent        actor.org_unit_closed%ROWTYPE;
81     breakout        INT := 0;
82 BEGIN
83
84     IF dow_count > 6 THEN
85         RETURN initial;
86     END IF;
87
88     IF initial_time IS NULL THEN
89         initial_time := initial::TIME;
90     END IF;
91
92     final_time := (initial + '1 second'::INTERVAL)::TEXT;
93     LOOP
94         breakout := breakout + 1;
95
96         time_adjusted := FALSE;
97
98         IF dow_count > 0 THEN -- we're recursing, so check for HOO closing
99             day_number := EXTRACT(ISODOW FROM final_time::TIMESTAMPTZ) - 1;
100             plus_days := 0;
101             FOR i IN 1..7 LOOP
102                 EXECUTE 'SELECT dow_' || day_number || '_open, dow_' || day_number || '_close FROM actor.hours_of_operation WHERE id = $1'
103                     INTO hoo_open, hoo_close
104                     USING circ_lib;
105
106                 -- RAISE NOTICE 'initial time: %; dow: %; close: %',initial_time,day_number,hoo_close;
107
108                 IF hoo_close = '00:00:00' THEN -- bah ... I guess we'll check the next day
109                     day_number := (day_number + 1) % 7;
110                     plus_days := plus_days + 1;
111                     time_adjusted := TRUE;
112                     CONTINUE;
113                 END IF;
114
115                 IF hoo_close IS NULL THEN -- no hours of operation ... assume no closing?
116                     hoo_close := '23:59:59';
117                 END IF;
118
119                 EXIT;
120             END LOOP;
121
122             final_time := DATE(final_time::TIMESTAMPTZ + (plus_days || ' days')::INTERVAL)::TEXT;
123             IF hoo_close <> '00:00:00' AND hourly THEN -- Not a day-granular circ
124                 final_time := final_time||' '|| hoo_close;
125             ELSE
126                 final_time := final_time||' 23:59:59';
127             END IF;
128         END IF;
129
130         -- Loop through other closings
131         LOOP 
132             SELECT * INTO adjacent FROM actor.org_unit_closed WHERE org_unit = circ_lib AND final_time::TIMESTAMPTZ between close_start AND close_end;
133             EXIT WHEN adjacent.id IS NULL;
134             time_adjusted := TRUE;
135             -- RAISE NOTICE 'recursing for closings with final_time: %',final_time;
136             final_time := evergreen.find_next_open_time(circ_lib, adjacent.close_end::TIMESTAMPTZ, hourly, initial_time, dow_count + 1)::TEXT;
137         END LOOP;
138
139         EXIT WHEN breakout > 100;
140         EXIT WHEN NOT time_adjusted;
141
142     END LOOP;
143
144     RETURN final_time;
145 END;
146 $$ LANGUAGE PLPGSQL;
147
148 CREATE TYPE action.emergency_closing_stage_1_count AS (circulations INT, reservations INT, holds INT);
149 CREATE OR REPLACE FUNCTION action.emergency_closing_stage_1 ( e_closing INT )
150     RETURNS SETOF action.emergency_closing_stage_1_count AS $$
151 DECLARE
152     tmp     INT;
153     touched action.emergency_closing_stage_1_count%ROWTYPE;
154 BEGIN
155     -- First, gather circs
156     INSERT INTO action.emergency_closing_circulation (emergency_closing, circulation)
157         SELECT  e_closing,
158                 circ.id
159           FROM  actor.org_unit_closed closing
160                 JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing)
161                 JOIN action.circulation circ ON (
162                     circ.circ_lib = closing.org_unit
163                     AND circ.due_date BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL)
164                     AND circ.xact_finish IS NULL
165                 )
166           WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_circulation t WHERE t.emergency_closing = e_closing AND t.circulation = circ.id);
167
168     GET DIAGNOSTICS tmp = ROW_COUNT;
169     touched.circulations := tmp;
170
171     INSERT INTO action.emergency_closing_reservation (emergency_closing, reservation)
172         SELECT  e_closing,
173                 res.id
174           FROM  actor.org_unit_closed closing
175                 JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing)
176                 JOIN booking.reservation res ON (
177                     res.pickup_lib = closing.org_unit
178                     AND res.end_time BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL)
179                 )
180           WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_reservation t WHERE t.emergency_closing = e_closing AND t.reservation = res.id);
181
182     GET DIAGNOSTICS tmp = ROW_COUNT;
183     touched.reservations := tmp;
184
185     INSERT INTO action.emergency_closing_hold (emergency_closing, hold)
186         SELECT  e_closing,
187                 hold.id
188           FROM  actor.org_unit_closed closing
189                 JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing)
190                 JOIN action.hold_request hold ON (
191                     pickup_lib = closing.org_unit
192                     AND hold.shelf_expire_time BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL)
193                     AND hold.fulfillment_time IS NULL
194                     AND hold.cancel_time IS NULL
195                 )
196           WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_hold t WHERE t.emergency_closing = e_closing AND t.hold = hold.id);
197
198     GET DIAGNOSTICS tmp = ROW_COUNT;
199     touched.holds := tmp;
200
201     UPDATE  action.emergency_closing
202       SET   process_start_time = NOW(),
203             last_update_time = NOW()
204       WHERE id = e_closing;
205
206     RETURN NEXT touched;
207 END;
208 $$ LANGUAGE PLPGSQL;
209
210 CREATE OR REPLACE FUNCTION action.emergency_closing_stage_2_hold ( hold_closing_entry INT )
211     RETURNS BOOL AS $$
212 DECLARE
213     hold        action.hold_request%ROWTYPE;
214     e_closing   action.emergency_closing%ROWTYPE;
215     e_c_hold    action.emergency_closing_hold%ROWTYPE;
216     closing     actor.org_unit_closed%ROWTYPE;
217     day_number  INT;
218     hoo_close   TIME WITHOUT TIME ZONE;
219     plus_days   INT;
220 BEGIN
221     -- Gather objects involved
222     SELECT  * INTO e_c_hold
223       FROM  action.emergency_closing_hold
224       WHERE id = hold_closing_entry;
225
226     IF e_c_hold.process_time IS NOT NULL THEN
227         -- Already processed ... moving on
228         RETURN FALSE;
229     END IF;
230
231     SELECT  * INTO e_closing
232       FROM  action.emergency_closing
233       WHERE id = e_c_hold.emergency_closing;
234
235     IF e_closing.process_start_time IS NULL THEN
236         -- Huh... that's odd. And wrong.
237         RETURN FALSE;
238     END IF;
239
240     SELECT  * INTO closing
241       FROM  actor.org_unit_closed
242       WHERE emergency_closing = e_closing.id;
243
244     SELECT  * INTO hold
245       FROM  action.hold_request h
246       WHERE id = e_c_hold.hold;
247
248     -- Record the processing
249     UPDATE  action.emergency_closing_hold
250       SET   original_shelf_expire_time = hold.shelf_expire_time,
251             process_time = NOW()
252       WHERE id = hold_closing_entry;
253
254     UPDATE  action.emergency_closing
255       SET   last_update_time = NOW()
256       WHERE id = e_closing.id;
257
258     UPDATE  action.hold_request
259       SET   shelf_expire_time = evergreen.find_next_open_time(closing.org_unit, hold.shelf_expire_time, TRUE)
260       WHERE id = hold.id;
261
262     RETURN TRUE;
263 END;
264 $$ LANGUAGE PLPGSQL;
265
266 CREATE OR REPLACE FUNCTION action.emergency_closing_stage_2_circ ( circ_closing_entry INT )
267     RETURNS BOOL AS $$
268 DECLARE
269     circ            action.circulation%ROWTYPE;
270     e_closing       action.emergency_closing%ROWTYPE;
271     e_c_circ        action.emergency_closing_circulation%ROWTYPE;
272     closing         actor.org_unit_closed%ROWTYPE;
273     adjacent        actor.org_unit_closed%ROWTYPE;
274     bill            money.billing%ROWTYPE;
275     last_bill       money.billing%ROWTYPE;
276     day_number      INT;
277     hoo_close       TIME WITHOUT TIME ZONE;
278     plus_days       INT;
279     avoid_negative  BOOL;
280     extend_grace    BOOL;
281     new_due_date    TEXT;
282 BEGIN
283     -- Gather objects involved
284     SELECT  * INTO e_c_circ
285       FROM  action.emergency_closing_circulation
286       WHERE id = circ_closing_entry;
287
288     IF e_c_circ.process_time IS NOT NULL THEN
289         -- Already processed ... moving on
290         RETURN FALSE;
291     END IF;
292
293     SELECT  * INTO e_closing
294       FROM  action.emergency_closing
295       WHERE id = e_c_circ.emergency_closing;
296
297     IF e_closing.process_start_time IS NULL THEN
298         -- Huh... that's odd. And wrong.
299         RETURN FALSE;
300     END IF;
301
302     SELECT  * INTO closing
303       FROM  actor.org_unit_closed
304       WHERE emergency_closing = e_closing.id;
305
306     SELECT  * INTO circ
307       FROM  action.circulation
308       WHERE id = e_c_circ.circulation;
309
310     -- Record the processing
311     UPDATE  action.emergency_closing_circulation
312       SET   original_due_date = circ.due_date,
313             process_time = NOW()
314       WHERE id = circ_closing_entry;
315
316     UPDATE  action.emergency_closing
317       SET   last_update_time = NOW()
318       WHERE id = e_closing.id;
319
320     SELECT value::BOOL INTO avoid_negative FROM actor.org_unit_ancestor_setting('bill.prohibit_negative_balance_on_overdues', circ.circ_lib);
321     SELECT value::BOOL INTO extend_grace FROM actor.org_unit_ancestor_setting('circ.grace.extend', circ.circ_lib);
322
323     new_due_date := evergreen.find_next_open_time( closing.org_unit, circ.due_date, EXTRACT(EPOCH FROM circ.duration)::INT % 86400 > 0 )::TEXT;
324     UPDATE action.circulation SET due_date = new_due_date::TIMESTAMPTZ WHERE id = circ.id;
325
326     -- Now, see if we need to get rid of some fines
327     SELECT  * INTO last_bill
328       FROM  money.billing b
329       WHERE b.xact = circ.id
330             AND NOT b.voided
331             AND b.btype = 1
332       ORDER BY billing_ts DESC
333       LIMIT 1;
334
335     FOR bill IN
336         SELECT  *
337           FROM  money.billing b
338           WHERE b.xact = circ.id
339                 AND b.btype = 1
340                 AND NOT b.voided
341                 AND (
342                     b.billing_ts BETWEEN closing.close_start AND new_due_date::TIMESTAMPTZ
343                     OR (extend_grace AND last_bill.billing_ts <= new_due_date::TIMESTAMPTZ + circ.grace_period)
344                 )
345                 AND NOT EXISTS (SELECT 1 FROM money.account_adjustment a WHERE a.billing = b.id)
346           ORDER BY billing_ts
347     LOOP
348         IF avoid_negative THEN
349             PERFORM FROM money.materialized_billable_xact_summary WHERE id = circ.id AND balanced_owd < bill.amount;
350             EXIT WHEN FOUND; -- We can't go negative, and voiding this bill would do that...
351         END IF;
352
353         UPDATE  money.billing
354           SET   voided = TRUE,
355                 void_time = NOW(),
356                 note = COALESCE(note,'') || ' :: Voided by emergency closing handler'
357           WHERE id = bill.id;
358     END LOOP;
359     
360     RETURN TRUE;
361 END;
362 $$ LANGUAGE PLPGSQL;
363
364 CREATE OR REPLACE FUNCTION action.emergency_closing_stage_2_reservation ( res_closing_entry INT )
365     RETURNS BOOL AS $$
366 DECLARE
367     res             booking.reservation%ROWTYPE;
368     e_closing       action.emergency_closing%ROWTYPE;
369     e_c_res         action.emergency_closing_reservation%ROWTYPE;
370     closing         actor.org_unit_closed%ROWTYPE;
371     adjacent        actor.org_unit_closed%ROWTYPE;
372     bill            money.billing%ROWTYPE;
373     day_number      INT;
374     hoo_close       TIME WITHOUT TIME ZONE;
375     plus_days       INT;
376     avoid_negative  BOOL;
377     new_due_date    TEXT;
378 BEGIN
379     -- Gather objects involved
380     SELECT  * INTO e_c_res
381       FROM  action.emergency_closing_reservation
382       WHERE id = res_closing_entry;
383
384     IF e_c_res.process_time IS NOT NULL THEN
385         -- Already processed ... moving on
386         RETURN FALSE;
387     END IF;
388
389     SELECT  * INTO e_closing
390       FROM  action.emergency_closing
391       WHERE id = e_c_res.emergency_closing;
392
393     IF e_closing.process_start_time IS NULL THEN
394         -- Huh... that's odd. And wrong.
395         RETURN FALSE;
396     END IF;
397
398     SELECT  * INTO closing
399       FROM  actor.org_unit_closed
400       WHERE emergency_closing = e_closing.id;
401
402     SELECT  * INTO res
403       FROM  booking.reservation
404       WHERE id = e_c_res.reservation;
405
406     IF res.pickup_lib IS NULL THEN -- Need to be far enough along to have a pickup lib
407         RETURN FALSE;
408     END IF;
409
410     -- Record the processing
411     UPDATE  action.emergency_closing_reservation
412       SET   original_end_time = res.end_time,
413             process_time = NOW()
414       WHERE id = res_closing_entry;
415
416     UPDATE  action.emergency_closing
417       SET   last_update_time = NOW()
418       WHERE id = e_closing.id;
419
420     SELECT value::BOOL INTO avoid_negative FROM actor.org_unit_ancestor_setting('bill.prohibit_negative_balance_on_overdues', res.pickup_lib);
421
422     new_due_date := evergreen.find_next_open_time( closing.org_unit, res.end_time, EXTRACT(EPOCH FROM res.booking_interval)::INT % 86400 > 0 )::TEXT;
423     UPDATE booking.reservation SET end_time = new_due_date::TIMESTAMPTZ WHERE id = res.id;
424
425     -- Now, see if we need to get rid of some fines
426     FOR bill IN
427         SELECT  *
428           FROM  money.billing b
429           WHERE b.xact = res.id
430                 AND b.btype = 1
431                 AND NOT b.voided
432                 AND b.billing_ts BETWEEN closing.close_start AND new_due_date::TIMESTAMPTZ
433                 AND NOT EXISTS (SELECT 1 FROM money.account_adjustment a WHERE a.billing = b.id)
434     LOOP
435         IF avoid_negative THEN
436             PERFORM FROM money.materialized_billable_xact_summary WHERE id = res.id AND balanced_owd < bill.amount;
437             EXIT WHEN FOUND; -- We can't go negative, and voiding this bill would do that...
438         END IF;
439
440         UPDATE  money.billing
441           SET   voided = TRUE,
442                 void_time = NOW(),
443                 note = COALESCE(note,'') || ' :: Voided by emergency closing handler'
444           WHERE id = bill.id;
445     END LOOP;
446     
447     RETURN TRUE;
448 END;
449 $$ LANGUAGE PLPGSQL;
450
451
452
453 CREATE TYPE actor.cascade_setting_summary AS (
454     name TEXT,
455     value JSON,
456     has_org_setting BOOLEAN,
457     has_user_setting BOOLEAN,
458     has_workstation_setting BOOLEAN
459 );
460
461 SELECT evergreen.upgrade_deps_block_check('1116', :eg_version);
462
463 CREATE TABLE config.workstation_setting_type (
464     name            TEXT    PRIMARY KEY,
465     label           TEXT    UNIQUE NOT NULL,
466     grp             TEXT    REFERENCES config.settings_group (name),
467     description     TEXT,
468     datatype        TEXT    NOT NULL DEFAULT 'string',
469     fm_class        TEXT,
470     --
471     -- define valid datatypes
472     --
473     CONSTRAINT cwst_valid_datatype CHECK ( datatype IN
474     ( 'bool', 'integer', 'float', 'currency', 'interval',
475       'date', 'string', 'object', 'array', 'link' ) ),
476     --
477     -- fm_class is meaningful only for 'link' datatype
478     --
479     CONSTRAINT cwst_no_empty_link CHECK
480     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
481       ( datatype <> 'link' AND fm_class IS NULL ) )
482 );
483
484 CREATE TABLE actor.workstation_setting (
485     id          SERIAL PRIMARY KEY,
486     workstation INT    NOT NULL REFERENCES actor.workstation (id) 
487                        ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
488     name        TEXT   NOT NULL REFERENCES config.workstation_setting_type (name) 
489                        ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
490     value       JSON   NOT NULL
491 );
492
493
494 CREATE INDEX actor_workstation_setting_workstation_idx 
495     ON actor.workstation_setting (workstation);
496
497 CREATE OR REPLACE FUNCTION config.setting_is_user_or_ws()
498 RETURNS TRIGGER AS $FUNC$
499 BEGIN
500
501     IF TG_TABLE_NAME = 'usr_setting_type' THEN
502         PERFORM TRUE FROM config.workstation_setting_type cwst
503             WHERE cwst.name = NEW.name;
504         IF NOT FOUND THEN
505             RETURN NULL;
506         END IF;
507     END IF;
508
509     IF TG_TABLE_NAME = 'workstation_setting_type' THEN
510         PERFORM TRUE FROM config.usr_setting_type cust
511             WHERE cust.name = NEW.name;
512         IF NOT FOUND THEN
513             RETURN NULL;
514         END IF;
515     END IF;
516
517     RAISE EXCEPTION 
518         '% Cannot be used as both a user setting and a workstation setting.', 
519         NEW.name;
520 END;
521 $FUNC$ LANGUAGE PLPGSQL STABLE;
522
523 CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws
524   AFTER INSERT OR UPDATE ON config.usr_setting_type
525   FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws();
526
527 CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws
528   AFTER INSERT OR UPDATE ON config.workstation_setting_type
529   FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws();
530
531 CREATE OR REPLACE FUNCTION actor.get_cascade_setting(
532     setting_name TEXT, org_id INT, user_id INT, workstation_id INT) 
533     RETURNS actor.cascade_setting_summary AS
534 $FUNC$
535 DECLARE
536     setting_value JSON;
537     summary actor.cascade_setting_summary;
538     org_setting_type config.org_unit_setting_type%ROWTYPE;
539 BEGIN
540
541     summary.name := setting_name;
542
543     -- Collect the org setting type status first in case we exit early.
544     -- The existance of an org setting type is not considered
545     -- privileged information.
546     SELECT INTO org_setting_type * 
547         FROM config.org_unit_setting_type WHERE name = setting_name;
548     IF FOUND THEN
549         summary.has_org_setting := TRUE;
550     ELSE
551         summary.has_org_setting := FALSE;
552     END IF;
553
554     -- User and workstation settings have the same priority.
555     -- Start with user settings since that's the simplest code path.
556     -- The workstation_id is ignored if no user_id is provided.
557     IF user_id IS NOT NULL THEN
558
559         SELECT INTO summary.value value FROM actor.usr_setting
560             WHERE usr = user_id AND name = setting_name;
561
562         IF FOUND THEN
563             -- if we have a value, we have a setting type
564             summary.has_user_setting := TRUE;
565
566             IF workstation_id IS NOT NULL THEN
567                 -- Only inform the caller about the workstation
568                 -- setting type disposition when a workstation id is
569                 -- provided.  Otherwise, it's NULL to indicate UNKNOWN.
570                 summary.has_workstation_setting := FALSE;
571             END IF;
572
573             RETURN summary;
574         END IF;
575
576         -- no user setting value, but a setting type may exist
577         SELECT INTO summary.has_user_setting EXISTS (
578             SELECT TRUE FROM config.usr_setting_type 
579             WHERE name = setting_name
580         );
581
582         IF workstation_id IS NOT NULL THEN 
583
584             IF NOT summary.has_user_setting THEN
585                 -- A workstation setting type may only exist when a user
586                 -- setting type does not.
587
588                 SELECT INTO summary.value value 
589                     FROM actor.workstation_setting         
590                     WHERE workstation = workstation_id AND name = setting_name;
591
592                 IF FOUND THEN
593                     -- if we have a value, we have a setting type
594                     summary.has_workstation_setting := TRUE;
595                     RETURN summary;
596                 END IF;
597
598                 -- no value, but a setting type may exist
599                 SELECT INTO summary.has_workstation_setting EXISTS (
600                     SELECT TRUE FROM config.workstation_setting_type 
601                     WHERE name = setting_name
602                 );
603             END IF;
604
605             -- Finally make use of the workstation to determine the org
606             -- unit if none is provided.
607             IF org_id IS NULL AND summary.has_org_setting THEN
608                 SELECT INTO org_id owning_lib 
609                     FROM actor.workstation WHERE id = workstation_id;
610             END IF;
611         END IF;
612     END IF;
613
614     -- Some org unit settings are protected by a view permission.
615     -- First see if we have any data that needs protecting, then 
616     -- check the permission if needed.
617
618     IF NOT summary.has_org_setting THEN
619         RETURN summary;
620     END IF;
621
622     -- avoid putting the value into the summary until we confirm
623     -- the value should be visible to the caller.
624     SELECT INTO setting_value value 
625         FROM actor.org_unit_ancestor_setting(setting_name, org_id);
626
627     IF NOT FOUND THEN
628         -- No value found -- perm check is irrelevant.
629         RETURN summary;
630     END IF;
631
632     IF org_setting_type.view_perm IS NOT NULL THEN
633
634         IF user_id IS NULL THEN
635             RAISE NOTICE 'Perm check required but no user_id provided';
636             RETURN summary;
637         END IF;
638
639         IF NOT permission.usr_has_perm(
640             user_id, (SELECT code FROM permission.perm_list 
641                 WHERE id = org_setting_type.view_perm), org_id) 
642         THEN
643             RAISE NOTICE 'Perm check failed for user % on %',
644                 user_id, org_setting_type.view_perm;
645             RETURN summary;
646         END IF;
647     END IF;
648
649     -- Perm check succeeded or was not necessary.
650     summary.value := setting_value;
651     RETURN summary;
652 END;
653 $FUNC$ LANGUAGE PLPGSQL;
654
655
656 CREATE OR REPLACE FUNCTION actor.get_cascade_setting_batch(
657     setting_names TEXT[], org_id INT, user_id INT, workstation_id INT) 
658     RETURNS SETOF actor.cascade_setting_summary AS
659 $FUNC$
660 -- Returns a row per setting matching the setting name order.  If no 
661 -- value is applied, NULL is returned to retain name-response ordering.
662 DECLARE
663     setting_name TEXT;
664     summary actor.cascade_setting_summary;
665 BEGIN
666     FOREACH setting_name IN ARRAY setting_names LOOP
667         SELECT INTO summary * FROM actor.get_cascade_setting(
668             setting_Name, org_id, user_id, workstation_id);
669         RETURN NEXT summary;
670     END LOOP;
671 END;
672 $FUNC$ LANGUAGE PLPGSQL;
673
674
675
676
677
678 SELECT evergreen.upgrade_deps_block_check('1117', :eg_version);
679
680 INSERT INTO permission.perm_list (id, code, description) VALUES
681  (608, 'APPLY_WORKSTATION_SETTING',
682    oils_i18n_gettext(608, 'APPLY_WORKSTATION_SETTING', 'ppl', 'description'));
683
684 INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
685 VALUES (
686     'eg.circ.checkin.no_precat_alert', 'circ', 'bool',
687     oils_i18n_gettext(
688         'eg.circ.checkin.no_precat_alert',
689         'Checkin: Ignore Precataloged Items',
690         'cwst', 'label'
691     )
692 ), (
693     'eg.circ.checkin.noop', 'circ', 'bool',
694     oils_i18n_gettext(
695         'eg.circ.checkin.noop',
696         'Checkin: Suppress Holds and Transits',
697         'cwst', 'label'
698     )
699 ), (
700     'eg.circ.checkin.void_overdues', 'circ', 'bool',
701     oils_i18n_gettext(
702         'eg.circ.checkin.void_overdues',
703         'Checkin: Amnesty Mode',
704         'cwst', 'label'
705     )
706 ), (
707     'eg.circ.checkin.auto_print_holds_transits', 'circ', 'bool',
708     oils_i18n_gettext(
709         'eg.circ.checkin.auto_print_holds_transits',
710         'Checkin: Auto-Print Holds and Transits',
711         'cwst', 'label'
712     )
713 ), (
714     'eg.circ.checkin.clear_expired', 'circ', 'bool',
715     oils_i18n_gettext(
716         'eg.circ.checkin.clear_expired',
717         'Checkin: Clear Holds Shelf',
718         'cwst', 'label'
719     )
720 ), (
721     'eg.circ.checkin.retarget_holds', 'circ', 'bool',
722     oils_i18n_gettext(
723         'eg.circ.checkin.retarget_holds',
724         'Checkin: Retarget Local Holds',
725         'cwst', 'label'
726     )
727 ), (
728     'eg.circ.checkin.retarget_holds_all', 'circ', 'bool',
729     oils_i18n_gettext(
730         'eg.circ.checkin.retarget_holds_all',
731         'Checkin: Retarget All Statuses',
732         'cwst', 'label'
733     )
734 ), (
735     'eg.circ.checkin.hold_as_transit', 'circ', 'bool',
736     oils_i18n_gettext(
737         'eg.circ.checkin.hold_as_transit',
738         'Checkin: Capture Local Holds as Transits',
739         'cwst', 'label'
740     )
741 ), (
742     'eg.circ.checkin.manual_float', 'circ', 'bool',
743     oils_i18n_gettext(
744         'eg.circ.checkin.manual_float',
745         'Checkin: Manual Floating Active',
746         'cwst', 'label'
747     )
748 ), (
749     'eg.circ.patron.summary.collapse', 'circ', 'bool',
750     oils_i18n_gettext(
751         'eg.circ.patron.summary.collapse',
752         'Collaps Patron Summary Display',
753         'cwst', 'label'
754     )
755 ), (
756     'circ.bills.receiptonpay', 'circ', 'bool',
757     oils_i18n_gettext(
758         'circ.bills.receiptonpay',
759         'Print Receipt On Payment',
760         'cwst', 'label'
761     )
762 ), (
763     'circ.renew.strict_barcode', 'circ', 'bool',
764     oils_i18n_gettext(
765         'circ.renew.strict_barcode',
766         'Renew: Strict Barcode',
767         'cwst', 'label'
768     )
769 ), (
770     'circ.checkin.strict_barcode', 'circ', 'bool',
771     oils_i18n_gettext(
772         'circ.checkin.strict_barcode',
773         'Checkin: Strict Barcode',
774         'cwst', 'label'
775     )
776 ), (
777     'circ.checkout.strict_barcode', 'circ', 'bool',
778     oils_i18n_gettext(
779         'circ.checkout.strict_barcode',
780         'Checkout: Strict Barcode',
781         'cwst', 'label'
782     )
783 ), (
784     'cat.holdings_show_copies', 'cat', 'bool',
785     oils_i18n_gettext(
786         'cat.holdings_show_copies',
787         'Holdings View Show Copies',
788         'cwst', 'label'
789     )
790 ), (
791     'cat.holdings_show_empty', 'cat', 'bool',
792     oils_i18n_gettext(
793         'cat.holdings_show_empty',
794         'Holdings View Show Empty Volumes',
795         'cwst', 'label'
796     )
797 ), (
798     'cat.holdings_show_empty_org', 'cat', 'bool',
799     oils_i18n_gettext(
800         'cat.holdings_show_empty_org',
801         'Holdings View Show Empty Orgs',
802         'cwst', 'label'
803     )
804 ), (
805     'cat.holdings_show_vols', 'cat', 'bool',
806     oils_i18n_gettext(
807         'cat.holdings_show_vols',
808         'Holdings View Show Volumes',
809         'cwst', 'label'
810     )
811 ), (
812     'cat.copy.defaults', 'cat', 'object',
813     oils_i18n_gettext(
814         'cat.copy.defaults',
815         'Copy Edit Default Values',
816         'cwst', 'label'
817     )
818 ), (
819     'cat.printlabels.default_template', 'cat', 'string',
820     oils_i18n_gettext(
821         'cat.printlabels.default_template',
822         'Print Label Default Template',
823         'cwst', 'label'
824     )
825 ), (
826     'cat.printlabels.templates', 'cat', 'object',
827     oils_i18n_gettext(
828         'cat.printlabels.templates',
829         'Print Label Templates',
830         'cwst', 'label'
831     )
832 ), (
833     'eg.circ.patron.search.include_inactive', 'circ', 'bool',
834     oils_i18n_gettext(
835         'eg.circ.patron.search.include_inactive',
836         'Patron Search Include Inactive',
837         'cwst', 'label'
838     )
839 ), (
840     'eg.circ.patron.search.show_extras', 'circ', 'bool',
841     oils_i18n_gettext(
842         'eg.circ.patron.search.show_extras',
843         'Patron Search Show Extra Search Options',
844         'cwst', 'label'
845     )
846 ), (
847     'eg.grid.circ.checkin.checkin', 'gui', 'object',
848     oils_i18n_gettext(
849         'eg.grid.circ.checkin.checkin',
850         'Grid Config: circ.checkin.checkin',
851         'cwst', 'label'
852     )
853 ), (
854     'eg.grid.circ.checkin.capture', 'gui', 'object',
855     oils_i18n_gettext(
856         'eg.grid.circ.checkin.capture',
857         'Grid Config: circ.checkin.capture',
858         'cwst', 'label'
859     )
860 ), (
861     'eg.grid.admin.server.config.copy_tag_type', 'gui', 'object',
862     oils_i18n_gettext(
863         'eg.grid.admin.server.config.copy_tag_type',
864         'Grid Config: admin.server.config.copy_tag_type',
865         'cwst', 'label'
866     )
867 ), (
868     'eg.grid.admin.server.config.metabib_field_virtual_map.grid', 'gui', 'object',
869     oils_i18n_gettext(
870         'eg.grid.admin.server.config.metabib_field_virtual_map.grid',
871         'Grid Config: admin.server.config.metabib_field_virtual_map.grid',
872         'cwst', 'label'
873     )
874 ), (
875     'eg.grid.admin.server.config.metabib_field.grid', 'gui', 'object',
876     oils_i18n_gettext(
877         'eg.grid.admin.server.config.metabib_field.grid',
878         'Grid Config: admin.server.config.metabib_field.grid',
879         'cwst', 'label'
880     )
881 ), (
882     'eg.grid.admin.server.config.marc_field', 'gui', 'object',
883     oils_i18n_gettext(
884         'eg.grid.admin.server.config.marc_field',
885         'Grid Config: admin.server.config.marc_field',
886         'cwst', 'label'
887     )
888 ), (
889     'eg.grid.admin.server.asset.copy_tag', 'gui', 'object',
890     oils_i18n_gettext(
891         'eg.grid.admin.server.asset.copy_tag',
892         'Grid Config: admin.server.asset.copy_tag',
893         'cwst', 'label'
894     )
895 ), (
896     'eg.grid.admin.local.circ.neg_balance_users', 'gui', 'object',
897     oils_i18n_gettext(
898         'eg.grid.admin.local.circ.neg_balance_users',
899         'Grid Config: admin.local.circ.neg_balance_users',
900         'cwst', 'label'
901     )
902 ), (
903     'eg.grid.admin.local.rating.badge', 'gui', 'object',
904     oils_i18n_gettext(
905         'eg.grid.admin.local.rating.badge',
906         'Grid Config: admin.local.rating.badge',
907         'cwst', 'label'
908     )
909 ), (
910     'eg.grid.admin.workstation.work_log', 'gui', 'object',
911     oils_i18n_gettext(
912         'eg.grid.admin.workstation.work_log',
913         'Grid Config: admin.workstation.work_log',
914         'cwst', 'label'
915     )
916 ), (
917     'eg.grid.admin.workstation.patron_log', 'gui', 'object',
918     oils_i18n_gettext(
919         'eg.grid.admin.workstation.patron_log',
920         'Grid Config: admin.workstation.patron_log',
921         'cwst', 'label'
922     )
923 ), (
924     'eg.grid.admin.serials.pattern_template', 'gui', 'object',
925     oils_i18n_gettext(
926         'eg.grid.admin.serials.pattern_template',
927         'Grid Config: admin.serials.pattern_template',
928         'cwst', 'label'
929     )
930 ), (
931     'eg.grid.serials.copy_templates', 'gui', 'object',
932     oils_i18n_gettext(
933         'eg.grid.serials.copy_templates',
934         'Grid Config: serials.copy_templates',
935         'cwst', 'label'
936     )
937 ), (
938     'eg.grid.cat.record_overlay.holdings', 'gui', 'object',
939     oils_i18n_gettext(
940         'eg.grid.cat.record_overlay.holdings',
941         'Grid Config: cat.record_overlay.holdings',
942         'cwst', 'label'
943     )
944 ), (
945     'eg.grid.cat.bucket.record.search', 'gui', 'object',
946     oils_i18n_gettext(
947         'eg.grid.cat.bucket.record.search',
948         'Grid Config: cat.bucket.record.search',
949         'cwst', 'label'
950     )
951 ), (
952     'eg.grid.cat.bucket.record.view', 'gui', 'object',
953     oils_i18n_gettext(
954         'eg.grid.cat.bucket.record.view',
955         'Grid Config: cat.bucket.record.view',
956         'cwst', 'label'
957     )
958 ), (
959     'eg.grid.cat.bucket.record.pending', 'gui', 'object',
960     oils_i18n_gettext(
961         'eg.grid.cat.bucket.record.pending',
962         'Grid Config: cat.bucket.record.pending',
963         'cwst', 'label'
964     )
965 ), (
966     'eg.grid.cat.bucket.copy.view', 'gui', 'object',
967     oils_i18n_gettext(
968         'eg.grid.cat.bucket.copy.view',
969         'Grid Config: cat.bucket.copy.view',
970         'cwst', 'label'
971     )
972 ), (
973     'eg.grid.cat.bucket.copy.pending', 'gui', 'object',
974     oils_i18n_gettext(
975         'eg.grid.cat.bucket.copy.pending',
976         'Grid Config: cat.bucket.copy.pending',
977         'cwst', 'label'
978     )
979 ), (
980     'eg.grid.cat.items', 'gui', 'object',
981     oils_i18n_gettext(
982         'eg.grid.cat.items',
983         'Grid Config: cat.items',
984         'cwst', 'label'
985     )
986 ), (
987     'eg.grid.cat.volcopy.copies', 'gui', 'object',
988     oils_i18n_gettext(
989         'eg.grid.cat.volcopy.copies',
990         'Grid Config: cat.volcopy.copies',
991         'cwst', 'label'
992     )
993 ), (
994     'eg.grid.cat.volcopy.copies.complete', 'gui', 'object',
995     oils_i18n_gettext(
996         'eg.grid.cat.volcopy.copies.complete',
997         'Grid Config: cat.volcopy.copies.complete',
998         'cwst', 'label'
999     )
1000 ), (
1001     'eg.grid.cat.peer_bibs', 'gui', 'object',
1002     oils_i18n_gettext(
1003         'eg.grid.cat.peer_bibs',
1004         'Grid Config: cat.peer_bibs',
1005         'cwst', 'label'
1006     )
1007 ), (
1008     'eg.grid.cat.catalog.holds', 'gui', 'object',
1009     oils_i18n_gettext(
1010         'eg.grid.cat.catalog.holds',
1011         'Grid Config: cat.catalog.holds',
1012         'cwst', 'label'
1013     )
1014 ), (
1015     'eg.grid.cat.holdings', 'gui', 'object',
1016     oils_i18n_gettext(
1017         'eg.grid.cat.holdings',
1018         'Grid Config: cat.holdings',
1019         'cwst', 'label'
1020     )
1021 ), (
1022     'eg.grid.cat.z3950_results', 'gui', 'object',
1023     oils_i18n_gettext(
1024         'eg.grid.cat.z3950_results',
1025         'Grid Config: cat.z3950_results',
1026         'cwst', 'label'
1027     )
1028 ), (
1029     'eg.grid.circ.holds.shelf', 'gui', 'object',
1030     oils_i18n_gettext(
1031         'eg.grid.circ.holds.shelf',
1032         'Grid Config: circ.holds.shelf',
1033         'cwst', 'label'
1034     )
1035 ), (
1036     'eg.grid.circ.holds.pull', 'gui', 'object',
1037     oils_i18n_gettext(
1038         'eg.grid.circ.holds.pull',
1039         'Grid Config: circ.holds.pull',
1040         'cwst', 'label'
1041     )
1042 ), (
1043     'eg.grid.circ.in_house_use', 'gui', 'object',
1044     oils_i18n_gettext(
1045         'eg.grid.circ.in_house_use',
1046         'Grid Config: circ.in_house_use',
1047         'cwst', 'label'
1048     )
1049 ), (
1050     'eg.grid.circ.renew', 'gui', 'object',
1051     oils_i18n_gettext(
1052         'eg.grid.circ.renew',
1053         'Grid Config: circ.renew',
1054         'cwst', 'label'
1055     )
1056 ), (
1057     'eg.grid.circ.transits.list', 'gui', 'object',
1058     oils_i18n_gettext(
1059         'eg.grid.circ.transits.list',
1060         'Grid Config: circ.transits.list',
1061         'cwst', 'label'
1062     )
1063 ), (
1064     'eg.grid.circ.patron.holds', 'gui', 'object',
1065     oils_i18n_gettext(
1066         'eg.grid.circ.patron.holds',
1067         'Grid Config: circ.patron.holds',
1068         'cwst', 'label'
1069     )
1070 ), (
1071     'eg.grid.circ.pending_patrons.list', 'gui', 'object',
1072     oils_i18n_gettext(
1073         'eg.grid.circ.pending_patrons.list',
1074         'Grid Config: circ.pending_patrons.list',
1075         'cwst', 'label'
1076     )
1077 ), (
1078     'eg.grid.circ.patron.items_out.noncat', 'gui', 'object',
1079     oils_i18n_gettext(
1080         'eg.grid.circ.patron.items_out.noncat',
1081         'Grid Config: circ.patron.items_out.noncat',
1082         'cwst', 'label'
1083     )
1084 ), (
1085     'eg.grid.circ.patron.items_out', 'gui', 'object',
1086     oils_i18n_gettext(
1087         'eg.grid.circ.patron.items_out',
1088         'Grid Config: circ.patron.items_out',
1089         'cwst', 'label'
1090     )
1091 ), (
1092     'eg.grid.circ.patron.billhistory_payments', 'gui', 'object',
1093     oils_i18n_gettext(
1094         'eg.grid.circ.patron.billhistory_payments',
1095         'Grid Config: circ.patron.billhistory_payments',
1096         'cwst', 'label'
1097     )
1098 ), (
1099     'eg.grid.user.bucket.view', 'gui', 'object',
1100     oils_i18n_gettext(
1101         'eg.grid.user.bucket.view',
1102         'Grid Config: user.bucket.view',
1103         'cwst', 'label'
1104     )
1105 ), (
1106     'eg.grid.user.bucket.pending', 'gui', 'object',
1107     oils_i18n_gettext(
1108         'eg.grid.user.bucket.pending',
1109         'Grid Config: user.bucket.pending',
1110         'cwst', 'label'
1111     )
1112 ), (
1113     'eg.grid.circ.patron.staff_messages', 'gui', 'object',
1114     oils_i18n_gettext(
1115         'eg.grid.circ.patron.staff_messages',
1116         'Grid Config: circ.patron.staff_messages',
1117         'cwst', 'label'
1118     )
1119 ), (
1120     'eg.grid.circ.patron.archived_messages', 'gui', 'object',
1121     oils_i18n_gettext(
1122         'eg.grid.circ.patron.archived_messages',
1123         'Grid Config: circ.patron.archived_messages',
1124         'cwst', 'label'
1125     )
1126 ), (
1127     'eg.grid.circ.patron.bills', 'gui', 'object',
1128     oils_i18n_gettext(
1129         'eg.grid.circ.patron.bills',
1130         'Grid Config: circ.patron.bills',
1131         'cwst', 'label'
1132     )
1133 ), (
1134     'eg.grid.circ.patron.checkout', 'gui', 'object',
1135     oils_i18n_gettext(
1136         'eg.grid.circ.patron.checkout',
1137         'Grid Config: circ.patron.checkout',
1138         'cwst', 'label'
1139     )
1140 ), (
1141     'eg.grid.serials.mfhd_grid', 'gui', 'object',
1142     oils_i18n_gettext(
1143         'eg.grid.serials.mfhd_grid',
1144         'Grid Config: serials.mfhd_grid',
1145         'cwst', 'label'
1146     )
1147 ), (
1148     'eg.grid.serials.view_item_grid', 'gui', 'object',
1149     oils_i18n_gettext(
1150         'eg.grid.serials.view_item_grid',
1151         'Grid Config: serials.view_item_grid',
1152         'cwst', 'label'
1153     )
1154 ), (
1155     'eg.grid.serials.dist_stream_grid', 'gui', 'object',
1156     oils_i18n_gettext(
1157         'eg.grid.serials.dist_stream_grid',
1158         'Grid Config: serials.dist_stream_grid',
1159         'cwst', 'label'
1160     )
1161 ), (
1162     'eg.grid.circ.patron.search', 'gui', 'object',
1163     oils_i18n_gettext(
1164         'eg.grid.circ.patron.search',
1165         'Grid Config: circ.patron.search',
1166         'cwst', 'label'
1167     )
1168 ), (
1169     'eg.cat.record.summary.collapse', 'gui', 'bool',
1170     oils_i18n_gettext(
1171         'eg.cat.record.summary.collapse',
1172         'Collapse Bib Record Summary',
1173         'cwst', 'label'
1174     )
1175 ), (
1176     'cat.marcedit.flateditor', 'gui', 'bool',
1177     oils_i18n_gettext(
1178         'cat.marcedit.flateditor',
1179         'Use Flat MARC Editor',
1180         'cwst', 'label'
1181     )
1182 ), (
1183     'cat.marcedit.stack_subfields', 'gui', 'bool',
1184     oils_i18n_gettext(
1185         'cat.marcedit.stack_subfields',
1186         'MARC Editor Stack Subfields',
1187         'cwst', 'label'
1188     )
1189 ), (
1190     'eg.offline.print_receipt', 'gui', 'bool',
1191     oils_i18n_gettext(
1192         'eg.offline.print_receipt',
1193         'Offline Print Receipt',
1194         'cwst', 'label'
1195     )
1196 ), (
1197     'eg.offline.strict_barcode', 'gui', 'bool',
1198     oils_i18n_gettext(
1199         'eg.offline.strict_barcode',
1200         'Offline Use Strict Barcode',
1201         'cwst', 'label'
1202     )
1203 ), (
1204     'cat.default_bib_marc_template', 'gui', 'string',
1205     oils_i18n_gettext(
1206         'cat.default_bib_marc_template',
1207         'Default MARC Template',
1208         'cwst', 'label'
1209     )
1210 ), (
1211     'eg.audio.disable', 'gui', 'bool',
1212     oils_i18n_gettext(
1213         'eg.audio.disable',
1214         'Disable Staff Client Notification Audio',
1215         'cwst', 'label'
1216     )
1217 ), (
1218     'eg.search.adv_pane', 'gui', 'string',
1219     oils_i18n_gettext(
1220         'eg.search.adv_pane',
1221         'Catalog Advanced Search Default Pane',
1222         'cwst', 'label'
1223     )
1224 ), (
1225     'eg.print.template_context.bills_current', 'gui', 'string',
1226     oils_i18n_gettext(
1227         'eg.print.template_context.bills_current',
1228         'Print Template Context: bills_current',
1229         'cwst', 'label'
1230     )
1231 ), (
1232     'eg.print.template.bills_current', 'gui', 'string',
1233     oils_i18n_gettext(
1234         'eg.print.template.bills_current',
1235         'Print Template: bills_current',
1236         'cwst', 'label'
1237     )
1238 ), (
1239     'eg.print.template_context.bills_historical', 'gui', 'string',
1240     oils_i18n_gettext(
1241         'eg.print.template_context.bills_historical',
1242         'Print Template Context: bills_historical',
1243         'cwst', 'label'
1244     )
1245 ), (
1246     'eg.print.template.bills_historical', 'gui', 'string',
1247     oils_i18n_gettext(
1248         'eg.print.template.bills_historical',
1249         'Print Template: bills_historical',
1250         'cwst', 'label'
1251     )
1252 ), (
1253     'eg.print.template_context.bill_payment', 'gui', 'string',
1254     oils_i18n_gettext(
1255         'eg.print.template_context.bill_payment',
1256         'Print Template Context: bill_payment',
1257         'cwst', 'label'
1258     )
1259 ), (
1260     'eg.print.template.bill_payment', 'gui', 'string',
1261     oils_i18n_gettext(
1262         'eg.print.template.bill_payment',
1263         'Print Template: bill_payment',
1264         'cwst', 'label'
1265     )
1266 ), (
1267     'eg.print.template_context.checkin', 'gui', 'string',
1268     oils_i18n_gettext(
1269         'eg.print.template_context.checkin',
1270         'Print Template Context: checkin',
1271         'cwst', 'label'
1272     )
1273 ), (
1274     'eg.print.template.checkin', 'gui', 'string',
1275     oils_i18n_gettext(
1276         'eg.print.template.checkin',
1277         'Print Template: checkin',
1278         'cwst', 'label'
1279     )
1280 ), (
1281     'eg.print.template_context.checkout', 'gui', 'string',
1282     oils_i18n_gettext(
1283         'eg.print.template_context.checkout',
1284         'Print Template Context: checkout',
1285         'cwst', 'label'
1286     )
1287 ), (
1288     'eg.print.template.checkout', 'gui', 'string',
1289     oils_i18n_gettext(
1290         'eg.print.template.checkout',
1291         'Print Template: checkout',
1292         'cwst', 'label'
1293     )
1294 ), (
1295     'eg.print.template_context.hold_transit_slip', 'gui', 'string',
1296     oils_i18n_gettext(
1297         'eg.print.template_context.hold_transit_slip',
1298         'Print Template Context: hold_transit_slip',
1299         'cwst', 'label'
1300     )
1301 ), (
1302     'eg.print.template.hold_transit_slip', 'gui', 'string',
1303     oils_i18n_gettext(
1304         'eg.print.template.hold_transit_slip',
1305         'Print Template: hold_transit_slip',
1306         'cwst', 'label'
1307     )
1308 ), (
1309     'eg.print.template_context.hold_shelf_slip', 'gui', 'string',
1310     oils_i18n_gettext(
1311         'eg.print.template_context.hold_shelf_slip',
1312         'Print Template Context: hold_shelf_slip',
1313         'cwst', 'label'
1314     )
1315 ), (
1316     'eg.print.template.hold_shelf_slip', 'gui', 'string',
1317     oils_i18n_gettext(
1318         'eg.print.template.hold_shelf_slip',
1319         'Print Template: hold_shelf_slip',
1320         'cwst', 'label'
1321     )
1322 ), (
1323     'eg.print.template_context.holds_for_bib', 'gui', 'string',
1324     oils_i18n_gettext(
1325         'eg.print.template_context.holds_for_bib',
1326         'Print Template Context: holds_for_bib',
1327         'cwst', 'label'
1328     )
1329 ), (
1330     'eg.print.template.holds_for_bib', 'gui', 'string',
1331     oils_i18n_gettext(
1332         'eg.print.template.holds_for_bib',
1333         'Print Template: holds_for_bib',
1334         'cwst', 'label'
1335     )
1336 ), (
1337     'eg.print.template_context.holds_for_patron', 'gui', 'string',
1338     oils_i18n_gettext(
1339         'eg.print.template_context.holds_for_patron',
1340         'Print Template Context: holds_for_patron',
1341         'cwst', 'label'
1342     )
1343 ), (
1344     'eg.print.template.holds_for_patron', 'gui', 'string',
1345     oils_i18n_gettext(
1346         'eg.print.template.holds_for_patron',
1347         'Print Template: holds_for_patron',
1348         'cwst', 'label'
1349     )
1350 ), (
1351     'eg.print.template_context.hold_pull_list', 'gui', 'string',
1352     oils_i18n_gettext(
1353         'eg.print.template_context.hold_pull_list',
1354         'Print Template Context: hold_pull_list',
1355         'cwst', 'label'
1356     )
1357 ), (
1358     'eg.print.template.hold_pull_list', 'gui', 'string',
1359     oils_i18n_gettext(
1360         'eg.print.template.hold_pull_list',
1361         'Print Template: hold_pull_list',
1362         'cwst', 'label'
1363     )
1364 ), (
1365     'eg.print.template_context.hold_shelf_list', 'gui', 'string',
1366     oils_i18n_gettext(
1367         'eg.print.template_context.hold_shelf_list',
1368         'Print Template Context: hold_shelf_list',
1369         'cwst', 'label'
1370     )
1371 ), (
1372     'eg.print.template.hold_shelf_list', 'gui', 'string',
1373     oils_i18n_gettext(
1374         'eg.print.template.hold_shelf_list',
1375         'Print Template: hold_shelf_list',
1376         'cwst', 'label'
1377     )
1378 ), (
1379     'eg.print.template_context.in_house_use_list', 'gui', 'string',
1380     oils_i18n_gettext(
1381         'eg.print.template_context.in_house_use_list',
1382         'Print Template Context: in_house_use_list',
1383         'cwst', 'label'
1384     )
1385 ), (
1386     'eg.print.template.in_house_use_list', 'gui', 'string',
1387     oils_i18n_gettext(
1388         'eg.print.template.in_house_use_list',
1389         'Print Template: in_house_use_list',
1390         'cwst', 'label'
1391     )
1392 ), (
1393     'eg.print.template_context.item_status', 'gui', 'string',
1394     oils_i18n_gettext(
1395         'eg.print.template_context.item_status',
1396         'Print Template Context: item_status',
1397         'cwst', 'label'
1398     )
1399 ), (
1400     'eg.print.template.item_status', 'gui', 'string',
1401     oils_i18n_gettext(
1402         'eg.print.template.item_status',
1403         'Print Template: item_status',
1404         'cwst', 'label'
1405     )
1406 ), (
1407     'eg.print.template_context.items_out', 'gui', 'string',
1408     oils_i18n_gettext(
1409         'eg.print.template_context.items_out',
1410         'Print Template Context: items_out',
1411         'cwst', 'label'
1412     )
1413 ), (
1414     'eg.print.template.items_out', 'gui', 'string',
1415     oils_i18n_gettext(
1416         'eg.print.template.items_out',
1417         'Print Template: items_out',
1418         'cwst', 'label'
1419     )
1420 ), (
1421     'eg.print.template_context.patron_address', 'gui', 'string',
1422     oils_i18n_gettext(
1423         'eg.print.template_context.patron_address',
1424         'Print Template Context: patron_address',
1425         'cwst', 'label'
1426     )
1427 ), (
1428     'eg.print.template.patron_address', 'gui', 'string',
1429     oils_i18n_gettext(
1430         'eg.print.template.patron_address',
1431         'Print Template: patron_address',
1432         'cwst', 'label'
1433     )
1434 ), (
1435     'eg.print.template_context.patron_data', 'gui', 'string',
1436     oils_i18n_gettext(
1437         'eg.print.template_context.patron_data',
1438         'Print Template Context: patron_data',
1439         'cwst', 'label'
1440     )
1441 ), (
1442     'eg.print.template.patron_data', 'gui', 'string',
1443     oils_i18n_gettext(
1444         'eg.print.template.patron_data',
1445         'Print Template: patron_data',
1446         'cwst', 'label'
1447     )
1448 ), (
1449     'eg.print.template_context.patron_note', 'gui', 'string',
1450     oils_i18n_gettext(
1451         'eg.print.template_context.patron_note',
1452         'Print Template Context: patron_note',
1453         'cwst', 'label'
1454     )
1455 ), (
1456     'eg.print.template.patron_note', 'gui', 'string',
1457     oils_i18n_gettext(
1458         'eg.print.template.patron_note',
1459         'Print Template: patron_note',
1460         'cwst', 'label'
1461     )
1462 ), (
1463     'eg.print.template_context.renew', 'gui', 'string',
1464     oils_i18n_gettext(
1465         'eg.print.template_context.renew',
1466         'Print Template Context: renew',
1467         'cwst', 'label'
1468     )
1469 ), (
1470     'eg.print.template.renew', 'gui', 'string',
1471     oils_i18n_gettext(
1472         'eg.print.template.renew',
1473         'Print Template: renew',
1474         'cwst', 'label'
1475     )
1476 ), (
1477     'eg.print.template_context.transit_list', 'gui', 'string',
1478     oils_i18n_gettext(
1479         'eg.print.template_context.transit_list',
1480         'Print Template Context: transit_list',
1481         'cwst', 'label'
1482     )
1483 ), (
1484     'eg.print.template.transit_list', 'gui', 'string',
1485     oils_i18n_gettext(
1486         'eg.print.template.transit_list',
1487         'Print Template: transit_list',
1488         'cwst', 'label'
1489     )
1490 ), (
1491     'eg.print.template_context.transit_slip', 'gui', 'string',
1492     oils_i18n_gettext(
1493         'eg.print.template_context.transit_slip',
1494         'Print Template Context: transit_slip',
1495         'cwst', 'label'
1496     )
1497 ), (
1498     'eg.print.template.transit_slip', 'gui', 'string',
1499     oils_i18n_gettext(
1500         'eg.print.template.transit_slip',
1501         'Print Template: transit_slip',
1502         'cwst', 'label'
1503     )
1504 ), (
1505     'eg.print.template_context.offline_checkout', 'gui', 'string',
1506     oils_i18n_gettext(
1507         'eg.print.template_context.offline_checkout',
1508         'Print Template Context: offline_checkout',
1509         'cwst', 'label'
1510     )
1511 ), (
1512     'eg.print.template.offline_checkout', 'gui', 'string',
1513     oils_i18n_gettext(
1514         'eg.print.template.offline_checkout',
1515         'Print Template: offline_checkout',
1516         'cwst', 'label'
1517     )
1518 ), (
1519     'eg.print.template_context.offline_renew', 'gui', 'string',
1520     oils_i18n_gettext(
1521         'eg.print.template_context.offline_renew',
1522         'Print Template Context: offline_renew',
1523         'cwst', 'label'
1524     )
1525 ), (
1526     'eg.print.template.offline_renew', 'gui', 'string',
1527     oils_i18n_gettext(
1528         'eg.print.template.offline_renew',
1529         'Print Template: offline_renew',
1530         'cwst', 'label'
1531     )
1532 ), (
1533     'eg.print.template_context.offline_checkin', 'gui', 'string',
1534     oils_i18n_gettext(
1535         'eg.print.template_context.offline_checkin',
1536         'Print Template Context: offline_checkin',
1537         'cwst', 'label'
1538     )
1539 ), (
1540     'eg.print.template.offline_checkin', 'gui', 'string',
1541     oils_i18n_gettext(
1542         'eg.print.template.offline_checkin',
1543         'Print Template: offline_checkin',
1544         'cwst', 'label'
1545     )
1546 ), (
1547     'eg.print.template_context.offline_in_house_use', 'gui', 'string',
1548     oils_i18n_gettext(
1549         'eg.print.template_context.offline_in_house_use',
1550         'Print Template Context: offline_in_house_use',
1551         'cwst', 'label'
1552     )
1553 ), (
1554     'eg.print.template.offline_in_house_use', 'gui', 'string',
1555     oils_i18n_gettext(
1556         'eg.print.template.offline_in_house_use',
1557         'Print Template: offline_in_house_use',
1558         'cwst', 'label'
1559     )
1560 ), (
1561     'eg.serials.stream_names', 'gui', 'array',
1562     oils_i18n_gettext(
1563         'eg.serials.stream_names',
1564         'Serials Local Stream Names',
1565         'cwst', 'label'
1566     )
1567 ), (
1568     'eg.serials.items.do_print_routing_lists', 'gui', 'bool',
1569     oils_i18n_gettext(
1570         'eg.serials.items.do_print_routing_lists',
1571         'Serials Print Routing Lists',
1572         'cwst', 'label'
1573     )
1574 ), (
1575     'eg.serials.items.receive_and_barcode', 'gui', 'bool',
1576     oils_i18n_gettext(
1577         'eg.serials.items.receive_and_barcode',
1578         'Serials Barcode On Receive',
1579         'cwst', 'label'
1580     )
1581 );
1582
1583
1584 -- More values with fm_class'es
1585 INSERT INTO config.workstation_setting_type (name, grp, datatype, fm_class, label)
1586 VALUES (
1587     'eg.search.search_lib', 'gui', 'link', 'aou',
1588     oils_i18n_gettext(
1589         'eg.search.search_lib',
1590         'Staff Catalog Default Search Library',
1591         'cwst', 'label'
1592     )
1593 ), (
1594     'eg.search.pref_lib', 'gui', 'link', 'aou',
1595     oils_i18n_gettext(
1596         'eg.search.pref_lib',
1597         'Staff Catalog Preferred Library',
1598         'cwst', 'label'
1599     )
1600 );
1601
1602
1603
1604
1605
1606 SELECT evergreen.upgrade_deps_block_check('1118', :eg_version);
1607
1608 UPDATE action_trigger.event_definition
1609 SET template =
1610 $$
1611 [%- USE date -%]
1612 [%- SET user = target.0.owner -%]
1613 To: [%- params.recipient_email || user.email %]
1614 From: [%- params.sender_email || default_sender %]
1615 Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %]
1616 Subject: Bibliographic Records
1617 Auto-Submitted: auto-generated
1618
1619 [% FOR cbreb IN target %]
1620 [% FOR item IN cbreb.items;
1621     bre_id = item.target_biblio_record_entry;
1622
1623     bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
1624     title = '';
1625     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
1626         title = title _ part.textContent;
1627     END;
1628
1629     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
1630     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
1631     publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
1632     pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
1633     isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
1634     issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
1635     upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
1636 %]
1637
1638 [% loop.count %]/[% loop.size %].  Bib ID# [% bre_id %] 
1639 [% IF isbn %]ISBN: [% isbn _ "\n" %][% END -%]
1640 [% IF issn %]ISSN: [% issn _ "\n" %][% END -%]
1641 [% IF upc  %]UPC:  [% upc _ "\n" %] [% END -%]
1642 Title: [% title %]
1643 Author: [% author %]
1644 Publication Info: [% publisher %] [% pubdate %]
1645 Item Type: [% item_type %]
1646
1647 [% END %]
1648 [% END %]
1649 $$
1650 WHERE hook = 'biblio.format.record_entry.email'
1651 -- from previous stock definition
1652 AND MD5(template) = 'ee4e6c1b3049086c570c7a77413d46c1';
1653
1654 UPDATE action_trigger.event_definition
1655 SET template =
1656 $$
1657 <div>
1658     <style> li { padding: 8px; margin 5px; }</style>
1659     <ol>
1660     [% FOR cbreb IN target %]
1661     [% FOR item IN cbreb.items;
1662         bre_id = item.target_biblio_record_entry;
1663
1664         bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
1665         title = '';
1666         FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
1667             title = title _ part.textContent;
1668         END;
1669
1670         author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
1671         item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
1672         publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
1673         pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
1674         isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
1675         %]
1676
1677         <li>
1678             Bib ID# [% bre_id %] ISBN: [% isbn %]<br />
1679             Title: [% title %]<br />
1680             Author: [% author %]<br />
1681             Publication Info: [% publisher %] [% pubdate %]<br/>
1682             Item Type: [% item_type %]
1683         </li>
1684     [% END %]
1685     [% END %]
1686     </ol>
1687 </div>
1688 $$
1689 WHERE hook = 'biblio.format.record_entry.print'
1690 -- from previous stock definition
1691 AND MD5(template) = '9ada7ea8417cb23f89d0dc8f15ec68d0';
1692
1693
1694 SELECT evergreen.upgrade_deps_block_check('1120', :eg_version);
1695
1696 --Only insert if the attributes are not already present
1697
1698 INSERT INTO config.z3950_attr (source, name, label, code, format, truncation)
1699 SELECT 'oclc','upc','UPC','1007','6','0'
1700 WHERE NOT EXISTS (SELECT name FROM config.z3950_attr WHERE source = 'oclc' AND name = 'upc');
1701
1702 INSERT INTO config.z3950_attr (source, name, label, code, format, truncation)
1703 SELECT 'loc','upc','UPC','1007','1','1'
1704 WHERE NOT EXISTS (SELECT name FROM config.z3950_attr WHERE source = 'loc' AND name = 'upc');
1705
1706 SELECT evergreen.upgrade_deps_block_check('1121', :eg_version);
1707
1708 CREATE TABLE permission.grp_tree_display_entry (
1709     id      SERIAL PRIMARY KEY,
1710     position INTEGER NOT NULL,
1711     org     INTEGER NOT NULL REFERENCES actor.org_unit (id)
1712             DEFERRABLE INITIALLY DEFERRED,
1713     grp     INTEGER NOT NULL REFERENCES permission.grp_tree (id)
1714             DEFERRABLE INITIALLY DEFERRED,
1715     CONSTRAINT pgtde_once_per_org UNIQUE (org, grp)
1716 );
1717
1718 ALTER TABLE permission.grp_tree_display_entry
1719     ADD COLUMN parent integer REFERENCES permission.grp_tree_display_entry (id)
1720             DEFERRABLE INITIALLY DEFERRED;
1721
1722 INSERT INTO permission.perm_list (id, code, description)
1723 VALUES (609, 'MANAGE_CUSTOM_PERM_GRP_TREE', oils_i18n_gettext( 609,
1724     'Allows a user to manage custom permission group lists.', 'ppl', 'description' ));
1725             
1726
1727 SELECT evergreen.upgrade_deps_block_check('1122', :eg_version);
1728
1729 ALTER TABLE actor.usr 
1730     ADD COLUMN pref_prefix TEXT,
1731     ADD COLUMN pref_first_given_name TEXT,
1732     ADD COLUMN pref_second_given_name TEXT,
1733     ADD COLUMN pref_family_name TEXT,
1734     ADD COLUMN pref_suffix TEXT,
1735     ADD COLUMN name_keywords TEXT,
1736     ADD COLUMN name_kw_tsvector TSVECTOR;
1737
1738 ALTER TABLE staging.user_stage
1739     ADD COLUMN pref_first_given_name TEXT,
1740     ADD COLUMN pref_second_given_name TEXT,
1741     ADD COLUMN pref_family_name TEXT;
1742
1743 CREATE INDEX actor_usr_pref_first_given_name_idx 
1744     ON actor.usr (evergreen.lowercase(pref_first_given_name));
1745 CREATE INDEX actor_usr_pref_second_given_name_idx 
1746     ON actor.usr (evergreen.lowercase(pref_second_given_name));
1747 CREATE INDEX actor_usr_pref_family_name_idx 
1748     ON actor.usr (evergreen.lowercase(pref_family_name));
1749 CREATE INDEX actor_usr_pref_first_given_name_unaccent_idx 
1750     ON actor.usr (evergreen.unaccent_and_squash(pref_first_given_name));
1751 CREATE INDEX actor_usr_pref_second_given_name_unaccent_idx 
1752     ON actor.usr (evergreen.unaccent_and_squash(pref_second_given_name));
1753 CREATE INDEX actor_usr_pref_family_name_unaccent_idx 
1754    ON actor.usr (evergreen.unaccent_and_squash(pref_family_name));
1755
1756 -- Update keyword indexes for existing patrons
1757
1758 UPDATE actor.usr SET name_kw_tsvector = 
1759     TO_TSVECTOR(
1760         COALESCE(prefix, '') || ' ' || 
1761         COALESCE(first_given_name, '') || ' ' || 
1762         COALESCE(evergreen.unaccent_and_squash(first_given_name), '') || ' ' || 
1763         COALESCE(second_given_name, '') || ' ' || 
1764         COALESCE(evergreen.unaccent_and_squash(second_given_name), '') || ' ' || 
1765         COALESCE(family_name, '') || ' ' || 
1766         COALESCE(evergreen.unaccent_and_squash(family_name), '') || ' ' || 
1767         COALESCE(suffix, '')
1768     );
1769
1770 CREATE OR REPLACE FUNCTION actor.user_ingest_name_keywords() 
1771     RETURNS TRIGGER AS $func$
1772 BEGIN
1773     NEW.name_kw_tsvector := TO_TSVECTOR(
1774         COALESCE(NEW.prefix, '')                || ' ' || 
1775         COALESCE(NEW.first_given_name, '')      || ' ' || 
1776         COALESCE(evergreen.unaccent_and_squash(NEW.first_given_name), '') || ' ' || 
1777         COALESCE(NEW.second_given_name, '')     || ' ' || 
1778         COALESCE(evergreen.unaccent_and_squash(NEW.second_given_name), '') || ' ' || 
1779         COALESCE(NEW.family_name, '')           || ' ' || 
1780         COALESCE(evergreen.unaccent_and_squash(NEW.family_name), '') || ' ' || 
1781         COALESCE(NEW.suffix, '')                || ' ' || 
1782         COALESCE(NEW.pref_prefix, '')            || ' ' || 
1783         COALESCE(NEW.pref_first_given_name, '')  || ' ' || 
1784         COALESCE(evergreen.unaccent_and_squash(NEW.pref_first_given_name), '') || ' ' || 
1785         COALESCE(NEW.pref_second_given_name, '') || ' ' || 
1786         COALESCE(evergreen.unaccent_and_squash(NEW.pref_second_given_name), '') || ' ' || 
1787         COALESCE(NEW.pref_family_name, '')       || ' ' || 
1788         COALESCE(evergreen.unaccent_and_squash(NEW.pref_family_name), '') || ' ' || 
1789         COALESCE(NEW.pref_suffix, '')            || ' ' || 
1790         COALESCE(NEW.name_keywords, '')
1791     );
1792     RETURN NEW;
1793 END;
1794 $func$ LANGUAGE PLPGSQL;
1795
1796 -- Add after the batch upate above to avoid duplicate updates.
1797 CREATE TRIGGER user_ingest_name_keywords_tgr 
1798     BEFORE INSERT OR UPDATE ON actor.usr 
1799     FOR EACH ROW EXECUTE PROCEDURE actor.user_ingest_name_keywords();
1800
1801
1802 -- merge pref names from source user to target user, except when
1803 -- clobbering existing pref names.
1804 CREATE OR REPLACE FUNCTION actor.usr_merge(src_usr INT, dest_usr INT, 
1805     del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) 
1806     RETURNS VOID AS $$
1807 DECLARE
1808         suffix TEXT;
1809         bucket_row RECORD;
1810         picklist_row RECORD;
1811         queue_row RECORD;
1812         folder_row RECORD;
1813 BEGIN
1814
1815     -- do some initial cleanup 
1816     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
1817     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
1818     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
1819
1820     -- actor.*
1821     IF del_cards THEN
1822         DELETE FROM actor.card where usr = src_usr;
1823     ELSE
1824         IF deactivate_cards THEN
1825             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
1826         END IF;
1827         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
1828     END IF;
1829
1830
1831     IF del_addrs THEN
1832         DELETE FROM actor.usr_address WHERE usr = src_usr;
1833     ELSE
1834         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
1835     END IF;
1836
1837     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
1838     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
1839     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
1840     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
1841     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
1842
1843     -- permission.*
1844     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
1845     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
1846     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
1847     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
1848
1849
1850     -- container.*
1851         
1852         -- For each *_bucket table: transfer every bucket belonging to src_usr
1853         -- into the custody of dest_usr.
1854         --
1855         -- In order to avoid colliding with an existing bucket owned by
1856         -- the destination user, append the source user's id (in parenthesese)
1857         -- to the name.  If you still get a collision, add successive
1858         -- spaces to the name and keep trying until you succeed.
1859         --
1860         FOR bucket_row in
1861                 SELECT id, name
1862                 FROM   container.biblio_record_entry_bucket
1863                 WHERE  owner = src_usr
1864         LOOP
1865                 suffix := ' (' || src_usr || ')';
1866                 LOOP
1867                         BEGIN
1868                                 UPDATE  container.biblio_record_entry_bucket
1869                                 SET     owner = dest_usr, name = name || suffix
1870                                 WHERE   id = bucket_row.id;
1871                         EXCEPTION WHEN unique_violation THEN
1872                                 suffix := suffix || ' ';
1873                                 CONTINUE;
1874                         END;
1875                         EXIT;
1876                 END LOOP;
1877         END LOOP;
1878
1879         FOR bucket_row in
1880                 SELECT id, name
1881                 FROM   container.call_number_bucket
1882                 WHERE  owner = src_usr
1883         LOOP
1884                 suffix := ' (' || src_usr || ')';
1885                 LOOP
1886                         BEGIN
1887                                 UPDATE  container.call_number_bucket
1888                                 SET     owner = dest_usr, name = name || suffix
1889                                 WHERE   id = bucket_row.id;
1890                         EXCEPTION WHEN unique_violation THEN
1891                                 suffix := suffix || ' ';
1892                                 CONTINUE;
1893                         END;
1894                         EXIT;
1895                 END LOOP;
1896         END LOOP;
1897
1898         FOR bucket_row in
1899                 SELECT id, name
1900                 FROM   container.copy_bucket
1901                 WHERE  owner = src_usr
1902         LOOP
1903                 suffix := ' (' || src_usr || ')';
1904                 LOOP
1905                         BEGIN
1906                                 UPDATE  container.copy_bucket
1907                                 SET     owner = dest_usr, name = name || suffix
1908                                 WHERE   id = bucket_row.id;
1909                         EXCEPTION WHEN unique_violation THEN
1910                                 suffix := suffix || ' ';
1911                                 CONTINUE;
1912                         END;
1913                         EXIT;
1914                 END LOOP;
1915         END LOOP;
1916
1917         FOR bucket_row in
1918                 SELECT id, name
1919                 FROM   container.user_bucket
1920                 WHERE  owner = src_usr
1921         LOOP
1922                 suffix := ' (' || src_usr || ')';
1923                 LOOP
1924                         BEGIN
1925                                 UPDATE  container.user_bucket
1926                                 SET     owner = dest_usr, name = name || suffix
1927                                 WHERE   id = bucket_row.id;
1928                         EXCEPTION WHEN unique_violation THEN
1929                                 suffix := suffix || ' ';
1930                                 CONTINUE;
1931                         END;
1932                         EXIT;
1933                 END LOOP;
1934         END LOOP;
1935
1936         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
1937
1938     -- vandelay.*
1939         -- transfer queues the same way we transfer buckets (see above)
1940         FOR queue_row in
1941                 SELECT id, name
1942                 FROM   vandelay.queue
1943                 WHERE  owner = src_usr
1944         LOOP
1945                 suffix := ' (' || src_usr || ')';
1946                 LOOP
1947                         BEGIN
1948                                 UPDATE  vandelay.queue
1949                                 SET     owner = dest_usr, name = name || suffix
1950                                 WHERE   id = queue_row.id;
1951                         EXCEPTION WHEN unique_violation THEN
1952                                 suffix := suffix || ' ';
1953                                 CONTINUE;
1954                         END;
1955                         EXIT;
1956                 END LOOP;
1957         END LOOP;
1958
1959     -- money.*
1960     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
1961     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
1962     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
1963     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
1964     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
1965
1966     -- action.*
1967     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
1968     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
1969     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
1970     UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
1971
1972     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
1973     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
1974     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
1975     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
1976
1977     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
1978     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
1979     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
1980     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
1981     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
1982
1983     -- acq.*
1984     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
1985         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
1986
1987         -- transfer picklists the same way we transfer buckets (see above)
1988         FOR picklist_row in
1989                 SELECT id, name
1990                 FROM   acq.picklist
1991                 WHERE  owner = src_usr
1992         LOOP
1993                 suffix := ' (' || src_usr || ')';
1994                 LOOP
1995                         BEGIN
1996                                 UPDATE  acq.picklist
1997                                 SET     owner = dest_usr, name = name || suffix
1998                                 WHERE   id = picklist_row.id;
1999                         EXCEPTION WHEN unique_violation THEN
2000                                 suffix := suffix || ' ';
2001                                 CONTINUE;
2002                         END;
2003                         EXIT;
2004                 END LOOP;
2005         END LOOP;
2006
2007     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
2008     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
2009     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
2010     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
2011     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
2012     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
2013     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
2014     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
2015
2016     -- asset.*
2017     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
2018     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
2019     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
2020     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
2021     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
2022     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
2023
2024     -- serial.*
2025     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
2026     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
2027
2028     -- reporter.*
2029     -- It's not uncommon to define the reporter schema in a replica 
2030     -- DB only, so don't assume these tables exist in the write DB.
2031     BEGIN
2032         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
2033     EXCEPTION WHEN undefined_table THEN
2034         -- do nothing
2035     END;
2036     BEGIN
2037         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
2038     EXCEPTION WHEN undefined_table THEN
2039         -- do nothing
2040     END;
2041     BEGIN
2042         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
2043     EXCEPTION WHEN undefined_table THEN
2044         -- do nothing
2045     END;
2046     BEGIN
2047                 -- transfer folders the same way we transfer buckets (see above)
2048                 FOR folder_row in
2049                         SELECT id, name
2050                         FROM   reporter.template_folder
2051                         WHERE  owner = src_usr
2052                 LOOP
2053                         suffix := ' (' || src_usr || ')';
2054                         LOOP
2055                                 BEGIN
2056                                         UPDATE  reporter.template_folder
2057                                         SET     owner = dest_usr, name = name || suffix
2058                                         WHERE   id = folder_row.id;
2059                                 EXCEPTION WHEN unique_violation THEN
2060                                         suffix := suffix || ' ';
2061                                         CONTINUE;
2062                                 END;
2063                                 EXIT;
2064                         END LOOP;
2065                 END LOOP;
2066     EXCEPTION WHEN undefined_table THEN
2067         -- do nothing
2068     END;
2069     BEGIN
2070                 -- transfer folders the same way we transfer buckets (see above)
2071                 FOR folder_row in
2072                         SELECT id, name
2073                         FROM   reporter.report_folder
2074                         WHERE  owner = src_usr
2075                 LOOP
2076                         suffix := ' (' || src_usr || ')';
2077                         LOOP
2078                                 BEGIN
2079                                         UPDATE  reporter.report_folder
2080                                         SET     owner = dest_usr, name = name || suffix
2081                                         WHERE   id = folder_row.id;
2082                                 EXCEPTION WHEN unique_violation THEN
2083                                         suffix := suffix || ' ';
2084                                         CONTINUE;
2085                                 END;
2086                                 EXIT;
2087                         END LOOP;
2088                 END LOOP;
2089     EXCEPTION WHEN undefined_table THEN
2090         -- do nothing
2091     END;
2092     BEGIN
2093                 -- transfer folders the same way we transfer buckets (see above)
2094                 FOR folder_row in
2095                         SELECT id, name
2096                         FROM   reporter.output_folder
2097                         WHERE  owner = src_usr
2098                 LOOP
2099                         suffix := ' (' || src_usr || ')';
2100                         LOOP
2101                                 BEGIN
2102                                         UPDATE  reporter.output_folder
2103                                         SET     owner = dest_usr, name = name || suffix
2104                                         WHERE   id = folder_row.id;
2105                                 EXCEPTION WHEN unique_violation THEN
2106                                         suffix := suffix || ' ';
2107                                         CONTINUE;
2108                                 END;
2109                                 EXIT;
2110                         END LOOP;
2111                 END LOOP;
2112     EXCEPTION WHEN undefined_table THEN
2113         -- do nothing
2114     END;
2115
2116     -- propagate preferred name values from the source user to the
2117     -- destination user, but only when values are not being replaced.
2118     WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
2119     UPDATE actor.usr SET 
2120         pref_prefix = 
2121             COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
2122         pref_first_given_name = 
2123             COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
2124         pref_second_given_name = 
2125             COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
2126         pref_family_name = 
2127             COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
2128         pref_suffix = 
2129             COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
2130     WHERE id = dest_usr;
2131
2132     -- Copy and deduplicate name keywords
2133     -- String -> array -> rows -> DISTINCT -> array -> string
2134     WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
2135          dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
2136     UPDATE actor.usr SET name_keywords = (
2137         WITH keywords AS (
2138             SELECT DISTINCT UNNEST(
2139                 REGEXP_SPLIT_TO_ARRAY(
2140                     COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
2141                     COALESCE((SELECT name_keywords FROM dusr), ''),  E'\\s+'
2142                 )
2143             ) AS parts
2144         ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
2145     ) WHERE id = dest_usr;
2146
2147     -- Finally, delete the source user
2148     DELETE FROM actor.usr WHERE id = src_usr;
2149
2150 END;
2151 $$ LANGUAGE plpgsql;
2152
2153
2154
2155
2156 SELECT evergreen.upgrade_deps_block_check('1123', :eg_version);
2157
2158     ALTER TABLE config.rule_circ_duration
2159     ADD column max_auto_renewals INTEGER;
2160
2161     ALTER TABLE action.circulation
2162     ADD column auto_renewal BOOLEAN;
2163
2164     ALTER TABLE action.circulation
2165     ADD column auto_renewal_remaining INTEGER;
2166
2167     ALTER TABLE action.aged_circulation
2168     ADD column auto_renewal BOOLEAN;
2169
2170     ALTER TABLE action.aged_circulation
2171     ADD column auto_renewal_remaining INTEGER;
2172
2173     INSERT INTO action_trigger.validator values('CircIsAutoRenewable', 'Checks whether the circulation is able to be autorenewed.');
2174     INSERT INTO action_trigger.reactor values('Circ::AutoRenew', 'Auto-Renews a circulation.');
2175     INSERT INTO action_trigger.hook(key, core_type, description) values('autorenewal', 'circ', 'Item was auto-renewed to patron.');
2176
2177     -- AutoRenewer A/T Def: 
2178     INSERT INTO action_trigger.event_definition(active, owner, name, hook, validator, reactor, delay, max_delay, delay_field, group_field)
2179         values (false, 1, 'Autorenew', 'checkout.due', 'CircIsOpen', 'Circ::AutoRenew', '-23 hours'::interval,'-1 minute'::interval, 'due_date', 'usr');
2180
2181     -- AutoRenewal outcome Email notifier A/T Def:
2182     INSERT INTO action_trigger.event_definition(active, owner, name, hook, validator, reactor, group_field, template)
2183         values (false, 1, 'AutorenewNotify', 'autorenewal', 'NOOP_True', 'SendEmail', 'usr', 
2184 $$
2185 [%- USE date -%]
2186 [%- user = target.0.usr -%]
2187 To: [%- params.recipient_email || user.email %]
2188 From: [%- params.sender_email || default_sender %]
2189 Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %]
2190 Subject: Items Out Auto-Renewal Notification 
2191 Auto-Submitted: auto-generated
2192
2193 Dear [% user.family_name %], [% user.first_given_name %]
2194 An automatic renewal attempt was made for the following items:
2195
2196 [% FOR circ IN target %]
2197     [%- SET idx = loop.count - 1; SET udata =  user_data.$idx -%]
2198     [%- SET cid = circ.target_copy || udata.copy -%]
2199     [%- SET copy_details = helpers.get_copy_bib_basics(cid) -%]
2200     Item# [% loop.count %]
2201     Title: [% copy_details.title %]
2202     Author: [% copy_details.author %]
2203     [%- IF udata.is_renewed %]
2204     Status: Loan Renewed
2205     New Due Date: [% date.format(helpers.format_date(udata.new_due_date), '%Y-%m-%d') %]
2206     [%- ELSE %]
2207     Status: Not Renewed
2208     Reason: [% udata.reason %]
2209     Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2210     [% END %]
2211 [% END %]
2212 $$
2213     );
2214
2215     INSERT INTO action_trigger.environment (event_def, path ) VALUES
2216     ( currval('action_trigger.event_definition_id_seq'), 'usr' ),
2217     ( currval('action_trigger.event_definition_id_seq'), 'circ_lib' );
2218
2219
2220 DROP VIEW action.all_circulation;
2221 CREATE OR REPLACE VIEW action.all_circulation AS
2222     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
2223         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
2224         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
2225         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
2226         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
2227         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ,
2228         auto_renewal, auto_renewal_remaining, NULL AS usr
2229       FROM  action.aged_circulation
2230             UNION ALL
2231     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,
2232         cp.call_number AS copy_call_number, circ.copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib,
2233         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
2234         circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
2235         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
2236         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
2237         circ.parent_circ, circ.auto_renewal, circ.auto_renewal_remaining, circ.usr
2238       FROM  action.circulation circ
2239         JOIN asset.copy cp ON (circ.target_copy = cp.id)
2240         JOIN asset.call_number cn ON (cp.call_number = cn.id)
2241         JOIN actor.usr p ON (circ.usr = p.id)
2242         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
2243         LEFT JOIN actor.usr_address b ON (p.billing_address = b.id);
2244
2245
2246 DROP FUNCTION action.summarize_all_circ_chain (INTEGER);
2247 DROP FUNCTION action.all_circ_chain (INTEGER);
2248
2249 -- rebuild slim circ view
2250 DROP VIEW action.all_circulation_slim;
2251 CREATE OR REPLACE VIEW action.all_circulation_slim AS
2252     SELECT
2253         id,
2254         usr,
2255         xact_start,
2256         xact_finish,
2257         unrecovered,
2258         target_copy,
2259         circ_lib,
2260         circ_staff,
2261         checkin_staff,
2262         checkin_lib,
2263         renewal_remaining,
2264         grace_period,
2265         due_date,
2266         stop_fines_time,
2267         checkin_time,
2268         create_time,
2269         duration,
2270         fine_interval,
2271         recurring_fine,
2272         max_fine,
2273         phone_renewal,
2274         desk_renewal,
2275         opac_renewal,
2276         duration_rule,
2277         recurring_fine_rule,
2278         max_fine_rule,
2279         stop_fines,
2280         workstation,
2281         checkin_workstation,
2282         copy_location,
2283         checkin_scan_time,
2284         auto_renewal,
2285         auto_renewal_remaining,
2286         parent_circ
2287     FROM action.circulation
2288 UNION ALL
2289     SELECT
2290         id,
2291         NULL AS usr,
2292         xact_start,
2293         xact_finish,
2294         unrecovered,
2295         target_copy,
2296         circ_lib,
2297         circ_staff,
2298         checkin_staff,
2299         checkin_lib,
2300         renewal_remaining,
2301         grace_period,
2302         due_date,
2303         stop_fines_time,
2304         checkin_time,
2305         create_time,
2306         duration,
2307         fine_interval,
2308         recurring_fine,
2309         max_fine,
2310         phone_renewal,
2311         desk_renewal,
2312         opac_renewal,
2313         duration_rule,
2314         recurring_fine_rule,
2315         max_fine_rule,
2316         stop_fines,
2317         workstation,
2318         checkin_workstation,
2319         copy_location,
2320         checkin_scan_time,
2321         auto_renewal,
2322         auto_renewal_remaining,
2323         parent_circ
2324     FROM action.aged_circulation
2325 ;
2326
2327 CREATE OR REPLACE FUNCTION action.all_circ_chain (ctx_circ_id INTEGER) 
2328     RETURNS SETOF action.all_circulation_slim AS $$
2329 DECLARE
2330     tmp_circ action.all_circulation_slim%ROWTYPE;
2331     circ_0 action.all_circulation_slim%ROWTYPE;
2332 BEGIN
2333
2334     SELECT INTO tmp_circ * FROM action.all_circulation_slim WHERE id = ctx_circ_id;
2335
2336     IF tmp_circ IS NULL THEN
2337         RETURN NEXT tmp_circ;
2338     END IF;
2339     circ_0 := tmp_circ;
2340
2341     -- find the front of the chain
2342     WHILE TRUE LOOP
2343         SELECT INTO tmp_circ * FROM action.all_circulation_slim 
2344             WHERE id = tmp_circ.parent_circ;
2345         IF tmp_circ IS NULL THEN
2346             EXIT;
2347         END IF;
2348         circ_0 := tmp_circ;
2349     END LOOP;
2350
2351     -- now send the circs to the caller, oldest to newest
2352     tmp_circ := circ_0;
2353     WHILE TRUE LOOP
2354         IF tmp_circ IS NULL THEN
2355             EXIT;
2356         END IF;
2357         RETURN NEXT tmp_circ;
2358         SELECT INTO tmp_circ * FROM action.all_circulation_slim 
2359             WHERE parent_circ = tmp_circ.id;
2360     END LOOP;
2361
2362 END;
2363 $$ LANGUAGE 'plpgsql';
2364
2365 -- same as action.summarize_circ_chain, but returns data collected
2366 -- from action.all_circulation, which may include aged circulations.
2367 CREATE OR REPLACE FUNCTION action.summarize_all_circ_chain 
2368     (ctx_circ_id INTEGER) RETURNS action.circ_chain_summary AS $$
2369
2370 DECLARE
2371
2372     -- first circ in the chain
2373     circ_0 action.all_circulation_slim%ROWTYPE;
2374
2375     -- last circ in the chain
2376     circ_n action.all_circulation_slim%ROWTYPE;
2377
2378     -- circ chain under construction
2379     chain action.circ_chain_summary;
2380     tmp_circ action.all_circulation_slim%ROWTYPE;
2381
2382 BEGIN
2383     
2384     chain.num_circs := 0;
2385     FOR tmp_circ IN SELECT * FROM action.all_circ_chain(ctx_circ_id) LOOP
2386
2387         IF chain.num_circs = 0 THEN
2388             circ_0 := tmp_circ;
2389         END IF;
2390
2391         chain.num_circs := chain.num_circs + 1;
2392         circ_n := tmp_circ;
2393     END LOOP;
2394
2395     chain.start_time := circ_0.xact_start;
2396     chain.last_stop_fines := circ_n.stop_fines;
2397     chain.last_stop_fines_time := circ_n.stop_fines_time;
2398     chain.last_checkin_time := circ_n.checkin_time;
2399     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
2400     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
2401     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
2402
2403     IF chain.num_circs > 1 THEN
2404         chain.last_renewal_time := circ_n.xact_start;
2405         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
2406     END IF;
2407
2408     RETURN chain;
2409
2410 END;
2411 $$ LANGUAGE 'plpgsql';
2412
2413
2414
2415 SELECT evergreen.upgrade_deps_block_check('1124', :eg_version);
2416
2417 INSERT into config.workstation_setting_type (name, grp, datatype, label)
2418 VALUES (
2419     'eg.grid.circ.wide_holds.shelf', 'gui', 'object',
2420     oils_i18n_gettext (
2421         'eg.grid.circ.wide_holds.shelf',
2422         'Grid Config: circ.wide_holds.shelf',
2423         'cwst', 'label'
2424     )
2425 ), (
2426     'eg.grid.cat.catalog.wide_holds', 'gui', 'object',
2427     oils_i18n_gettext(
2428         'eg.grid.cat.catalog.wide_holds',
2429         'Grid Config: cat.catalog.wide_holds',
2430         'cwst', 'label'
2431     )
2432 );
2433
2434 DELETE from config.workstation_setting_type
2435 WHERE name = 'eg.grid.cat.catalog.holds' OR name = 'eg.grid.circ.holds.shelf';
2436
2437
2438 SELECT evergreen.upgrade_deps_block_check('1125', :eg_version);
2439
2440 CREATE TABLE asset.latest_inventory (
2441     id                          SERIAL                      PRIMARY KEY,
2442     inventory_workstation       INTEGER                     REFERENCES actor.workstation (id) DEFERRABLE INITIALLY DEFERRED,
2443     inventory_date              TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
2444     copy                        BIGINT                      NOT NULL
2445 );
2446 CREATE INDEX latest_inventory_copy_idx ON asset.latest_inventory (copy);
2447
2448 CREATE OR REPLACE FUNCTION evergreen.asset_latest_inventory_copy_inh_fkey() RETURNS TRIGGER AS $f$
2449 BEGIN
2450         PERFORM 1 FROM asset.copy WHERE id = NEW.copy;
2451         IF NOT FOUND THEN
2452                 RAISE foreign_key_violation USING MESSAGE = FORMAT(
2453                         $$Referenced asset.copy id not found, copy:%s$$, NEW.copy
2454                 );
2455         END IF;
2456         RETURN NEW;
2457 END;
2458 $f$ LANGUAGE PLPGSQL VOLATILE COST 50;
2459
2460 CREATE CONSTRAINT TRIGGER inherit_asset_latest_inventory_copy_fkey
2461         AFTER UPDATE OR INSERT ON asset.latest_inventory
2462         DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_latest_inventory_copy_inh_fkey();
2463
2464 INSERT into config.workstation_setting_type (name, grp, datatype, label)
2465 VALUES (
2466     'eg.circ.checkin.do_inventory_update', 'circ', 'bool',
2467     oils_i18n_gettext (
2468              'eg.circ.checkin.do_inventory_update',
2469              'Checkin: Update Inventory',
2470              'cwst', 'label'
2471     )
2472 );
2473
2474
2475 SELECT evergreen.upgrade_deps_block_check('1126', :eg_version);
2476
2477 CREATE TABLE vandelay.session_tracker (
2478     id          BIGSERIAL PRIMARY KEY,
2479
2480     -- string of characters (e.g. md5) used for linking trackers
2481     -- of different actions into a series.  There can be multiple
2482     -- session_keys of each action type, creating the opportunity
2483     -- to link multiple action trackers into a single session.
2484     session_key TEXT NOT NULL,
2485
2486     -- optional user-supplied name
2487     name        TEXT NOT NULL, 
2488
2489     usr         INTEGER NOT NULL REFERENCES actor.usr(id)
2490                 DEFERRABLE INITIALLY DEFERRED,
2491
2492     -- org unit can be derived from WS
2493     workstation INTEGER NOT NULL REFERENCES actor.workstation(id)
2494                 ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2495
2496     -- bib/auth
2497     record_type vandelay.bib_queue_queue_type NOT NULL DEFAULT 'bib',
2498
2499     -- Queue defines the source of the data, it does not necessarily
2500     -- mean that an action is being performed against an entire queue.
2501     -- E.g. some imports are misc. lists of record IDs, but they always 
2502     -- come from one queue.
2503     -- No foreign key -- could be auth or bib queue.
2504     queue       BIGINT NOT NULL,
2505
2506     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
2507     update_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
2508
2509     state       TEXT NOT NULL DEFAULT 'active',
2510
2511     action_type TEXT NOT NULL DEFAULT 'enqueue', -- import
2512
2513     -- total number of tasks to perform / loosely defined
2514     -- could be # of recs to import or # of recs + # of copies 
2515     -- depending on the import context
2516     total_actions INTEGER NOT NULL DEFAULT 0,
2517
2518     -- total number of tasked performed so far
2519     actions_performed INTEGER NOT NULL DEFAULT 0,
2520
2521     CONSTRAINT vand_tracker_valid_state 
2522         CHECK (state IN ('active','error','complete')),
2523
2524     CONSTRAINT vand_tracker_valid_action_type
2525         CHECK (action_type IN ('upload', 'enqueue', 'import'))
2526 );
2527
2528
2529 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 $$
2530 DECLARE
2531         suffix TEXT;
2532         bucket_row RECORD;
2533         picklist_row RECORD;
2534         queue_row RECORD;
2535         folder_row RECORD;
2536 BEGIN
2537
2538     -- do some initial cleanup 
2539     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
2540     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
2541     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
2542
2543     -- actor.*
2544     IF del_cards THEN
2545         DELETE FROM actor.card where usr = src_usr;
2546     ELSE
2547         IF deactivate_cards THEN
2548             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
2549         END IF;
2550         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
2551     END IF;
2552
2553
2554     IF del_addrs THEN
2555         DELETE FROM actor.usr_address WHERE usr = src_usr;
2556     ELSE
2557         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
2558     END IF;
2559
2560     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
2561     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
2562     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
2563     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
2564     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
2565
2566     -- permission.*
2567     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
2568     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
2569     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
2570     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
2571
2572
2573     -- container.*
2574         
2575         -- For each *_bucket table: transfer every bucket belonging to src_usr
2576         -- into the custody of dest_usr.
2577         --
2578         -- In order to avoid colliding with an existing bucket owned by
2579         -- the destination user, append the source user's id (in parenthesese)
2580         -- to the name.  If you still get a collision, add successive
2581         -- spaces to the name and keep trying until you succeed.
2582         --
2583         FOR bucket_row in
2584                 SELECT id, name
2585                 FROM   container.biblio_record_entry_bucket
2586                 WHERE  owner = src_usr
2587         LOOP
2588                 suffix := ' (' || src_usr || ')';
2589                 LOOP
2590                         BEGIN
2591                                 UPDATE  container.biblio_record_entry_bucket
2592                                 SET     owner = dest_usr, name = name || suffix
2593                                 WHERE   id = bucket_row.id;
2594                         EXCEPTION WHEN unique_violation THEN
2595                                 suffix := suffix || ' ';
2596                                 CONTINUE;
2597                         END;
2598                         EXIT;
2599                 END LOOP;
2600         END LOOP;
2601
2602         FOR bucket_row in
2603                 SELECT id, name
2604                 FROM   container.call_number_bucket
2605                 WHERE  owner = src_usr
2606         LOOP
2607                 suffix := ' (' || src_usr || ')';
2608                 LOOP
2609                         BEGIN
2610                                 UPDATE  container.call_number_bucket
2611                                 SET     owner = dest_usr, name = name || suffix
2612                                 WHERE   id = bucket_row.id;
2613                         EXCEPTION WHEN unique_violation THEN
2614                                 suffix := suffix || ' ';
2615                                 CONTINUE;
2616                         END;
2617                         EXIT;
2618                 END LOOP;
2619         END LOOP;
2620
2621         FOR bucket_row in
2622                 SELECT id, name
2623                 FROM   container.copy_bucket
2624                 WHERE  owner = src_usr
2625         LOOP
2626                 suffix := ' (' || src_usr || ')';
2627                 LOOP
2628                         BEGIN
2629                                 UPDATE  container.copy_bucket
2630                                 SET     owner = dest_usr, name = name || suffix
2631                                 WHERE   id = bucket_row.id;
2632                         EXCEPTION WHEN unique_violation THEN
2633                                 suffix := suffix || ' ';
2634                                 CONTINUE;
2635                         END;
2636                         EXIT;
2637                 END LOOP;
2638         END LOOP;
2639
2640         FOR bucket_row in
2641                 SELECT id, name
2642                 FROM   container.user_bucket
2643                 WHERE  owner = src_usr
2644         LOOP
2645                 suffix := ' (' || src_usr || ')';
2646                 LOOP
2647                         BEGIN
2648                                 UPDATE  container.user_bucket
2649                                 SET     owner = dest_usr, name = name || suffix
2650                                 WHERE   id = bucket_row.id;
2651                         EXCEPTION WHEN unique_violation THEN
2652                                 suffix := suffix || ' ';
2653                                 CONTINUE;
2654                         END;
2655                         EXIT;
2656                 END LOOP;
2657         END LOOP;
2658
2659         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
2660
2661     -- vandelay.*
2662         -- transfer queues the same way we transfer buckets (see above)
2663         FOR queue_row in
2664                 SELECT id, name
2665                 FROM   vandelay.queue
2666                 WHERE  owner = src_usr
2667         LOOP
2668                 suffix := ' (' || src_usr || ')';
2669                 LOOP
2670                         BEGIN
2671                                 UPDATE  vandelay.queue
2672                                 SET     owner = dest_usr, name = name || suffix
2673                                 WHERE   id = queue_row.id;
2674                         EXCEPTION WHEN unique_violation THEN
2675                                 suffix := suffix || ' ';
2676                                 CONTINUE;
2677                         END;
2678                         EXIT;
2679                 END LOOP;
2680         END LOOP;
2681
2682     UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
2683
2684     -- money.*
2685     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
2686     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
2687     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
2688     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
2689     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
2690
2691     -- action.*
2692     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
2693     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
2694     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
2695     UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
2696
2697     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
2698     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
2699     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
2700     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
2701
2702     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
2703     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
2704     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
2705     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
2706     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
2707
2708     -- acq.*
2709     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
2710         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
2711
2712         -- transfer picklists the same way we transfer buckets (see above)
2713         FOR picklist_row in
2714                 SELECT id, name
2715                 FROM   acq.picklist
2716                 WHERE  owner = src_usr
2717         LOOP
2718                 suffix := ' (' || src_usr || ')';
2719                 LOOP
2720                         BEGIN
2721                                 UPDATE  acq.picklist
2722                                 SET     owner = dest_usr, name = name || suffix
2723                                 WHERE   id = picklist_row.id;
2724                         EXCEPTION WHEN unique_violation THEN
2725                                 suffix := suffix || ' ';
2726                                 CONTINUE;
2727                         END;
2728                         EXIT;
2729                 END LOOP;
2730         END LOOP;
2731
2732     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
2733     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
2734     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
2735     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
2736     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
2737     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
2738     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
2739     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
2740
2741     -- asset.*
2742     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
2743     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
2744     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
2745     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
2746     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
2747     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
2748
2749     -- serial.*
2750     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
2751     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
2752
2753     -- reporter.*
2754     -- It's not uncommon to define the reporter schema in a replica 
2755     -- DB only, so don't assume these tables exist in the write DB.
2756     BEGIN
2757         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
2758     EXCEPTION WHEN undefined_table THEN
2759         -- do nothing
2760     END;
2761     BEGIN
2762         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
2763     EXCEPTION WHEN undefined_table THEN
2764         -- do nothing
2765     END;
2766     BEGIN
2767         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
2768     EXCEPTION WHEN undefined_table THEN
2769         -- do nothing
2770     END;
2771     BEGIN
2772                 -- transfer folders the same way we transfer buckets (see above)
2773                 FOR folder_row in
2774                         SELECT id, name
2775                         FROM   reporter.template_folder
2776                         WHERE  owner = src_usr
2777                 LOOP
2778                         suffix := ' (' || src_usr || ')';
2779                         LOOP
2780                                 BEGIN
2781                                         UPDATE  reporter.template_folder
2782                                         SET     owner = dest_usr, name = name || suffix
2783                                         WHERE   id = folder_row.id;
2784                                 EXCEPTION WHEN unique_violation THEN
2785                                         suffix := suffix || ' ';
2786                                         CONTINUE;
2787                                 END;
2788                                 EXIT;
2789                         END LOOP;
2790                 END LOOP;
2791     EXCEPTION WHEN undefined_table THEN
2792         -- do nothing
2793     END;
2794     BEGIN
2795                 -- transfer folders the same way we transfer buckets (see above)
2796                 FOR folder_row in
2797                         SELECT id, name
2798                         FROM   reporter.report_folder
2799                         WHERE  owner = src_usr
2800                 LOOP
2801                         suffix := ' (' || src_usr || ')';
2802                         LOOP
2803                                 BEGIN
2804                                         UPDATE  reporter.report_folder
2805                                         SET     owner = dest_usr, name = name || suffix
2806                                         WHERE   id = folder_row.id;
2807                                 EXCEPTION WHEN unique_violation THEN
2808                                         suffix := suffix || ' ';
2809                                         CONTINUE;
2810                                 END;
2811                                 EXIT;
2812                         END LOOP;
2813                 END LOOP;
2814     EXCEPTION WHEN undefined_table THEN
2815         -- do nothing
2816     END;
2817     BEGIN
2818                 -- transfer folders the same way we transfer buckets (see above)
2819                 FOR folder_row in
2820                         SELECT id, name
2821                         FROM   reporter.output_folder
2822                         WHERE  owner = src_usr
2823                 LOOP
2824                         suffix := ' (' || src_usr || ')';
2825                         LOOP
2826                                 BEGIN
2827                                         UPDATE  reporter.output_folder
2828                                         SET     owner = dest_usr, name = name || suffix
2829                                         WHERE   id = folder_row.id;
2830                                 EXCEPTION WHEN unique_violation THEN
2831                                         suffix := suffix || ' ';
2832                                         CONTINUE;
2833                                 END;
2834                                 EXIT;
2835                         END LOOP;
2836                 END LOOP;
2837     EXCEPTION WHEN undefined_table THEN
2838         -- do nothing
2839     END;
2840
2841     -- propagate preferred name values from the source user to the
2842     -- destination user, but only when values are not being replaced.
2843     WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
2844     UPDATE actor.usr SET 
2845         pref_prefix = 
2846             COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
2847         pref_first_given_name = 
2848             COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
2849         pref_second_given_name = 
2850             COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
2851         pref_family_name = 
2852             COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
2853         pref_suffix = 
2854             COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
2855     WHERE id = dest_usr;
2856
2857     -- Copy and deduplicate name keywords
2858     -- String -> array -> rows -> DISTINCT -> array -> string
2859     WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
2860          dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
2861     UPDATE actor.usr SET name_keywords = (
2862         WITH keywords AS (
2863             SELECT DISTINCT UNNEST(
2864                 REGEXP_SPLIT_TO_ARRAY(
2865                     COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
2866                     COALESCE((SELECT name_keywords FROM dusr), ''),  E'\\s+'
2867                 )
2868             ) AS parts
2869         ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
2870     ) WHERE id = dest_usr;
2871
2872     -- Finally, delete the source user
2873     DELETE FROM actor.usr WHERE id = src_usr;
2874
2875 END;
2876 $$ LANGUAGE plpgsql;
2877
2878
2879 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
2880         src_usr  IN INTEGER,
2881         specified_dest_usr IN INTEGER
2882 ) RETURNS VOID AS $$
2883 DECLARE
2884         suffix TEXT;
2885         renamable_row RECORD;
2886         dest_usr INTEGER;
2887 BEGIN
2888
2889         IF specified_dest_usr IS NULL THEN
2890                 dest_usr := 1; -- Admin user on stock installs
2891         ELSE
2892                 dest_usr := specified_dest_usr;
2893         END IF;
2894
2895         -- acq.*
2896         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
2897         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
2898         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
2899         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
2900         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
2901         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
2902         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
2903
2904         -- Update with a rename to avoid collisions
2905         FOR renamable_row in
2906                 SELECT id, name
2907                 FROM   acq.picklist
2908                 WHERE  owner = src_usr
2909         LOOP
2910                 suffix := ' (' || src_usr || ')';
2911                 LOOP
2912                         BEGIN
2913                                 UPDATE  acq.picklist
2914                                 SET     owner = dest_usr, name = name || suffix
2915                                 WHERE   id = renamable_row.id;
2916                         EXCEPTION WHEN unique_violation THEN
2917                                 suffix := suffix || ' ';
2918                                 CONTINUE;
2919                         END;
2920                         EXIT;
2921                 END LOOP;
2922         END LOOP;
2923
2924         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
2925         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
2926         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
2927         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
2928         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
2929         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
2930         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
2931         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
2932
2933         -- action.*
2934         DELETE FROM action.circulation WHERE usr = src_usr;
2935         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
2936         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
2937         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
2938         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
2939         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
2940         DELETE FROM action.hold_request WHERE usr = src_usr;
2941         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
2942         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
2943         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
2944         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
2945         DELETE FROM action.survey_response WHERE usr = src_usr;
2946         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
2947         DELETE FROM action.usr_circ_history WHERE usr = src_usr;
2948
2949         -- actor.*
2950         DELETE FROM actor.card WHERE usr = src_usr;
2951         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
2952
2953         -- The following update is intended to avoid transient violations of a foreign
2954         -- key constraint, whereby actor.usr_address references itself.  It may not be
2955         -- necessary, but it does no harm.
2956         UPDATE actor.usr_address SET replaces = NULL
2957                 WHERE usr = src_usr AND replaces IS NOT NULL;
2958         DELETE FROM actor.usr_address WHERE usr = src_usr;
2959         DELETE FROM actor.usr_note WHERE usr = src_usr;
2960         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
2961         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
2962         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
2963         DELETE FROM actor.usr_setting WHERE usr = src_usr;
2964         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
2965         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
2966
2967         -- asset.*
2968         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
2969         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
2970         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
2971         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
2972         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
2973         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
2974
2975         -- auditor.*
2976         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
2977         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
2978         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
2979         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
2980         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
2981         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
2982         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
2983         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
2984
2985         -- biblio.*
2986         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
2987         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
2988         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
2989         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
2990
2991         -- container.*
2992         -- Update buckets with a rename to avoid collisions
2993         FOR renamable_row in
2994                 SELECT id, name
2995                 FROM   container.biblio_record_entry_bucket
2996                 WHERE  owner = src_usr
2997         LOOP
2998                 suffix := ' (' || src_usr || ')';
2999                 LOOP
3000                         BEGIN
3001                                 UPDATE  container.biblio_record_entry_bucket
3002                                 SET     owner = dest_usr, name = name || suffix
3003                                 WHERE   id = renamable_row.id;
3004                         EXCEPTION WHEN unique_violation THEN
3005                                 suffix := suffix || ' ';
3006                                 CONTINUE;
3007                         END;
3008                         EXIT;
3009                 END LOOP;
3010         END LOOP;
3011
3012         FOR renamable_row in
3013                 SELECT id, name
3014                 FROM   container.call_number_bucket
3015                 WHERE  owner = src_usr
3016         LOOP
3017                 suffix := ' (' || src_usr || ')';
3018                 LOOP
3019                         BEGIN
3020                                 UPDATE  container.call_number_bucket
3021                                 SET     owner = dest_usr, name = name || suffix
3022                                 WHERE   id = renamable_row.id;
3023                         EXCEPTION WHEN unique_violation THEN
3024                                 suffix := suffix || ' ';
3025                                 CONTINUE;
3026                         END;
3027                         EXIT;
3028                 END LOOP;
3029         END LOOP;
3030
3031         FOR renamable_row in
3032                 SELECT id, name
3033                 FROM   container.copy_bucket
3034                 WHERE  owner = src_usr
3035         LOOP
3036                 suffix := ' (' || src_usr || ')';
3037                 LOOP
3038                         BEGIN
3039                                 UPDATE  container.copy_bucket
3040                                 SET     owner = dest_usr, name = name || suffix
3041                                 WHERE   id = renamable_row.id;
3042                         EXCEPTION WHEN unique_violation THEN
3043                                 suffix := suffix || ' ';
3044                                 CONTINUE;
3045                         END;
3046                         EXIT;
3047                 END LOOP;
3048         END LOOP;
3049
3050         FOR renamable_row in
3051                 SELECT id, name
3052                 FROM   container.user_bucket
3053                 WHERE  owner = src_usr
3054         LOOP
3055                 suffix := ' (' || src_usr || ')';
3056                 LOOP
3057                         BEGIN
3058                                 UPDATE  container.user_bucket
3059                                 SET     owner = dest_usr, name = name || suffix
3060                                 WHERE   id = renamable_row.id;
3061                         EXCEPTION WHEN unique_violation THEN
3062                                 suffix := suffix || ' ';
3063                                 CONTINUE;
3064                         END;
3065                         EXIT;
3066                 END LOOP;
3067         END LOOP;
3068
3069         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
3070
3071         -- money.*
3072         DELETE FROM money.billable_xact WHERE usr = src_usr;
3073         DELETE FROM money.collections_tracker WHERE usr = src_usr;
3074         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
3075
3076         -- permission.*
3077         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
3078         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
3079         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
3080         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
3081
3082         -- reporter.*
3083         -- Update with a rename to avoid collisions
3084         BEGIN
3085                 FOR renamable_row in
3086                         SELECT id, name
3087                         FROM   reporter.output_folder
3088                         WHERE  owner = src_usr
3089                 LOOP
3090                         suffix := ' (' || src_usr || ')';
3091                         LOOP
3092                                 BEGIN
3093                                         UPDATE  reporter.output_folder
3094                                         SET     owner = dest_usr, name = name || suffix
3095                                         WHERE   id = renamable_row.id;
3096                                 EXCEPTION WHEN unique_violation THEN
3097                                         suffix := suffix || ' ';
3098                                         CONTINUE;
3099                                 END;
3100                                 EXIT;
3101                         END LOOP;
3102                 END LOOP;
3103         EXCEPTION WHEN undefined_table THEN
3104                 -- do nothing
3105         END;
3106
3107         BEGIN
3108                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
3109         EXCEPTION WHEN undefined_table THEN
3110                 -- do nothing
3111         END;
3112
3113         -- Update with a rename to avoid collisions
3114         BEGIN
3115                 FOR renamable_row in
3116                         SELECT id, name
3117                         FROM   reporter.report_folder
3118                         WHERE  owner = src_usr
3119                 LOOP
3120                         suffix := ' (' || src_usr || ')';
3121                         LOOP
3122                                 BEGIN
3123                                         UPDATE  reporter.report_folder
3124                                         SET     owner = dest_usr, name = name || suffix
3125                                         WHERE   id = renamable_row.id;
3126                                 EXCEPTION WHEN unique_violation THEN
3127                                         suffix := suffix || ' ';
3128                                         CONTINUE;
3129                                 END;
3130                                 EXIT;
3131                         END LOOP;
3132                 END LOOP;
3133         EXCEPTION WHEN undefined_table THEN
3134                 -- do nothing
3135         END;
3136
3137         BEGIN
3138                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
3139         EXCEPTION WHEN undefined_table THEN
3140                 -- do nothing
3141         END;
3142
3143         BEGIN
3144                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
3145         EXCEPTION WHEN undefined_table THEN
3146                 -- do nothing
3147         END;
3148
3149         -- Update with a rename to avoid collisions
3150         BEGIN
3151                 FOR renamable_row in
3152                         SELECT id, name
3153                         FROM   reporter.template_folder
3154                         WHERE  owner = src_usr
3155                 LOOP
3156                         suffix := ' (' || src_usr || ')';
3157                         LOOP
3158                                 BEGIN
3159                                         UPDATE  reporter.template_folder
3160                                         SET     owner = dest_usr, name = name || suffix
3161                                         WHERE   id = renamable_row.id;
3162                                 EXCEPTION WHEN unique_violation THEN
3163                                         suffix := suffix || ' ';
3164                                         CONTINUE;
3165                                 END;
3166                                 EXIT;
3167                         END LOOP;
3168                 END LOOP;
3169         EXCEPTION WHEN undefined_table THEN
3170         -- do nothing
3171         END;
3172
3173         -- vandelay.*
3174         -- Update with a rename to avoid collisions
3175         FOR renamable_row in
3176                 SELECT id, name
3177                 FROM   vandelay.queue
3178                 WHERE  owner = src_usr
3179         LOOP
3180                 suffix := ' (' || src_usr || ')';
3181                 LOOP
3182                         BEGIN
3183                                 UPDATE  vandelay.queue
3184                                 SET     owner = dest_usr, name = name || suffix
3185                                 WHERE   id = renamable_row.id;
3186                         EXCEPTION WHEN unique_violation THEN
3187                                 suffix := suffix || ' ';
3188                                 CONTINUE;
3189                         END;
3190                         EXIT;
3191                 END LOOP;
3192         END LOOP;
3193
3194     UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
3195
3196     -- NULL-ify addresses last so other cleanup (e.g. circ anonymization)
3197     -- can access the information before deletion.
3198         UPDATE actor.usr SET
3199                 active = FALSE,
3200                 card = NULL,
3201                 mailing_address = NULL,
3202                 billing_address = NULL
3203         WHERE id = src_usr;
3204
3205 END;
3206 $$ LANGUAGE plpgsql;
3207
3208
3209
3210 SELECT evergreen.upgrade_deps_block_check('1127', :eg_version);
3211
3212 ALTER TABLE acq.user_request ADD COLUMN cancel_time TIMESTAMPTZ;
3213 ALTER TABLE acq.user_request ADD COLUMN upc TEXT;
3214 ALTER TABLE action.hold_request ADD COLUMN acq_request INT REFERENCES acq.user_request (id);
3215
3216 UPDATE
3217     config.org_unit_setting_type
3218 SET
3219     label = oils_i18n_gettext(
3220         'circ.holds.canceled.display_age',
3221         'Canceled holds/requests display age',
3222         'coust', 'label'),
3223     description = oils_i18n_gettext(
3224         'circ.holds.canceled.display_age',
3225         'Show all canceled entries in patron holds and patron acquisition requests interfaces that were canceled within this amount of time',
3226         'coust', 'description')
3227 WHERE
3228     name = 'circ.holds.canceled.display_age'
3229 ;
3230
3231 UPDATE
3232     config.org_unit_setting_type
3233 SET
3234     label = oils_i18n_gettext(
3235         'circ.holds.canceled.display_count',
3236         'Canceled holds/requests display count',
3237         'coust', 'label'),
3238     description = oils_i18n_gettext(
3239         'circ.holds.canceled.display_count',
3240         'How many canceled entries to show in patron holds and patron acquisition requests interfaces',
3241         'coust', 'description')
3242 WHERE
3243     name = 'circ.holds.canceled.display_count'
3244 ;
3245
3246 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description)
3247     VALUES (
3248         1, 'f', 1015,
3249         oils_i18n_gettext(1015, 'Canceled: Fulfilled', 'acqcr', 'label'),
3250         oils_i18n_gettext(1015, 'This acquisition request has been fulfilled.', 'acqcr', 'description')
3251     )
3252 ;
3253
3254 UPDATE
3255     acq.user_request_type
3256 SET
3257     label = oils_i18n_gettext('2', 'Articles', 'aurt', 'label')
3258 WHERE
3259     id = 2
3260 ;
3261
3262 INSERT INTO acq.user_request_type (id,label)
3263     SELECT 6, oils_i18n_gettext('6', 'Other', 'aurt', 'label');
3264
3265 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, (SELECT MAX(id)+1 FROM acq.user_request_type));
3266
3267 INSERT INTO permission.perm_list ( id, code, description ) VALUES
3268  ( 610, 'CLEAR_PURCHASE_REQUEST', oils_i18n_gettext(610,
3269     'Clear Completed User Purchase Requests', 'ppl', 'description'))
3270 ;
3271
3272 CREATE TABLE acq.user_request_status_type (
3273      id  SERIAL  PRIMARY KEY
3274     ,label TEXT
3275 );
3276
3277 INSERT INTO acq.user_request_status_type (id,label) VALUES
3278      (0,oils_i18n_gettext(0,'Error','aurst','label'))
3279     ,(1,oils_i18n_gettext(1,'New','aurst','label'))
3280     ,(2,oils_i18n_gettext(2,'Pending','aurst','label'))
3281     ,(3,oils_i18n_gettext(3,'Ordered, Hold Not Placed','aurst','label'))
3282     ,(4,oils_i18n_gettext(4,'Ordered, Hold Placed','aurst','label'))
3283     ,(5,oils_i18n_gettext(5,'Received','aurst','label'))
3284     ,(6,oils_i18n_gettext(6,'Fulfilled','aurst','label'))
3285     ,(7,oils_i18n_gettext(7,'Canceled','aurst','label'))
3286 ;
3287
3288 SELECT SETVAL('acq.user_request_status_type_id_seq'::TEXT, 100);
3289
3290 -- not used
3291 DELETE FROM actor.org_unit_setting WHERE name = 'acq.holds.allow_holds_from_purchase_request';
3292 DELETE FROM config.org_unit_setting_type_log WHERE field_name = 'acq.holds.allow_holds_from_purchase_request';
3293 DELETE FROM config.org_unit_setting_type WHERE name = 'acq.holds.allow_holds_from_purchase_request';
3294
3295
3296 SELECT evergreen.upgrade_deps_block_check('1128', :eg_version);
3297
3298 DROP VIEW auditor.acq_invoice_lifecycle;
3299
3300 ALTER TABLE acq.invoice
3301     ADD COLUMN close_date TIMESTAMPTZ,
3302     ADD COLUMN closed_by  INTEGER 
3303         REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED;
3304
3305 -- duplicate steps for auditor table
3306 ALTER TABLE auditor.acq_invoice_history
3307     ADD COLUMN close_date TIMESTAMPTZ,
3308     ADD COLUMN closed_by  INTEGER;
3309
3310 UPDATE acq.invoice SET close_date = NOW() WHERE complete;
3311 UPDATE auditor.acq_invoice_history SET close_date = NOW() WHERE complete;
3312
3313 ALTER TABLE acq.invoice DROP COLUMN complete;
3314 ALTER TABLE auditor.acq_invoice_history DROP COLUMN complete;
3315
3316 -- this recreates auditor.acq_invoice_lifecycle;
3317 SELECT auditor.update_auditors();
3318
3319 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 $$
3320 DECLARE
3321         suffix TEXT;
3322         bucket_row RECORD;
3323         picklist_row RECORD;
3324         queue_row RECORD;
3325         folder_row RECORD;
3326 BEGIN
3327
3328     -- do some initial cleanup 
3329     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
3330     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
3331     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
3332
3333     -- actor.*
3334     IF del_cards THEN
3335         DELETE FROM actor.card where usr = src_usr;
3336     ELSE
3337         IF deactivate_cards THEN
3338             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
3339         END IF;
3340         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
3341     END IF;
3342
3343
3344     IF del_addrs THEN
3345         DELETE FROM actor.usr_address WHERE usr = src_usr;
3346     ELSE
3347         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
3348     END IF;
3349
3350     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
3351     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
3352     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
3353     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
3354     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
3355
3356     -- permission.*
3357     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
3358     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
3359     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
3360     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
3361
3362
3363     -- container.*
3364         
3365         -- For each *_bucket table: transfer every bucket belonging to src_usr
3366         -- into the custody of dest_usr.
3367         --
3368         -- In order to avoid colliding with an existing bucket owned by
3369         -- the destination user, append the source user's id (in parenthesese)
3370         -- to the name.  If you still get a collision, add successive
3371         -- spaces to the name and keep trying until you succeed.
3372         --
3373         FOR bucket_row in
3374                 SELECT id, name
3375                 FROM   container.biblio_record_entry_bucket
3376                 WHERE  owner = src_usr
3377         LOOP
3378                 suffix := ' (' || src_usr || ')';
3379                 LOOP
3380                         BEGIN
3381                                 UPDATE  container.biblio_record_entry_bucket
3382                                 SET     owner = dest_usr, name = name || suffix
3383                                 WHERE   id = bucket_row.id;
3384                         EXCEPTION WHEN unique_violation THEN
3385                                 suffix := suffix || ' ';
3386                                 CONTINUE;
3387                         END;
3388                         EXIT;
3389                 END LOOP;
3390         END LOOP;
3391
3392         FOR bucket_row in
3393                 SELECT id, name
3394                 FROM   container.call_number_bucket
3395                 WHERE  owner = src_usr
3396         LOOP
3397                 suffix := ' (' || src_usr || ')';
3398                 LOOP
3399                         BEGIN
3400                                 UPDATE  container.call_number_bucket
3401                                 SET     owner = dest_usr, name = name || suffix
3402                                 WHERE   id = bucket_row.id;
3403                         EXCEPTION WHEN unique_violation THEN
3404                                 suffix := suffix || ' ';
3405                                 CONTINUE;
3406                         END;
3407                         EXIT;
3408                 END LOOP;
3409         END LOOP;
3410
3411         FOR bucket_row in
3412                 SELECT id, name
3413                 FROM   container.copy_bucket
3414                 WHERE  owner = src_usr
3415         LOOP
3416                 suffix := ' (' || src_usr || ')';
3417                 LOOP
3418                         BEGIN
3419                                 UPDATE  container.copy_bucket
3420                                 SET     owner = dest_usr, name = name || suffix
3421                                 WHERE   id = bucket_row.id;
3422                         EXCEPTION WHEN unique_violation THEN
3423                                 suffix := suffix || ' ';
3424                                 CONTINUE;
3425                         END;
3426                         EXIT;
3427                 END LOOP;
3428         END LOOP;
3429
3430         FOR bucket_row in
3431                 SELECT id, name
3432                 FROM   container.user_bucket
3433                 WHERE  owner = src_usr
3434         LOOP
3435                 suffix := ' (' || src_usr || ')';
3436                 LOOP
3437                         BEGIN
3438                                 UPDATE  container.user_bucket
3439                                 SET     owner = dest_usr, name = name || suffix
3440                                 WHERE   id = bucket_row.id;
3441                         EXCEPTION WHEN unique_violation THEN
3442                                 suffix := suffix || ' ';
3443                                 CONTINUE;
3444                         END;
3445                         EXIT;
3446                 END LOOP;
3447         END LOOP;
3448
3449         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
3450
3451     -- vandelay.*
3452         -- transfer queues the same way we transfer buckets (see above)
3453         FOR queue_row in
3454                 SELECT id, name
3455                 FROM   vandelay.queue
3456                 WHERE  owner = src_usr
3457         LOOP
3458                 suffix := ' (' || src_usr || ')';
3459                 LOOP
3460                         BEGIN
3461                                 UPDATE  vandelay.queue
3462                                 SET     owner = dest_usr, name = name || suffix
3463                                 WHERE   id = queue_row.id;
3464                         EXCEPTION WHEN unique_violation THEN
3465                                 suffix := suffix || ' ';
3466                                 CONTINUE;
3467                         END;
3468                         EXIT;
3469                 END LOOP;
3470         END LOOP;
3471
3472     -- money.*
3473     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
3474     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
3475     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
3476     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
3477     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
3478
3479     -- action.*
3480     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
3481     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
3482     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
3483     UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
3484
3485     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
3486     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
3487     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
3488     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
3489
3490     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
3491     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
3492     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
3493     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
3494     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
3495
3496     -- acq.*
3497     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
3498         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
3499     UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
3500
3501         -- transfer picklists the same way we transfer buckets (see above)
3502         FOR picklist_row in
3503                 SELECT id, name
3504                 FROM   acq.picklist
3505                 WHERE  owner = src_usr
3506         LOOP
3507                 suffix := ' (' || src_usr || ')';
3508                 LOOP
3509                         BEGIN
3510                                 UPDATE  acq.picklist
3511                                 SET     owner = dest_usr, name = name || suffix
3512                                 WHERE   id = picklist_row.id;
3513                         EXCEPTION WHEN unique_violation THEN
3514                                 suffix := suffix || ' ';
3515                                 CONTINUE;
3516                         END;
3517                         EXIT;
3518                 END LOOP;
3519         END LOOP;
3520
3521     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
3522     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
3523     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
3524     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
3525     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
3526     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
3527     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
3528     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
3529
3530     -- asset.*
3531     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
3532     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
3533     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
3534     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
3535     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
3536     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
3537
3538     -- serial.*
3539     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
3540     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
3541
3542     -- reporter.*
3543     -- It's not uncommon to define the reporter schema in a replica 
3544     -- DB only, so don't assume these tables exist in the write DB.
3545     BEGIN
3546         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
3547     EXCEPTION WHEN undefined_table THEN
3548         -- do nothing
3549     END;
3550     BEGIN
3551         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
3552     EXCEPTION WHEN undefined_table THEN
3553         -- do nothing
3554     END;
3555     BEGIN
3556         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
3557     EXCEPTION WHEN undefined_table THEN
3558         -- do nothing
3559     END;
3560     BEGIN
3561                 -- transfer folders the same way we transfer buckets (see above)
3562                 FOR folder_row in
3563                         SELECT id, name
3564                         FROM   reporter.template_folder
3565                         WHERE  owner = src_usr
3566                 LOOP
3567                         suffix := ' (' || src_usr || ')';
3568                         LOOP
3569                                 BEGIN
3570                                         UPDATE  reporter.template_folder
3571                                         SET     owner = dest_usr, name = name || suffix
3572                                         WHERE   id = folder_row.id;
3573                                 EXCEPTION WHEN unique_violation THEN
3574                                         suffix := suffix || ' ';
3575                                         CONTINUE;
3576                                 END;
3577                                 EXIT;
3578                         END LOOP;
3579                 END LOOP;
3580     EXCEPTION WHEN undefined_table THEN
3581         -- do nothing
3582     END;
3583     BEGIN
3584                 -- transfer folders the same way we transfer buckets (see above)
3585                 FOR folder_row in
3586                         SELECT id, name
3587                         FROM   reporter.report_folder
3588                         WHERE  owner = src_usr
3589                 LOOP
3590                         suffix := ' (' || src_usr || ')';
3591                         LOOP
3592                                 BEGIN
3593                                         UPDATE  reporter.report_folder
3594                                         SET     owner = dest_usr, name = name || suffix
3595                                         WHERE   id = folder_row.id;
3596                                 EXCEPTION WHEN unique_violation THEN
3597                                         suffix := suffix || ' ';
3598                                         CONTINUE;
3599                                 END;
3600                                 EXIT;
3601                         END LOOP;
3602                 END LOOP;
3603     EXCEPTION WHEN undefined_table THEN
3604         -- do nothing
3605     END;
3606     BEGIN
3607                 -- transfer folders the same way we transfer buckets (see above)
3608                 FOR folder_row in
3609                         SELECT id, name
3610                         FROM   reporter.output_folder
3611                         WHERE  owner = src_usr
3612                 LOOP
3613                         suffix := ' (' || src_usr || ')';
3614                         LOOP
3615                                 BEGIN
3616                                         UPDATE  reporter.output_folder
3617                                         SET     owner = dest_usr, name = name || suffix
3618                                         WHERE   id = folder_row.id;
3619                                 EXCEPTION WHEN unique_violation THEN
3620                                         suffix := suffix || ' ';
3621                                         CONTINUE;
3622                                 END;
3623                                 EXIT;
3624                         END LOOP;
3625                 END LOOP;
3626     EXCEPTION WHEN undefined_table THEN
3627         -- do nothing
3628     END;
3629
3630     -- Finally, delete the source user
3631     DELETE FROM actor.usr WHERE id = src_usr;
3632
3633 END;
3634 $$ LANGUAGE plpgsql;
3635
3636
3637 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
3638         src_usr  IN INTEGER,
3639         specified_dest_usr IN INTEGER
3640 ) RETURNS VOID AS $$
3641 DECLARE
3642         suffix TEXT;
3643         renamable_row RECORD;
3644         dest_usr INTEGER;
3645 BEGIN
3646
3647         IF specified_dest_usr IS NULL THEN
3648                 dest_usr := 1; -- Admin user on stock installs
3649         ELSE
3650                 dest_usr := specified_dest_usr;
3651         END IF;
3652
3653         -- acq.*
3654         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
3655         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
3656         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
3657         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
3658         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
3659         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
3660     UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
3661         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
3662
3663         -- Update with a rename to avoid collisions
3664         FOR renamable_row in
3665                 SELECT id, name
3666                 FROM   acq.picklist
3667                 WHERE  owner = src_usr
3668         LOOP
3669                 suffix := ' (' || src_usr || ')';
3670                 LOOP
3671                         BEGIN
3672                                 UPDATE  acq.picklist
3673                                 SET     owner = dest_usr, name = name || suffix
3674                                 WHERE   id = renamable_row.id;
3675                         EXCEPTION WHEN unique_violation THEN
3676                                 suffix := suffix || ' ';
3677                                 CONTINUE;
3678                         END;
3679                         EXIT;
3680                 END LOOP;
3681         END LOOP;
3682
3683         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
3684         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
3685         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
3686         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
3687         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
3688         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
3689         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
3690         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
3691
3692         -- action.*
3693         DELETE FROM action.circulation WHERE usr = src_usr;
3694         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
3695         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
3696         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
3697         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
3698         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
3699         DELETE FROM action.hold_request WHERE usr = src_usr;
3700         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
3701         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
3702         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
3703         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
3704         DELETE FROM action.survey_response WHERE usr = src_usr;
3705         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
3706         DELETE FROM action.usr_circ_history WHERE usr = src_usr;
3707
3708         -- actor.*
3709         DELETE FROM actor.card WHERE usr = src_usr;
3710         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
3711
3712         -- The following update is intended to avoid transient violations of a foreign
3713         -- key constraint, whereby actor.usr_address references itself.  It may not be
3714         -- necessary, but it does no harm.
3715         UPDATE actor.usr_address SET replaces = NULL
3716                 WHERE usr = src_usr AND replaces IS NOT NULL;
3717         DELETE FROM actor.usr_address WHERE usr = src_usr;
3718         DELETE FROM actor.usr_note WHERE usr = src_usr;
3719         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
3720         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
3721         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
3722         DELETE FROM actor.usr_setting WHERE usr = src_usr;
3723         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
3724         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
3725
3726         -- asset.*
3727         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
3728         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
3729         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
3730         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
3731         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
3732         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
3733
3734         -- auditor.*
3735         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
3736         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
3737         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
3738         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
3739         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
3740         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
3741         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
3742         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
3743
3744         -- biblio.*
3745         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
3746         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
3747         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
3748         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
3749
3750         -- container.*
3751         -- Update buckets with a rename to avoid collisions
3752         FOR renamable_row in
3753                 SELECT id, name
3754                 FROM   container.biblio_record_entry_bucket
3755                 WHERE  owner = src_usr
3756         LOOP
3757                 suffix := ' (' || src_usr || ')';
3758                 LOOP
3759                         BEGIN
3760                                 UPDATE  container.biblio_record_entry_bucket
3761                                 SET     owner = dest_usr, name = name || suffix
3762                                 WHERE   id = renamable_row.id;
3763                         EXCEPTION WHEN unique_violation THEN
3764                                 suffix := suffix || ' ';
3765                                 CONTINUE;
3766                         END;
3767                         EXIT;
3768                 END LOOP;
3769         END LOOP;
3770
3771         FOR renamable_row in
3772                 SELECT id, name
3773                 FROM   container.call_number_bucket
3774                 WHERE  owner = src_usr
3775         LOOP
3776                 suffix := ' (' || src_usr || ')';
3777                 LOOP
3778                         BEGIN
3779                                 UPDATE  container.call_number_bucket
3780                                 SET     owner = dest_usr, name = name || suffix
3781                                 WHERE   id = renamable_row.id;
3782                         EXCEPTION WHEN unique_violation THEN
3783                                 suffix := suffix || ' ';
3784                                 CONTINUE;
3785                         END;
3786                         EXIT;
3787                 END LOOP;
3788         END LOOP;
3789
3790         FOR renamable_row in
3791                 SELECT id, name
3792                 FROM   container.copy_bucket
3793                 WHERE  owner = src_usr
3794         LOOP
3795                 suffix := ' (' || src_usr || ')';
3796                 LOOP
3797                         BEGIN
3798                                 UPDATE  container.copy_bucket
3799                                 SET     owner = dest_usr, name = name || suffix
3800                                 WHERE   id = renamable_row.id;
3801                         EXCEPTION WHEN unique_violation THEN
3802                                 suffix := suffix || ' ';
3803                                 CONTINUE;
3804                         END;
3805                         EXIT;
3806                 END LOOP;
3807         END LOOP;
3808
3809         FOR renamable_row in
3810                 SELECT id, name
3811                 FROM   container.user_bucket
3812                 WHERE  owner = src_usr
3813         LOOP
3814                 suffix := ' (' || src_usr || ')';
3815                 LOOP
3816                         BEGIN
3817                                 UPDATE  container.user_bucket
3818                                 SET     owner = dest_usr, name = name || suffix
3819                                 WHERE   id = renamable_row.id;
3820                         EXCEPTION WHEN unique_violation THEN
3821                                 suffix := suffix || ' ';
3822                                 CONTINUE;
3823                         END;
3824                         EXIT;
3825                 END LOOP;
3826         END LOOP;
3827
3828         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
3829
3830         -- money.*
3831         DELETE FROM money.billable_xact WHERE usr = src_usr;
3832         DELETE FROM money.collections_tracker WHERE usr = src_usr;
3833         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
3834
3835         -- permission.*
3836         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
3837         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
3838         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
3839         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
3840
3841         -- reporter.*
3842         -- Update with a rename to avoid collisions
3843         BEGIN
3844                 FOR renamable_row in
3845                         SELECT id, name
3846                         FROM   reporter.output_folder
3847                         WHERE  owner = src_usr
3848                 LOOP
3849                         suffix := ' (' || src_usr || ')';
3850                         LOOP
3851                                 BEGIN
3852                                         UPDATE  reporter.output_folder
3853                                         SET     owner = dest_usr, name = name || suffix
3854                                         WHERE   id = renamable_row.id;
3855                                 EXCEPTION WHEN unique_violation THEN
3856                                         suffix := suffix || ' ';
3857                                         CONTINUE;
3858                                 END;
3859                                 EXIT;
3860                         END LOOP;
3861                 END LOOP;
3862         EXCEPTION WHEN undefined_table THEN
3863                 -- do nothing
3864         END;
3865
3866         BEGIN
3867                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
3868         EXCEPTION WHEN undefined_table THEN
3869                 -- do nothing
3870         END;
3871
3872         -- Update with a rename to avoid collisions
3873         BEGIN
3874                 FOR renamable_row in
3875                         SELECT id, name
3876                         FROM   reporter.report_folder
3877                         WHERE  owner = src_usr
3878                 LOOP
3879                         suffix := ' (' || src_usr || ')';
3880                         LOOP
3881                                 BEGIN
3882                                         UPDATE  reporter.report_folder
3883                                         SET     owner = dest_usr, name = name || suffix
3884                                         WHERE   id = renamable_row.id;
3885                                 EXCEPTION WHEN unique_violation THEN
3886                                         suffix := suffix || ' ';
3887                                         CONTINUE;
3888                                 END;
3889                                 EXIT;
3890                         END LOOP;
3891                 END LOOP;
3892         EXCEPTION WHEN undefined_table THEN
3893                 -- do nothing
3894         END;
3895
3896         BEGIN
3897                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
3898         EXCEPTION WHEN undefined_table THEN
3899                 -- do nothing
3900         END;
3901
3902         BEGIN
3903                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
3904         EXCEPTION WHEN undefined_table THEN
3905                 -- do nothing
3906         END;
3907
3908         -- Update with a rename to avoid collisions
3909         BEGIN
3910                 FOR renamable_row in
3911                         SELECT id, name
3912                         FROM   reporter.template_folder
3913                         WHERE  owner = src_usr
3914                 LOOP
3915                         suffix := ' (' || src_usr || ')';
3916                         LOOP
3917                                 BEGIN
3918                                         UPDATE  reporter.template_folder
3919                                         SET     owner = dest_usr, name = name || suffix
3920                                         WHERE   id = renamable_row.id;
3921                                 EXCEPTION WHEN unique_violation THEN
3922                                         suffix := suffix || ' ';
3923                                         CONTINUE;
3924                                 END;
3925                                 EXIT;
3926                         END LOOP;
3927                 END LOOP;
3928         EXCEPTION WHEN undefined_table THEN
3929         -- do nothing
3930         END;
3931
3932         -- vandelay.*
3933         -- Update with a rename to avoid collisions
3934         FOR renamable_row in
3935                 SELECT id, name
3936                 FROM   vandelay.queue
3937                 WHERE  owner = src_usr
3938         LOOP
3939                 suffix := ' (' || src_usr || ')';
3940                 LOOP
3941                         BEGIN
3942                                 UPDATE  vandelay.queue
3943                                 SET     owner = dest_usr, name = name || suffix
3944                                 WHERE   id = renamable_row.id;
3945                         EXCEPTION WHEN unique_violation THEN
3946                                 suffix := suffix || ' ';
3947                                 CONTINUE;
3948                         END;
3949                         EXIT;
3950                 END LOOP;
3951         END LOOP;
3952
3953     -- NULL-ify addresses last so other cleanup (e.g. circ anonymization)
3954     -- can access the information before deletion.
3955         UPDATE actor.usr SET
3956                 active = FALSE,
3957                 card = NULL,
3958                 mailing_address = NULL,
3959                 billing_address = NULL
3960         WHERE id = src_usr;
3961
3962 END;
3963 $$ LANGUAGE plpgsql;
3964
3965
3966
3967
3968
3969 -- UNDO (minus user purge/merge changes)
3970 /*
3971
3972 DROP VIEW auditor.acq_invoice_lifecycle;
3973 ALTER TABLE acq.invoice ADD COLUMN complete BOOLEAN NOT NULL DEFAULT FALSE;
3974 ALTER TABLE auditor.acq_invoice_history 
3975     ADD COLUMN complete BOOLEAN NOT NULL DEFAULT FALSE;
3976 UPDATE acq.invoice SET complete = TRUE where close_date IS NOT NULL;
3977 UPDATE auditor.acq_invoice_history 
3978     SET complete = TRUE where close_date IS NOT NULL;
3979 SET CONSTRAINTS ALL IMMEDIATE; -- or get pending triggers error.
3980 ALTER TABLE acq.invoice DROP COLUMN close_date, DROP COLUMN closed_by;
3981 ALTER TABLE auditor.acq_invoice_history
3982     DROP COLUMN close_date, DROP COLUMN closed_by;
3983 SELECT auditor.update_auditors();
3984
3985 */
3986
3987
3988 SELECT evergreen.upgrade_deps_block_check('1129', :eg_version);
3989
3990 INSERT into config.workstation_setting_type (name, grp, datatype, label)
3991 VALUES (
3992     'eg.grid.admin.acq.cancel_reason', 'gui', 'object',
3993     oils_i18n_gettext (
3994         'eg.grid.admin.acq.cancel_reason',
3995         'Grid Config: admin.acq.cancel_reason',
3996         'cwst', 'label'
3997     )
3998 ), (
3999     'eg.grid.admin.acq.claim_event_type', 'gui', 'object',
4000     oils_i18n_gettext (
4001     'eg.grid.admin.acq.claim_event_type',
4002         'Grid Config: admin.acq.claim_event_type',
4003         'cwst', 'label'
4004     )
4005 ), (
4006     'eg.grid.admin.acq.claim_policy', 'gui', 'object',
4007     oils_i18n_gettext (
4008     'eg.grid.admin.acq.claim_policy',
4009         'Grid Config: admin.acq.claim_policy',
4010         'cwst', 'label'
4011     )
4012 ), (
4013     'eg.grid.admin.acq.claim_policy_action', 'gui', 'object',
4014     oils_i18n_gettext (
4015     'eg.grid.admin.acq.claim_policy_action',
4016         'Grid Config: admin.acq.claim_policy_action',
4017         'cwst', 'label'
4018     )
4019 ), (
4020     'eg.grid.admin.acq.claim_type', 'gui', 'object',
4021     oils_i18n_gettext (
4022     'eg.grid.admin.acq.claim_type',
4023         'Grid Config: admin.acq.claim_type',
4024         'cwst', 'label'
4025     )
4026 ), (
4027     'eg.grid.admin.acq.currency_type', 'gui', 'object',
4028     oils_i18n_gettext (
4029     'eg.grid.admin.acq.currency_type',
4030         'Grid Config: admin.acq.currency_type',
4031         'cwst', 'label'
4032     )
4033 ), (
4034     'eg.grid.admin.acq.edi_account', 'gui', 'object',
4035     oils_i18n_gettext (
4036     'eg.grid.admin.acq.edi_account',
4037         'Grid Config: admin.acq.edi_account',
4038         'cwst', 'label'
4039     )
4040 ), (
4041     'eg.grid.admin.acq.edi_message', 'gui', 'object',
4042     oils_i18n_gettext (
4043     'eg.grid.admin.acq.edi_message',
4044         'Grid Config: admin.acq.edi_message',
4045         'cwst', 'label'
4046     )
4047 ), (
4048     'eg.grid.admin.acq.exchange_rate', 'gui', 'object',
4049     oils_i18n_gettext (
4050     'eg.grid.admin.acq.exchange_rate',
4051         'Grid Config: admin.acq.exchange_rate',
4052         'cwst', 'label'
4053     )
4054 ), (
4055     'eg.grid.admin.acq.fund_tag', 'gui', 'object',
4056     oils_i18n_gettext (
4057     'eg.grid.admin.acq.fund_tag',
4058         'Grid Config: admin.acq.fund_tag',
4059         'cwst', 'label'
4060     )
4061 ), (
4062     'eg.grid.admin.acq.invoice_item_type', 'gui', 'object',
4063     oils_i18n_gettext (
4064     'eg.grid.admin.acq.invoice_item_type',
4065         'Grid Config: admin.acq.invoice_item_type',
4066         'cwst', 'label'
4067     )
4068 ), (
4069     'eg.grid.admin.acq.invoice_payment_method', 'gui', 'object',
4070     oils_i18n_gettext (
4071     'eg.grid.admin.acq.invoice_payment_method',
4072         'Grid Config: admin.acq.invoice_payment_method',
4073         'cwst', 'label'
4074     )
4075 ), (
4076     'eg.grid.admin.acq.lineitem_alert_text', 'gui', 'object',
4077     oils_i18n_gettext (
4078     'eg.grid.admin.acq.lineitem_alert_text',
4079         'Grid Config: admin.acq.lineitem_alert_text',
4080         'cwst', 'label'
4081     )
4082 ), (
4083     'eg.grid.admin.acq.lineitem_marc_attr_definition', 'gui', 'object',
4084     oils_i18n_gettext (
4085     'eg.grid.admin.acq.lineitem_marc_attr_definition',
4086         'Grid Config: admin.acq.lineitem_marc_attr_definition',
4087         'cwst', 'label'
4088     )
4089 );
4090
4091
4092 SELECT evergreen.upgrade_deps_block_check('1130', :eg_version);
4093
4094 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 $$
4095 DECLARE
4096         suffix TEXT;
4097         bucket_row RECORD;
4098         picklist_row RECORD;
4099         queue_row RECORD;
4100         folder_row RECORD;
4101 BEGIN
4102
4103     -- Bail if src_usr equals dest_usr because the result of merging a
4104     -- user with itself is not what you want.
4105     IF src_usr = dest_usr THEN
4106         RETURN;
4107     END IF;
4108
4109     -- do some initial cleanup 
4110     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
4111     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
4112     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
4113
4114     -- actor.*
4115     IF del_cards THEN
4116         DELETE FROM actor.card where usr = src_usr;
4117     ELSE
4118         IF deactivate_cards THEN
4119             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
4120         END IF;
4121         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
4122     END IF;
4123
4124
4125     IF del_addrs THEN
4126         DELETE FROM actor.usr_address WHERE usr = src_usr;
4127     ELSE
4128         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
4129     END IF;
4130
4131     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
4132     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
4133     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
4134     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
4135     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
4136
4137     -- permission.*
4138     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
4139     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
4140     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
4141     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
4142
4143
4144     -- container.*
4145         
4146         -- For each *_bucket table: transfer every bucket belonging to src_usr
4147         -- into the custody of dest_usr.
4148         --
4149         -- In order to avoid colliding with an existing bucket owned by
4150         -- the destination user, append the source user's id (in parenthesese)
4151         -- to the name.  If you still get a collision, add successive
4152         -- spaces to the name and keep trying until you succeed.
4153         --
4154         FOR bucket_row in
4155                 SELECT id, name
4156                 FROM   container.biblio_record_entry_bucket
4157                 WHERE  owner = src_usr
4158         LOOP
4159                 suffix := ' (' || src_usr || ')';
4160                 LOOP
4161                         BEGIN
4162                                 UPDATE  container.biblio_record_entry_bucket
4163                                 SET     owner = dest_usr, name = name || suffix
4164                                 WHERE   id = bucket_row.id;
4165                         EXCEPTION WHEN unique_violation THEN
4166                                 suffix := suffix || ' ';
4167                                 CONTINUE;
4168                         END;
4169                         EXIT;
4170                 END LOOP;
4171         END LOOP;
4172
4173         FOR bucket_row in
4174                 SELECT id, name
4175                 FROM   container.call_number_bucket
4176                 WHERE  owner = src_usr
4177         LOOP
4178                 suffix := ' (' || src_usr || ')';
4179                 LOOP
4180                         BEGIN
4181                                 UPDATE  container.call_number_bucket
4182                                 SET     owner = dest_usr, name = name || suffix
4183                                 WHERE   id = bucket_row.id;
4184                         EXCEPTION WHEN unique_violation THEN
4185                                 suffix := suffix || ' ';
4186                                 CONTINUE;
4187                         END;
4188                         EXIT;
4189                 END LOOP;
4190         END LOOP;
4191
4192         FOR bucket_row in
4193                 SELECT id, name
4194                 FROM   container.copy_bucket
4195                 WHERE  owner = src_usr
4196         LOOP
4197                 suffix := ' (' || src_usr || ')';
4198                 LOOP
4199                         BEGIN
4200                                 UPDATE  container.copy_bucket
4201                                 SET     owner = dest_usr, name = name || suffix
4202                                 WHERE   id = bucket_row.id;
4203                         EXCEPTION WHEN unique_violation THEN
4204                                 suffix := suffix || ' ';
4205                                 CONTINUE;
4206                         END;
4207                         EXIT;
4208                 END LOOP;
4209         END LOOP;
4210
4211         FOR bucket_row in
4212                 SELECT id, name
4213                 FROM   container.user_bucket
4214                 WHERE  owner = src_usr
4215         LOOP
4216                 suffix := ' (' || src_usr || ')';
4217                 LOOP
4218                         BEGIN
4219                                 UPDATE  container.user_bucket
4220                                 SET     owner = dest_usr, name = name || suffix
4221                                 WHERE   id = bucket_row.id;
4222                         EXCEPTION WHEN unique_violation THEN
4223                                 suffix := suffix || ' ';
4224                                 CONTINUE;
4225                         END;
4226                         EXIT;
4227                 END LOOP;
4228         END LOOP;
4229
4230         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
4231
4232     -- vandelay.*
4233         -- transfer queues the same way we transfer buckets (see above)
4234         FOR queue_row in
4235                 SELECT id, name
4236                 FROM   vandelay.queue
4237                 WHERE  owner = src_usr
4238         LOOP
4239                 suffix := ' (' || src_usr || ')';
4240                 LOOP
4241                         BEGIN
4242                                 UPDATE  vandelay.queue
4243                                 SET     owner = dest_usr, name = name || suffix
4244                                 WHERE   id = queue_row.id;
4245                         EXCEPTION WHEN unique_violation THEN
4246                                 suffix := suffix || ' ';
4247                                 CONTINUE;
4248                         END;
4249                         EXIT;
4250                 END LOOP;
4251         END LOOP;
4252
4253     UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
4254
4255     -- money.*
4256     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
4257     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
4258     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
4259     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
4260     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
4261
4262     -- action.*
4263     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
4264     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
4265     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
4266     UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
4267
4268     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
4269     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
4270     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
4271     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
4272
4273     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
4274     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
4275     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
4276     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
4277     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
4278
4279     -- acq.*
4280     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
4281         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
4282     UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
4283
4284         -- transfer picklists the same way we transfer buckets (see above)
4285         FOR picklist_row in
4286                 SELECT id, name
4287                 FROM   acq.picklist
4288                 WHERE  owner = src_usr
4289         LOOP
4290                 suffix := ' (' || src_usr || ')';
4291                 LOOP
4292                         BEGIN
4293                                 UPDATE  acq.picklist
4294                                 SET     owner = dest_usr, name = name || suffix
4295                                 WHERE   id = picklist_row.id;
4296                         EXCEPTION WHEN unique_violation THEN
4297                                 suffix := suffix || ' ';
4298                                 CONTINUE;
4299                         END;
4300                         EXIT;
4301                 END LOOP;
4302         END LOOP;
4303
4304     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
4305     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
4306     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
4307     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
4308     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
4309     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
4310     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
4311     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
4312
4313     -- asset.*
4314     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
4315     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
4316     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
4317     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
4318     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
4319     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
4320
4321     -- serial.*
4322     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
4323     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
4324
4325     -- reporter.*
4326     -- It's not uncommon to define the reporter schema in a replica 
4327     -- DB only, so don't assume these tables exist in the write DB.
4328     BEGIN
4329         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
4330     EXCEPTION WHEN undefined_table THEN
4331         -- do nothing
4332     END;
4333     BEGIN
4334         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
4335     EXCEPTION WHEN undefined_table THEN
4336         -- do nothing
4337     END;
4338     BEGIN
4339         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
4340     EXCEPTION WHEN undefined_table THEN
4341         -- do nothing
4342     END;
4343     BEGIN
4344                 -- transfer folders the same way we transfer buckets (see above)
4345                 FOR folder_row in
4346                         SELECT id, name
4347                         FROM   reporter.template_folder
4348                         WHERE  owner = src_usr
4349                 LOOP
4350                         suffix := ' (' || src_usr || ')';
4351                         LOOP
4352                                 BEGIN
4353                                         UPDATE  reporter.template_folder
4354                                         SET     owner = dest_usr, name = name || suffix
4355                                         WHERE   id = folder_row.id;
4356                                 EXCEPTION WHEN unique_violation THEN
4357                                         suffix := suffix || ' ';
4358                                         CONTINUE;
4359                                 END;
4360                                 EXIT;
4361                         END LOOP;
4362                 END LOOP;
4363     EXCEPTION WHEN undefined_table THEN
4364         -- do nothing
4365     END;
4366     BEGIN
4367                 -- transfer folders the same way we transfer buckets (see above)
4368                 FOR folder_row in
4369                         SELECT id, name
4370                         FROM   reporter.report_folder
4371                         WHERE  owner = src_usr
4372                 LOOP
4373                         suffix := ' (' || src_usr || ')';
4374                         LOOP
4375                                 BEGIN
4376                                         UPDATE  reporter.report_folder
4377                                         SET     owner = dest_usr, name = name || suffix
4378                                         WHERE   id = folder_row.id;
4379                                 EXCEPTION WHEN unique_violation THEN
4380                                         suffix := suffix || ' ';
4381                                         CONTINUE;
4382                                 END;
4383                                 EXIT;
4384                         END LOOP;
4385                 END LOOP;
4386     EXCEPTION WHEN undefined_table THEN
4387         -- do nothing
4388     END;
4389     BEGIN
4390                 -- transfer folders the same way we transfer buckets (see above)
4391                 FOR folder_row in
4392                         SELECT id, name
4393                         FROM   reporter.output_folder
4394                         WHERE  owner = src_usr
4395                 LOOP
4396                         suffix := ' (' || src_usr || ')';
4397                         LOOP
4398                                 BEGIN
4399                                         UPDATE  reporter.output_folder
4400                                         SET     owner = dest_usr, name = name || suffix
4401                                         WHERE   id = folder_row.id;
4402                                 EXCEPTION WHEN unique_violation THEN
4403                                         suffix := suffix || ' ';
4404                                         CONTINUE;
4405                                 END;
4406                                 EXIT;
4407                         END LOOP;
4408                 END LOOP;
4409     EXCEPTION WHEN undefined_table THEN
4410         -- do nothing
4411     END;
4412
4413     -- propagate preferred name values from the source user to the
4414     -- destination user, but only when values are not being replaced.
4415     WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
4416     UPDATE actor.usr SET 
4417         pref_prefix = 
4418             COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
4419         pref_first_given_name = 
4420             COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
4421         pref_second_given_name = 
4422             COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
4423         pref_family_name = 
4424             COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
4425         pref_suffix = 
4426             COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
4427     WHERE id = dest_usr;
4428
4429     -- Copy and deduplicate name keywords
4430     -- String -> array -> rows -> DISTINCT -> array -> string
4431     WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
4432          dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
4433     UPDATE actor.usr SET name_keywords = (
4434         WITH keywords AS (
4435             SELECT DISTINCT UNNEST(
4436                 REGEXP_SPLIT_TO_ARRAY(
4437                     COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
4438                     COALESCE((SELECT name_keywords FROM dusr), ''),  E'\\s+'
4439                 )
4440             ) AS parts
4441         ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
4442     ) WHERE id = dest_usr;
4443
4444     -- Finally, delete the source user
4445     DELETE FROM actor.usr WHERE id = src_usr;
4446
4447 END;
4448 $$ LANGUAGE plpgsql;
4449
4450
4451 SELECT evergreen.upgrade_deps_block_check('1131', :eg_version);
4452
4453 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 $$
4454 DECLARE
4455         suffix TEXT;
4456         bucket_row RECORD;
4457         picklist_row RECORD;
4458         queue_row RECORD;
4459         folder_row RECORD;
4460 BEGIN
4461
4462     -- Bail if src_usr equals dest_usr because the result of merging a
4463     -- user with itself is not what you want.
4464     IF src_usr = dest_usr THEN
4465         RETURN;
4466     END IF;
4467
4468     -- do some initial cleanup 
4469     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
4470     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
4471     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
4472
4473     -- actor.*
4474     IF del_cards THEN
4475         DELETE FROM actor.card where usr = src_usr;
4476     ELSE
4477         IF deactivate_cards THEN
4478             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
4479         END IF;
4480         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
4481     END IF;
4482
4483
4484     IF del_addrs THEN
4485         DELETE FROM actor.usr_address WHERE usr = src_usr;
4486     ELSE
4487         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
4488     END IF;
4489
4490     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
4491     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
4492     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
4493     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
4494     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
4495
4496     -- permission.*
4497     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
4498     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
4499     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
4500     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
4501
4502
4503     -- container.*
4504         
4505         -- For each *_bucket table: transfer every bucket belonging to src_usr
4506         -- into the custody of dest_usr.
4507         --
4508         -- In order to avoid colliding with an existing bucket owned by
4509         -- the destination user, append the source user's id (in parenthesese)
4510         -- to the name.  If you still get a collision, add successive
4511         -- spaces to the name and keep trying until you succeed.
4512         --
4513         FOR bucket_row in
4514                 SELECT id, name
4515                 FROM   container.biblio_record_entry_bucket
4516                 WHERE  owner = src_usr
4517         LOOP
4518                 suffix := ' (' || src_usr || ')';
4519                 LOOP
4520                         BEGIN
4521                                 UPDATE  container.biblio_record_entry_bucket
4522                                 SET     owner = dest_usr, name = name || suffix
4523                                 WHERE   id = bucket_row.id;
4524                         EXCEPTION WHEN unique_violation THEN
4525                                 suffix := suffix || ' ';
4526                                 CONTINUE;
4527                         END;
4528                         EXIT;
4529                 END LOOP;
4530         END LOOP;
4531
4532         FOR bucket_row in
4533                 SELECT id, name
4534                 FROM   container.call_number_bucket
4535                 WHERE  owner = src_usr
4536         LOOP
4537                 suffix := ' (' || src_usr || ')';
4538                 LOOP
4539                         BEGIN
4540                                 UPDATE  container.call_number_bucket
4541                                 SET     owner = dest_usr, name = name || suffix
4542                                 WHERE   id = bucket_row.id;
4543                         EXCEPTION WHEN unique_violation THEN
4544                                 suffix := suffix || ' ';
4545                                 CONTINUE;
4546                         END;
4547                         EXIT;
4548                 END LOOP;
4549         END LOOP;
4550
4551         FOR bucket_row in
4552                 SELECT id, name
4553                 FROM   container.copy_bucket
4554                 WHERE  owner = src_usr
4555         LOOP
4556                 suffix := ' (' || src_usr || ')';
4557                 LOOP
4558                         BEGIN
4559                                 UPDATE  container.copy_bucket
4560                                 SET     owner = dest_usr, name = name || suffix
4561                                 WHERE   id = bucket_row.id;
4562                         EXCEPTION WHEN unique_violation THEN
4563                                 suffix := suffix || ' ';
4564                                 CONTINUE;
4565                         END;
4566                         EXIT;
4567                 END LOOP;
4568         END LOOP;
4569
4570         FOR bucket_row in
4571                 SELECT id, name
4572                 FROM   container.user_bucket
4573                 WHERE  owner = src_usr
4574         LOOP
4575                 suffix := ' (' || src_usr || ')';
4576                 LOOP
4577                         BEGIN
4578                                 UPDATE  container.user_bucket
4579                                 SET     owner = dest_usr, name = name || suffix
4580                                 WHERE   id = bucket_row.id;
4581                         EXCEPTION WHEN unique_violation THEN
4582                                 suffix := suffix || ' ';
4583                                 CONTINUE;
4584                         END;
4585                         EXIT;
4586                 END LOOP;
4587         END LOOP;
4588
4589         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
4590
4591     -- vandelay.*
4592         -- transfer queues the same way we transfer buckets (see above)
4593         FOR queue_row in
4594                 SELECT id, name
4595                 FROM   vandelay.queue
4596                 WHERE  owner = src_usr
4597         LOOP
4598                 suffix := ' (' || src_usr || ')';
4599                 LOOP
4600                         BEGIN
4601                                 UPDATE  vandelay.queue
4602                                 SET     owner = dest_usr, name = name || suffix
4603                                 WHERE   id = queue_row.id;
4604                         EXCEPTION WHEN unique_violation THEN
4605                                 suffix := suffix || ' ';
4606                                 CONTINUE;
4607                         END;
4608                         EXIT;
4609                 END LOOP;
4610         END LOOP;
4611
4612     UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
4613
4614     -- money.*
4615     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
4616     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
4617     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
4618     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
4619     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
4620
4621     -- action.*
4622     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
4623     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
4624     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
4625     UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
4626
4627     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
4628     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
4629     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
4630     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
4631
4632     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
4633     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
4634     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
4635     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
4636     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
4637
4638     -- acq.*
4639     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
4640         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
4641     UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
4642
4643         -- transfer picklists the same way we transfer buckets (see above)
4644         FOR picklist_row in
4645                 SELECT id, name
4646                 FROM   acq.picklist
4647                 WHERE  owner = src_usr
4648         LOOP
4649                 suffix := ' (' || src_usr || ')';
4650                 LOOP
4651                         BEGIN
4652                                 UPDATE  acq.picklist
4653                                 SET     owner = dest_usr, name = name || suffix
4654                                 WHERE   id = picklist_row.id;
4655                         EXCEPTION WHEN unique_violation THEN
4656                                 suffix := suffix || ' ';
4657                                 CONTINUE;
4658                         END;
4659                         EXIT;
4660                 END LOOP;
4661         END LOOP;
4662
4663     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
4664     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
4665     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
4666     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
4667     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
4668     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
4669     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
4670     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
4671
4672     -- asset.*
4673     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
4674     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
4675     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
4676     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
4677     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
4678     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
4679
4680     -- serial.*
4681     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
4682     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
4683
4684     -- reporter.*
4685     -- It's not uncommon to define the reporter schema in a replica 
4686     -- DB only, so don't assume these tables exist in the write DB.
4687     BEGIN
4688         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
4689     EXCEPTION WHEN undefined_table THEN
4690         -- do nothing
4691     END;
4692     BEGIN
4693         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
4694     EXCEPTION WHEN undefined_table THEN
4695         -- do nothing
4696     END;
4697     BEGIN
4698         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
4699     EXCEPTION WHEN undefined_table THEN
4700         -- do nothing
4701     END;
4702     BEGIN
4703                 -- transfer folders the same way we transfer buckets (see above)
4704                 FOR folder_row in
4705                         SELECT id, name
4706                         FROM   reporter.template_folder
4707                         WHERE  owner = src_usr
4708                 LOOP
4709                         suffix := ' (' || src_usr || ')';
4710                         LOOP
4711                                 BEGIN
4712                                         UPDATE  reporter.template_folder
4713                                         SET     owner = dest_usr, name = name || suffix
4714                                         WHERE   id = folder_row.id;
4715                                 EXCEPTION WHEN unique_violation THEN
4716                                         suffix := suffix || ' ';
4717                                         CONTINUE;
4718                                 END;
4719                                 EXIT;
4720                         END LOOP;
4721                 END LOOP;
4722     EXCEPTION WHEN undefined_table THEN
4723         -- do nothing
4724     END;
4725     BEGIN
4726                 -- transfer folders the same way we transfer buckets (see above)
4727                 FOR folder_row in
4728                         SELECT id, name
4729                         FROM   reporter.report_folder
4730                         WHERE  owner = src_usr
4731                 LOOP
4732                         suffix := ' (' || src_usr || ')';
4733                         LOOP
4734                                 BEGIN
4735                                         UPDATE  reporter.report_folder
4736                                         SET     owner = dest_usr, name = name || suffix
4737                                         WHERE   id = folder_row.id;
4738                                 EXCEPTION WHEN unique_violation THEN
4739                                         suffix := suffix || ' ';
4740                                         CONTINUE;
4741                                 END;
4742                                 EXIT;
4743                         END LOOP;
4744                 END LOOP;
4745     EXCEPTION WHEN undefined_table THEN
4746         -- do nothing
4747     END;
4748     BEGIN
4749                 -- transfer folders the same way we transfer buckets (see above)
4750                 FOR folder_row in
4751                         SELECT id, name
4752                         FROM   reporter.output_folder
4753                         WHERE  owner = src_usr
4754                 LOOP
4755                         suffix := ' (' || src_usr || ')';
4756                         LOOP
4757                                 BEGIN
4758                                         UPDATE  reporter.output_folder
4759                                         SET     owner = dest_usr, name = name || suffix
4760                                         WHERE   id = folder_row.id;
4761                                 EXCEPTION WHEN unique_violation THEN
4762                                         suffix := suffix || ' ';
4763                                         CONTINUE;
4764                                 END;
4765                                 EXIT;
4766                         END LOOP;
4767                 END LOOP;
4768     EXCEPTION WHEN undefined_table THEN
4769         -- do nothing
4770     END;
4771
4772     -- propagate preferred name values from the source user to the
4773     -- destination user, but only when values are not being replaced.
4774     WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
4775     UPDATE actor.usr SET 
4776         pref_prefix = 
4777             COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
4778         pref_first_given_name = 
4779             COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
4780         pref_second_given_name = 
4781             COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
4782         pref_family_name = 
4783             COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
4784         pref_suffix = 
4785             COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
4786     WHERE id = dest_usr;
4787
4788     -- Copy and deduplicate name keywords
4789     -- String -> array -> rows -> DISTINCT -> array -> string
4790     WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
4791          dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
4792     UPDATE actor.usr SET name_keywords = (
4793         WITH keywords AS (
4794             SELECT DISTINCT UNNEST(
4795                 REGEXP_SPLIT_TO_ARRAY(
4796                     COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
4797                     COALESCE((SELECT name_keywords FROM dusr), ''),  E'\\s+'
4798                 )
4799             ) AS parts
4800         ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
4801     ) WHERE id = dest_usr;
4802
4803     -- Finally, delete the source user
4804     DELETE FROM actor.usr WHERE id = src_usr;
4805
4806 END;
4807 $$ LANGUAGE plpgsql;
4808
4809
4810 -- check whether patch can be applied
4811 SELECT evergreen.upgrade_deps_block_check('1132', :eg_version); -- remingtron/csharp
4812
4813 -- fix two typo/pasto's in setting descriptions
4814 UPDATE config.org_unit_setting_type
4815 SET description = oils_i18n_gettext(
4816         'circ.copy_alerts.forgive_fines_on_long_overdue_checkin',
4817         'Controls whether fines are automatically forgiven when checking out an '||
4818         'item that has been marked as long-overdue, and the corresponding copy alert has been '||
4819         'suppressed.',
4820         'coust', 'description'
4821 )
4822 WHERE NAME = 'circ.copy_alerts.forgive_fines_on_long_overdue_checkin';
4823
4824 UPDATE config.org_unit_setting_type
4825 SET description = oils_i18n_gettext(
4826         'circ.longoverdue.xact_open_on_zero',
4827         'Leave transaction open when long-overdue balance equals zero.  ' ||
4828         'This leaves the long-overdue copy on the patron record when it is paid',
4829         'coust', 'description'
4830 )
4831 WHERE NAME = 'circ.longoverdue.xact_open_on_zero';
4832
4833
4834
4835 SELECT evergreen.upgrade_deps_block_check('1133', :eg_version);
4836
4837 /* 
4838 Unique indexes are not inherited by child tables, so they will not prevent
4839 duplicate inserts on action.transit_copy and action.hold_transit_copy,
4840 for example.  Use check constraints instead to enforce unique-per-copy
4841 transits accross all transit types.
4842 */
4843
4844 -- Create an index for speedy check constraint lookups.
4845 CREATE INDEX active_transit_for_copy 
4846     ON action.transit_copy (target_copy)
4847     WHERE dest_recv_time IS NULL AND cancel_time IS NULL;
4848
4849 -- Check for duplicate transits across all transit types
4850 CREATE OR REPLACE FUNCTION action.copy_transit_is_unique() 
4851     RETURNS TRIGGER AS $func$
4852 BEGIN
4853     PERFORM * FROM action.transit_copy 
4854         WHERE target_copy = NEW.target_copy 
4855               AND dest_recv_time IS NULL 
4856               AND cancel_time IS NULL;
4857     IF FOUND THEN
4858         RAISE EXCEPTION 'Copy id=% is already in transit', NEW.target_copy;
4859     END IF;
4860     RETURN NULL;
4861 END;
4862 $func$ LANGUAGE PLPGSQL STABLE;
4863
4864 -- Apply constraint to all transit tables
4865 CREATE CONSTRAINT TRIGGER transit_copy_is_unique_check
4866     AFTER INSERT ON action.transit_copy
4867     FOR EACH ROW EXECUTE PROCEDURE action.copy_transit_is_unique();
4868
4869 CREATE CONSTRAINT TRIGGER hold_transit_copy_is_unique_check
4870     AFTER INSERT ON action.hold_transit_copy
4871     FOR EACH ROW EXECUTE PROCEDURE action.copy_transit_is_unique();
4872
4873 CREATE CONSTRAINT TRIGGER reservation_transit_copy_is_unique_check
4874     AFTER INSERT ON action.reservation_transit_copy
4875     FOR EACH ROW EXECUTE PROCEDURE action.copy_transit_is_unique();
4876
4877 /*
4878 -- UNDO
4879 DROP TRIGGER transit_copy_is_unique_check ON action.transit_copy;
4880 DROP TRIGGER hold_transit_copy_is_unique_check ON action.hold_transit_copy;
4881 DROP TRIGGER reservation_transit_copy_is_unique_check ON action.reservation_transit_copy;
4882 DROP INDEX action.active_transit_for_copy;
4883 */
4884
4885
4886 COMMIT;
4887
4888 \qecho A unique constraint was applied to action.transit_copy.  This will
4889 \qecho only effect newly created transits.  Admins are encouraged to manually 
4890 \qecho remove any existing duplicate transits by applying values for cancel_time
4891 \qecho or dest_recv_time, or by deleting the offending transits. Below is a
4892 \qecho query to locate duplicate transits.  Note dupes may exist accross
4893 \qecho parent (action.transit_copy) and child tables (action.hold_transit_copy,
4894 \qecho action.reservation_transit_copy)
4895 \qecho 
4896 \qecho    WITH dupe_transits AS (
4897 \qecho        SELECT COUNT(*), target_copy FROM action.transit_copy
4898 \qecho        WHERE dest_recv_time IS NULL AND cancel_time IS NULL
4899 \qecho        GROUP BY 2 HAVING COUNT(*) > 1
4900 \qecho    ) SELECT atc.* 
4901 \qecho        FROM dupe_transits
4902 \qecho        JOIN action.transit_copy atc USING (target_copy)
4903 \qecho        WHERE dest_recv_time IS NULL AND cancel_time IS NULL;
4904 \qecho