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