]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/999.functions.global.sql
In actor.usr_merge(): For each *_bucket table, transfer the buckets
[Evergreen.git] / Open-ILS / src / sql / Pg / 999.functions.global.sql
1 /*
2  * Copyright (C) 2008 Equinox Software, Inc.
3  * Bill Erickson <erickson@esilibrary.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  */
16
17 CREATE OR REPLACE FUNCTION actor.usr_merge_rows( table_name TEXT, col_name TEXT, src_usr INT, dest_usr INT ) RETURNS VOID AS $$
18 DECLARE
19     sel TEXT;
20     upd TEXT;
21     del TEXT;
22     cur_row RECORD;
23 BEGIN
24     sel := 'SELECT id::BIGINT FROM ' || table_name || ' WHERE ' || quote_ident(col_name) || ' = ' || quote_literal(src_usr);
25     upd := 'UPDATE ' || table_name || ' SET ' || quote_ident(col_name) || ' = ' || quote_literal(dest_usr) || ' WHERE id = ';
26     del := 'DELETE FROM ' || table_name || ' WHERE id = ';
27     FOR cur_row IN EXECUTE sel LOOP
28         BEGIN
29             --RAISE NOTICE 'Attempting to merge % %', table_name, cur_row.id;
30             EXECUTE upd || cur_row.id;
31         EXCEPTION WHEN unique_violation THEN
32             --RAISE NOTICE 'Deleting conflicting % %', table_name, cur_row.id;
33             EXECUTE del || cur_row.id;
34         END;
35     END LOOP;
36 END;
37 $$ LANGUAGE plpgsql;
38
39 COMMENT ON FUNCTION actor.usr_merge_rows(TEXT, TEXT, INT, INT) IS $$
40 /**
41  * Attempts to move each row of the specified table from src_user to dest_user.  
42  * Where conflicts exist, the conflicting "source" row is deleted.
43  */
44 $$;
45
46
47 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 $$
48 DECLARE
49         suffix TEXT;
50         bucket_row RECORD;
51 BEGIN
52
53     -- do some initial cleanup 
54     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
55     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
56     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
57
58     -- actor.*
59     IF del_cards THEN
60         DELETE FROM actor.card where usr = src_usr;
61     ELSE
62         IF deactivate_cards THEN
63             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
64         END IF;
65         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
66     END IF;
67
68
69     IF del_addrs THEN
70         DELETE FROM actor.usr_address WHERE usr = src_usr;
71     ELSE
72         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
73     END IF;
74
75     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
76     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
77     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
78     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
79     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
80
81     -- permission.*
82     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
83     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
84     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
85     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
86
87
88     -- container.*
89         
90         -- For each *_bucket table: transfer every bucket belonging to src_usr
91         -- into the custody of dest_usr.
92         --
93         -- In order to avoid colliding with an existing bucket owned by
94         -- the destination user, append the source user's id (in parenthesese)
95         -- to the name.  If you still get a collision, add successive
96         -- spaces to the name and keep trying until you succeed.
97         --
98         FOR bucket_row in
99                 SELECT id, name
100                 FROM   container.biblio_record_entry_bucket
101                 WHERE  owner = src_usr
102         LOOP
103                 suffix := ' (' || src_usr || ')';
104                 LOOP
105                         BEGIN
106                                 UPDATE  container.biblio_record_entry_bucket
107                                 SET     owner = dest_usr, name = name || suffix
108                                 WHERE   id = bucket_row.id;
109                         EXCEPTION WHEN unique_violation THEN
110                                 suffix := suffix || ' ';
111                                 CONTINUE;
112                         END;
113                         EXIT;
114                 END LOOP;
115         END LOOP;
116
117         FOR bucket_row in
118                 SELECT id, name
119                 FROM   container.call_number_bucket
120                 WHERE  owner = src_usr
121         LOOP
122                 suffix := ' (' || src_usr || ')';
123                 LOOP
124                         BEGIN
125                                 UPDATE  container.call_number_bucket
126                                 SET     owner = dest_usr, name = name || suffix
127                                 WHERE   id = bucket_row.id;
128                         EXCEPTION WHEN unique_violation THEN
129                                 suffix := suffix || ' ';
130                                 CONTINUE;
131                         END;
132                         EXIT;
133                 END LOOP;
134         END LOOP;
135
136         FOR bucket_row in
137                 SELECT id, name
138                 FROM   container.copy_bucket
139                 WHERE  owner = src_usr
140         LOOP
141                 suffix := ' (' || src_usr || ')';
142                 LOOP
143                         BEGIN
144                                 UPDATE  container.copy_bucket
145                                 SET     owner = dest_usr, name = name || suffix
146                                 WHERE   id = bucket_row.id;
147                         EXCEPTION WHEN unique_violation THEN
148                                 suffix := suffix || ' ';
149                                 CONTINUE;
150                         END;
151                         EXIT;
152                 END LOOP;
153         END LOOP;
154
155         FOR bucket_row in
156                 SELECT id, name
157                 FROM   container.user_bucket
158                 WHERE  owner = src_usr
159         LOOP
160                 suffix := ' (' || src_usr || ')';
161                 LOOP
162                         BEGIN
163                                 UPDATE  container.user_bucket
164                                 SET     owner = dest_usr, name = name || suffix
165                                 WHERE   id = bucket_row.id;
166                         EXCEPTION WHEN unique_violation THEN
167                                 suffix := suffix || ' ';
168                                 CONTINUE;
169                         END;
170                         EXIT;
171                 END LOOP;
172         END LOOP;
173
174         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
175
176     -- vandelay.*
177     PERFORM actor.usr_merge_rows('vandelay.queue', 'owner', src_usr, dest_usr);
178
179     -- money.*
180     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
181     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
182     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
183     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
184     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
185
186     -- action.*
187     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
188     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
189     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
190
191     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
192     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
193     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
194     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
195
196     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
197     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
198     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
199     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
200     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
201
202     -- acq.*
203     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
204     PERFORM actor.usr_merge_rows('acq.picklist', 'owner', src_usr, dest_usr);
205     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
206     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
207     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
208     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
209     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
210     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
211
212     -- asset.*
213     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
214     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
215     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
216     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
217     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
218     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
219
220     -- serial.*
221     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
222     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
223
224     -- reporter.*
225     -- It's not uncommon to define the reporter schema in a replica 
226     -- DB only, so don't assume these tables exist in the write DB.
227     BEGIN
228         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
229     EXCEPTION WHEN undefined_table THEN
230         -- do nothing
231     END;
232     BEGIN
233         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
234     EXCEPTION WHEN undefined_table THEN
235         -- do nothing
236     END;
237     BEGIN
238         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
239     EXCEPTION WHEN undefined_table THEN
240         -- do nothing
241     END;
242     BEGIN
243         PERFORM actor.usr_merge_rows('reporter.template_folder', 'owner', src_usr, dest_usr);
244     EXCEPTION WHEN undefined_table THEN
245         -- do nothing
246     END;
247     BEGIN
248         PERFORM actor.usr_merge_rows('reporter.report_folder', 'owner', src_usr, dest_usr);
249     EXCEPTION WHEN undefined_table THEN
250         -- do nothing
251     END;
252     BEGIN
253         PERFORM actor.usr_merge_rows('reporter.output_folder', 'owner', src_usr, dest_usr);
254     EXCEPTION WHEN undefined_table THEN
255         -- do nothing
256     END;
257
258     -- Finally, delete the source user
259     DELETE FROM actor.usr WHERE id = src_usr;
260
261 END;
262 $$ LANGUAGE plpgsql;
263
264 COMMENT ON FUNCTION actor.usr_merge(INT, INT, BOOLEAN, BOOLEAN, BOOLEAN) IS $$
265 /**
266  * Merges all user date from src_usr to dest_usr.  When collisions occur, 
267  * keep dest_usr's data and delete src_usr's data.
268  */
269 $$;
270
271
272
273 CREATE OR REPLACE FUNCTION actor.approve_pending_address(pending_id INT) RETURNS BIGINT AS $$
274 DECLARE
275     old_id INT;
276 BEGIN
277     SELECT INTO old_id replaces FROM actor.usr_address where id = pending_id;
278     IF old_id IS NULL THEN
279         UPDATE actor.usr_address SET pending = 'f' WHERE id = pending_id;
280         RETURN pending_id;
281     END IF;
282     -- address replaces an existing address
283     DELETE FROM actor.usr_address WHERE id = -old_id;
284     UPDATE actor.usr_address SET id = -id WHERE id = old_id;
285     UPDATE actor.usr_address SET replaces = NULL, id = old_id, pending = 'f' WHERE id = pending_id;
286     RETURN old_id;
287 END
288 $$ LANGUAGE plpgsql;
289
290 COMMENT ON FUNCTION actor.approve_pending_address(INT) IS $$
291 /**
292  * Replaces an address with a pending address.  This is done by giving the pending 
293  * address the ID of the old address.  The replaced address is retained with -id.
294  */
295 $$;
296
297 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
298          ac_usr IN INTEGER
299 ) RETURNS VOID AS $$
300 --
301 -- Delete old circulation bucket items for a specified user.
302 -- "Old" means older than the interval specified by a
303 -- user-level setting, if it is so specified.
304 --
305 DECLARE
306     threshold TIMESTAMP WITH TIME ZONE;
307 BEGIN
308         -- Sanity check
309         IF ac_usr IS NULL THEN
310                 RETURN;
311         END IF;
312         -- Determine the threshold date that defines "old".  Subtract the
313         -- interval from the system date, then truncate to midnight.
314         SELECT
315                 date_trunc( 
316                         'day',
317                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
318                 )
319         INTO
320                 threshold
321         FROM
322                 actor.usr_setting
323         WHERE
324                 usr = ac_usr
325                 AND name = 'patron.max_reading_list_interval';
326         --
327         IF threshold is null THEN
328                 -- No interval defined; don't delete anything
329                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
330                 return;
331         END IF;
332         --
333         -- RAISE NOTICE 'Date threshold: %', threshold;
334         --
335         -- Threshold found; do the delete
336         delete from container.copy_bucket_item
337         where
338                 bucket in
339                 (
340                         select
341                                 id
342                         from
343                                 container.copy_bucket
344                         where
345                                 owner = ac_usr
346                                 and btype = 'circ_history'
347                 )
348                 and create_time < threshold;
349         --
350         RETURN;
351 END;
352 $$ LANGUAGE plpgsql;
353
354 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
355 /*
356  * Delete old circulation bucket items for a specified user.
357  * "Old" means older than the interval specified by a
358  * user-level setting, if it is so specified.
359 */
360 $$;
361
362 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
363 RETURNS VOID AS $$
364 --
365 -- Delete expired circulation bucket items for all users that have
366 -- a setting for patron.max_reading_list_interval.
367 --
368 DECLARE
369     today        TIMESTAMP WITH TIME ZONE;
370     threshold    TIMESTAMP WITH TIME ZONE;
371         usr_setting  RECORD;
372 BEGIN
373         SELECT date_trunc( 'day', now() ) INTO today;
374         --
375         FOR usr_setting in
376                 SELECT
377                         usr,
378                         value
379                 FROM
380                         actor.usr_setting
381                 WHERE
382                         name = 'patron.max_reading_list_interval'
383         LOOP
384                 --
385                 -- Make sure the setting is a valid interval
386                 --
387                 BEGIN
388                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
389                 EXCEPTION
390                         WHEN OTHERS THEN
391                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
392                                         usr_setting.usr, usr_setting.value;
393                                 CONTINUE;
394                 END;
395                 --
396                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
397                 --
398         DELETE FROM container.copy_bucket_item
399         WHERE
400                 bucket IN
401                 (
402                     SELECT
403                         id
404                     FROM
405                         container.copy_bucket
406                     WHERE
407                         owner = usr_setting.usr
408                         AND btype = 'circ_history'
409                 )
410                 AND create_time < threshold;
411         END LOOP;
412         --
413 END;
414 $$ LANGUAGE plpgsql;
415
416 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
417 /*
418  * Delete expired circulation bucket items for all users that have
419  * a setting for patron.max_reading_list_interval.
420 */
421 $$
422
423
424 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
425 DECLARE
426         moved_objects INT := 0;
427         source_cn     asset.call_number%ROWTYPE;
428         target_cn     asset.call_number%ROWTYPE;
429         metarec       metabib.metarecord%ROWTYPE;
430         hold          action.hold_request%ROWTYPE;
431         ser_rec       serial.record_entry%ROWTYPE;
432     uri_count     INT := 0;
433     counter       INT := 0;
434     uri_datafield TEXT;
435     uri_text      TEXT := '';
436 BEGIN
437
438     -- move any 856 entries on records that have at least one MARC-mapped URI entry
439     SELECT  INTO uri_count COUNT(*)
440       FROM  asset.uri_call_number_map m
441             JOIN asset.call_number cn ON (m.call_number = cn.id)
442       WHERE cn.record = source_record;
443
444     IF uri_count > 0 THEN
445         
446         SELECT  COUNT(*) INTO counter
447           FROM  xpath_table(
448                     'id',
449                     'marc',
450                     'acq.lineitem',
451                     '//*[@tag="856"]',
452                     'id=' || lineitem
453                 ) as t(i int,c text);
454     
455         FOR i IN 1 .. counter LOOP
456             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim" tag="856">' ||
457                         array_to_string(
458                             array_accum(
459                                 '<subfield code="' || subfield || '">' ||
460                                 regexp_replace(
461                                     regexp_replace(
462                                         regexp_replace(data,'&','&amp;','g'),
463                                         '>', '&gt;', 'g'
464                                     ),
465                                     '<', '&lt;', 'g'
466                                 ) || '</subfield>'
467                             ), ''
468                         ) || '</datafield>' INTO uri_datafield
469               FROM  xpath_table(
470                         'id',
471                         'marc',
472                         'biblio.record_entry',
473                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
474                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
475                         'id=' || source_record
476                     ) as t(id int,subfield text,data text);
477
478             uri_text := uri_text || uri_datafield;
479         END LOOP;
480
481         IF uri_text <> '' THEN
482             UPDATE  biblio.record_entry
483               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
484               WHERE id = target_record;
485         END IF;
486
487     END IF;
488
489         -- Find and move metarecords to the target record
490         SELECT  INTO metarec *
491           FROM  metabib.metarecord
492           WHERE master_record = source_record;
493
494         IF FOUND THEN
495                 UPDATE  metabib.metarecord
496                   SET   master_record = target_record,
497                         mods = NULL
498                   WHERE id = metarec.id;
499
500                 moved_objects := moved_objects + 1;
501         END IF;
502
503         -- Find call numbers attached to the source ...
504         FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
505
506                 SELECT  INTO target_cn *
507                   FROM  asset.call_number
508                   WHERE label = source_cn.label
509                         AND owning_lib = source_cn.owning_lib
510                         AND record = target_record;
511
512                 -- ... and if there's a conflicting one on the target ...
513                 IF FOUND THEN
514
515                         -- ... move the copies to that, and ...
516                         UPDATE  asset.copy
517                           SET   call_number = target_cn.id
518                           WHERE call_number = source_cn.id;
519
520                         -- ... move V holds to the move-target call number
521                         FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
522                 
523                                 UPDATE  action.hold_request
524                                   SET   target = target_cn.id
525                                   WHERE id = hold.id;
526                 
527                                 moved_objects := moved_objects + 1;
528                         END LOOP;
529
530                 -- ... if not ...
531                 ELSE
532                         -- ... just move the call number to the target record
533                         UPDATE  asset.call_number
534                           SET   record = target_record
535                           WHERE id = source_cn.id;
536                 END IF;
537
538                 moved_objects := moved_objects + 1;
539         END LOOP;
540
541         -- Find T holds targeting the source record ...
542         FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
543
544                 -- ... and move them to the target record
545                 UPDATE  action.hold_request
546                   SET   target = target_record
547                   WHERE id = hold.id;
548
549                 moved_objects := moved_objects + 1;
550         END LOOP;
551
552         -- Find serial records targeting the source record ...
553         FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
554                 -- ... and move them to the target record
555                 UPDATE  serial.record_entry
556                   SET   record = target_record
557                   WHERE id = ser_rec.id;
558
559                 moved_objects := moved_objects + 1;
560         END LOOP;
561
562     -- Finally, "delete" the source record
563     DELETE FROM biblio.record_entry WHERE id = source_record;
564
565         -- That's all, folks!
566         RETURN moved_objects;
567 END;
568 $func$ LANGUAGE plpgsql;
569