Add srfsh script to call action.purge_circulations().
[working/Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / XXXX.purge_circulations_updates.sql
1 INSERT INTO config.global_flag (name, label)
2     VALUES (
3         'history.circ.retention_uses_last_finished',
4         oils_i18n_gettext(
5             'history.circ.retention_uses_last_finished',
6             'Historical Circulations use most recent xact_finish date instead of last circ''s.',
7             'cgf',
8             'label'
9         )
10     ),(
11         'history.circ.retention_age_is_min',
12         oils_i18n_gettext(
13             'history.circ.retention_age_is_min',
14             'Historical Circulations are kept for global retention age at a minimum, regardless of user preferences.',
15             'cgf',
16             'label'
17         )
18     );
19
20
21 -- Drop old variants
22 DROP FUNCTION IF EXISTS action.circ_chain(INTEGER);
23 DROP FUNCTION IF EXISTS action.summarize_circ_chain(INTEGER);
24
25 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id BIGINT ) RETURNS SETOF action.circulation AS $$
26 DECLARE
27     tmp_circ action.circulation%ROWTYPE;
28     circ_0 action.circulation%ROWTYPE;
29 BEGIN
30
31     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
32
33     IF tmp_circ IS NULL THEN
34         RETURN NEXT tmp_circ;
35     END IF;
36     circ_0 := tmp_circ;
37
38     -- find the front of the chain
39     WHILE TRUE LOOP
40         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
41         IF tmp_circ IS NULL THEN
42             EXIT;
43         END IF;
44         circ_0 := tmp_circ;
45     END LOOP;
46
47     -- now send the circs to the caller, oldest to newest
48     tmp_circ := circ_0;
49     WHILE TRUE LOOP
50         IF tmp_circ IS NULL THEN
51             EXIT;
52         END IF;
53         RETURN NEXT tmp_circ;
54         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
55     END LOOP;
56
57 END;
58 $$ LANGUAGE 'plpgsql';
59
60 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id BIGINT ) RETURNS action.circ_chain_summary AS $$
61
62 DECLARE
63
64     -- first circ in the chain
65     circ_0 action.circulation%ROWTYPE;
66
67     -- last circ in the chain
68     circ_n action.circulation%ROWTYPE;
69
70     -- circ chain under construction
71     chain action.circ_chain_summary;
72     tmp_circ action.circulation%ROWTYPE;
73
74 BEGIN
75     
76     chain.num_circs := 0;
77     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
78
79         IF chain.num_circs = 0 THEN
80             circ_0 := tmp_circ;
81         END IF;
82
83         chain.num_circs := chain.num_circs + 1;
84         circ_n := tmp_circ;
85     END LOOP;
86
87     chain.start_time := circ_0.xact_start;
88     chain.last_stop_fines := circ_n.stop_fines;
89     chain.last_stop_fines_time := circ_n.stop_fines_time;
90     chain.last_checkin_time := circ_n.checkin_time;
91     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
92     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
93     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
94
95     IF chain.num_circs > 1 THEN
96         chain.last_renewal_time := circ_n.xact_start;
97         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
98     END IF;
99
100     RETURN chain;
101
102 END;
103 $$ LANGUAGE 'plpgsql';
104
105 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
106 DECLARE
107     usr_keep_age    actor.usr_setting%ROWTYPE;
108     usr_keep_start  actor.usr_setting%ROWTYPE;
109     org_keep_age    INTERVAL;
110     org_use_last    BOOL = false;
111     org_age_is_min  BOOL = false;
112     org_keep_count  INT;
113
114     keep_age        INTERVAL;
115
116     target_acp      RECORD;
117     circ_chain_head action.circulation%ROWTYPE;
118     circ_chain_tail action.circulation%ROWTYPE;
119
120     count_purged    INT;
121     num_incomplete  INT;
122
123     last_finished   TIMESTAMP WITH TIME ZONE;
124 BEGIN
125
126     count_purged := 0;
127
128     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
129
130     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
131     IF org_keep_count IS NULL THEN
132         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
133     END IF;
134
135     SELECT enabled INTO org_use_last FROM config.global_flag WHERE name = 'history.circ.retention_uses_last_finished';
136     SELECT enabled INTO org_age_is_min FROM config.global_flag WHERE name = 'history.circ.retention_age_is_min';
137
138     -- First, find copies with more than keep_count non-renewal circs
139     FOR target_acp IN
140         SELECT  target_copy,
141                 COUNT(*) AS total_real_circs
142           FROM  action.circulation
143           WHERE parent_circ IS NULL
144                 AND xact_finish IS NOT NULL
145           GROUP BY target_copy
146           HAVING COUNT(*) > org_keep_count
147     LOOP
148         -- And, for those, select circs that are finished and older than keep_age
149         FOR circ_chain_head IN
150             -- For reference, the subquery uses a window function to order the circs newest to oldest and number them
151             -- The outer query then uses that information to skip the most recent set the library wants to keep
152             -- End result is we don't care what order they come out in, as they are all potentials for deletion.
153             SELECT ac.* FROM action.circulation ac JOIN (
154               SELECT  rank() OVER (ORDER BY xact_start DESC), ac.id
155                 FROM  action.circulation ac
156                 WHERE ac.target_copy = target_acp.target_copy
157                   AND ac.parent_circ IS NULL
158                 ORDER BY ac.xact_start ) ranked USING (id)
159                 WHERE ranked.rank > org_keep_count
160         LOOP
161
162             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
163             SELECT COUNT(CASE WHEN xact_finish IS NULL THEN 1 ELSE NULL END), MAX(xact_finish) INTO num_incomplete, last_finished FROM action.circ_chain(circ_chain_head.id);
164             CONTINUE WHEN circ_chain_tail.xact_finish IS NULL OR num_incomplete > 0;
165
166             IF NOT org_use_last THEN
167                 last_finished := circ_chain_tail.xact_finish;
168             END IF;
169
170             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
171             usr_keep_age.value := NULL;
172             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
173
174             usr_keep_start.value := NULL;
175             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
176
177             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
178                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
179                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
180                 ELSE
181                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
182                 END IF;
183             ELSIF usr_keep_start.value IS NOT NULL THEN
184                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
185             ELSE
186                 keep_age := COALESCE( org_keep_age, '2000 years'::INTERVAL );
187             END IF;
188
189             IF org_age_is_min THEN
190                 keep_age := GREATEST( keep_age, org_keep_age );
191             END IF;
192
193             CONTINUE WHEN AGE(NOW(), last_finished) < keep_age;
194
195             -- We've passed the purging tests, purge the circ chain starting at the end
196             -- A trigger should auto-purge the rest of the chain.
197             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
198
199             count_purged := count_purged + 1;
200
201         END LOOP;
202     END LOOP;
203
204     return count_purged;
205 END;
206 $func$ LANGUAGE PLPGSQL;
207