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