]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/reporter-schema.sql
LP#1709698: no longer require descriptions in report templates
[Evergreen.git] / Open-ILS / src / sql / Pg / reporter-schema.sql
1 /*
2  * Copyright (C) 2004-2008  Georgia Public Library Service
3  * Copyright (C) 2007-2008  Equinox Software, Inc.
4  * Mike Rylander <miker@esilibrary.com> 
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  */
17
18 DROP SCHEMA IF EXISTS reporter CASCADE;
19
20 BEGIN;
21
22 CREATE SCHEMA reporter;
23
24 CREATE TABLE reporter.template_folder (
25         id              SERIAL                          PRIMARY KEY,
26         parent          INT                             REFERENCES reporter.template_folder (id) DEFERRABLE INITIALLY DEFERRED,
27         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
28         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
29         name            TEXT                            NOT NULL,
30         shared          BOOL                            NOT NULL DEFAULT FALSE,
31         share_with      INT                             REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
32 );
33 CREATE INDEX rpt_tmpl_fldr_owner_idx ON reporter.template_folder (owner);
34 CREATE UNIQUE INDEX rpt_template_folder_once_parent_idx ON reporter.template_folder (name,parent);
35 CREATE UNIQUE INDEX rpt_template_folder_once_idx ON reporter.template_folder (name,owner) WHERE parent IS NULL;
36
37 CREATE TABLE reporter.report_folder (
38         id              SERIAL                          PRIMARY KEY,
39         parent          INT                             REFERENCES reporter.report_folder (id) DEFERRABLE INITIALLY DEFERRED,
40         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
41         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
42         name            TEXT                            NOT NULL,
43         shared          BOOL                            NOT NULL DEFAULT FALSE,
44         share_with      INT                             REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
45 );
46 CREATE INDEX rpt_rpt_fldr_owner_idx ON reporter.report_folder (owner);
47 CREATE UNIQUE INDEX rpt_report_folder_once_parent_idx ON reporter.report_folder (name,parent);
48 CREATE UNIQUE INDEX rpt_report_folder_once_idx ON reporter.report_folder (name,owner) WHERE parent IS NULL;
49
50 CREATE TABLE reporter.output_folder (
51         id              SERIAL                          PRIMARY KEY,
52         parent          INT                             REFERENCES reporter.output_folder (id) DEFERRABLE INITIALLY DEFERRED,
53         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
54         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
55         name            TEXT                            NOT NULL,
56         shared          BOOL                            NOT NULL DEFAULT FALSE,
57         share_with      INT                             REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
58 );
59 CREATE INDEX rpt_output_fldr_owner_idx ON reporter.output_folder (owner);
60 CREATE UNIQUE INDEX rpt_output_folder_once_parent_idx ON reporter.output_folder (name,parent);
61 CREATE UNIQUE INDEX rpt_output_folder_once_idx ON reporter.output_folder (name,owner) WHERE parent IS NULL;
62
63
64 CREATE TABLE reporter.template (
65         id              SERIAL                          PRIMARY KEY,
66         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
67         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
68         name            TEXT                            NOT NULL,
69         description     TEXT                            NOT NULL DEFAULT '',
70         data            TEXT                            NOT NULL,
71         folder          INT                             NOT NULL REFERENCES reporter.template_folder (id) DEFERRABLE INITIALLY DEFERRED
72 );
73 CREATE INDEX rpt_tmpl_owner_idx ON reporter.template (owner);
74 CREATE INDEX rpt_tmpl_fldr_idx ON reporter.template (folder);
75 CREATE UNIQUE INDEX rtp_template_folder_once_idx ON reporter.template (name,folder);
76
77 CREATE TABLE reporter.report (
78         id              SERIAL                          PRIMARY KEY,
79         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
80         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
81         name            TEXT                            NOT NULL DEFAULT '',
82         description     TEXT                            NOT NULL DEFAULT '',
83         template        INT                             NOT NULL REFERENCES reporter.template (id) DEFERRABLE INITIALLY DEFERRED,
84         data            TEXT                            NOT NULL,
85         folder          INT                             NOT NULL REFERENCES reporter.report_folder (id) DEFERRABLE INITIALLY DEFERRED,
86         recur           BOOL                            NOT NULL DEFAULT FALSE,
87         recurrence      INTERVAL
88 );
89 CREATE INDEX rpt_rpt_owner_idx ON reporter.report (owner);
90 CREATE INDEX rpt_rpt_fldr_idx ON reporter.report (folder);
91 CREATE UNIQUE INDEX rtp_report_folder_once_idx ON reporter.report (name,folder);
92
93 CREATE TABLE reporter.schedule (
94         id              SERIAL                          PRIMARY KEY,
95         report          INT                             NOT NULL REFERENCES reporter.report (id) DEFERRABLE INITIALLY DEFERRED,
96         folder          INT                             NOT NULL REFERENCES reporter.output_folder (id) DEFERRABLE INITIALLY DEFERRED,
97         runner          INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
98         run_time        TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
99         start_time      TIMESTAMP WITH TIME ZONE,
100         complete_time   TIMESTAMP WITH TIME ZONE,
101         email           TEXT,
102         excel_format    BOOL                            NOT NULL DEFAULT TRUE,
103         html_format     BOOL                            NOT NULL DEFAULT TRUE,
104         csv_format      BOOL                            NOT NULL DEFAULT TRUE,
105         chart_pie       BOOL                            NOT NULL DEFAULT FALSE,
106         chart_bar       BOOL                            NOT NULL DEFAULT FALSE,
107         chart_line      BOOL                            NOT NULL DEFAULT FALSE,
108         error_code      INT,
109         error_text      TEXT
110 );
111 CREATE INDEX rpt_sched_runner_idx ON reporter.schedule (runner);
112 CREATE INDEX rpt_sched_folder_idx ON reporter.schedule (folder);
113
114 CREATE OR REPLACE VIEW reporter.simple_record AS
115 SELECT  r.id,
116         s.metarecord,
117         r.fingerprint,
118         r.quality,
119         r.tcn_source,
120         r.tcn_value,
121         title.value AS title,
122         uniform_title.value AS uniform_title,
123         author.value AS author,
124         publisher.value AS publisher,
125         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
126         series_title.value AS series_title,
127         series_statement.value AS series_statement,
128         summary.value AS summary,
129         ARRAY_AGG( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
130         ARRAY_AGG( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
131         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
132         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
133         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
134         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
135         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
136         ARRAY((SELECT value FROM metabib.full_rec WHERE tag = '856' AND subfield IN ('3','y','u') AND record = r.id ORDER BY CASE WHEN subfield IN ('3','y') THEN 0 ELSE 1 END)) AS external_uri
137   FROM  biblio.record_entry r
138         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
139         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
140         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
141         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
142         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND (publisher.tag = '260' OR (publisher.tag = '264' AND publisher.ind2 = '1')) AND publisher.subfield = 'b')
143         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND (pubdate.tag = '260' OR (pubdate.tag = '264' AND pubdate.ind2 = '1')) AND pubdate.subfield = 'c')
144         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
145         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
146         LEFT JOIN metabib.full_rec series_title ON (r.id = series_title.record AND series_title.tag IN ('830','440') AND series_title.subfield = 'a')
147         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
148         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
149   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
150
151 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
152 SELECT  r.id,
153     r.fingerprint,
154     r.quality,
155     r.tcn_source,
156     r.tcn_value,
157     evergreen.oils_json_to_text(d.title) AS title,
158     evergreen.oils_json_to_text(d.author) AS author,
159     evergreen.oils_json_to_text(d.publisher) AS publisher,
160     evergreen.oils_json_to_text(d.pubdate) AS pubdate,
161     CASE WHEN d.isbn = 'null'
162         THEN NULL
163         ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.isbn::JSON)))
164     END AS isbn,
165     CASE WHEN d.issn = 'null'
166         THEN NULL
167         ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.issn::JSON)))
168     END AS issn
169   FROM  biblio.record_entry r
170         JOIN metabib.wide_display_entry d ON (r.id = d.source);
171
172 CREATE TABLE reporter.materialized_simple_record AS SELECT * FROM reporter.old_super_simple_record WHERE 1=0;
173 ALTER TABLE reporter.materialized_simple_record ADD PRIMARY KEY (id);
174
175 CREATE VIEW reporter.super_simple_record AS SELECT * FROM reporter.materialized_simple_record;
176
177 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT, deleted BOOL) RETURNS BOOL AS $$
178 BEGIN
179
180     DELETE FROM reporter.materialized_simple_record WHERE id = r_id;
181
182     IF NOT deleted THEN
183         INSERT INTO reporter.materialized_simple_record SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record WHERE id = r_id;
184     END IF;
185
186     RETURN TRUE;
187
188 END;
189 $$ LANGUAGE PLPGSQL;
190
191 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT) RETURNS BOOL AS $$
192     SELECT reporter.simple_rec_update($1, FALSE);
193 $$ LANGUAGE SQL;
194
195 CREATE OR REPLACE FUNCTION reporter.simple_rec_delete (r_id BIGINT) RETURNS BOOL AS $$
196     SELECT reporter.simple_rec_update($1, TRUE);
197 $$ LANGUAGE SQL;
198
199 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
200 BEGIN
201     IF TG_OP = 'DELETE' THEN
202         PERFORM reporter.simple_rec_delete(NEW.id);
203     ELSE
204         PERFORM reporter.simple_rec_update(NEW.id);
205     END IF;
206
207     RETURN NEW;
208 END;
209 $func$ LANGUAGE PLPGSQL;
210
211 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
212     DROP TRIGGER IF EXISTS bbb_simple_rec_trigger ON biblio.record_entry;
213 $$ LANGUAGE SQL;
214
215 CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$
216
217     TRUNCATE TABLE reporter.materialized_simple_record;
218
219     INSERT INTO reporter.materialized_simple_record
220         (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn)
221         SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record;
222
223     CREATE TRIGGER bbb_simple_rec_trigger
224         AFTER INSERT OR UPDATE OR DELETE ON biblio.record_entry
225         FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger();
226
227 $$ LANGUAGE SQL;
228
229 CREATE OR REPLACE FUNCTION reporter.refresh_materialized_simple_record () RETURNS VOID AS $$
230     SELECT reporter.disable_materialized_simple_record_trigger();
231     SELECT reporter.enable_materialized_simple_record_trigger();
232 $$ LANGUAGE SQL;
233
234 CREATE OR REPLACE VIEW reporter.demographic AS
235 SELECT  u.id,
236         u.dob,
237         CASE
238                 WHEN u.dob IS NULL
239                         THEN 'Adult'
240                 WHEN AGE(u.dob) > '18 years'::INTERVAL
241                         THEN 'Adult'
242                 ELSE 'Juvenile'
243         END AS general_division
244   FROM  actor.usr u;
245
246 CREATE OR REPLACE VIEW reporter.circ_type AS
247 SELECT  id,
248         CASE WHEN opac_renewal OR phone_renewal OR desk_renewal
249                 THEN 'RENEWAL'
250                 ELSE 'CHECKOUT'
251         END AS "type"
252   FROM  action.circulation;
253
254 -- rhrr needs to be a real table, so it can be fast. To that end, we use
255 -- a materialized view updated via a trigger.
256 CREATE TABLE reporter.hold_request_record  AS
257 SELECT  id,
258         target,
259         hold_type,
260         CASE
261                 WHEN hold_type = 'T'
262                         THEN target
263                 WHEN hold_type = 'I'
264                         THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
265                 WHEN hold_type = 'V'
266                         THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
267                 WHEN hold_type IN ('C','R','F')
268                         THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
269                 WHEN hold_type = 'M'
270                         THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
271                 WHEN hold_type = 'P'
272                         THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target)
273         END AS bib_record
274   FROM  action.hold_request ahr;
275
276 CREATE UNIQUE INDEX reporter_hold_request_record_pkey_idx ON reporter.hold_request_record (id);
277 CREATE INDEX reporter_hold_request_record_bib_record_idx ON reporter.hold_request_record (bib_record);
278
279 ALTER TABLE reporter.hold_request_record ADD PRIMARY KEY USING INDEX reporter_hold_request_record_pkey_idx;
280
281 CREATE OR REPLACE FUNCTION reporter.hold_request_record_mapper () RETURNS TRIGGER AS $$
282 BEGIN
283     IF TG_OP = 'INSERT' THEN
284         INSERT INTO reporter.hold_request_record (id, target, hold_type, bib_record)
285         SELECT  NEW.id,
286                 NEW.target,
287                 NEW.hold_type,
288                 CASE
289                     WHEN NEW.hold_type = 'T'
290                         THEN NEW.target
291                     WHEN NEW.hold_type = 'I'
292                         THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
293                     WHEN NEW.hold_type = 'V'
294                         THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
295                     WHEN NEW.hold_type IN ('C','R','F')
296                         THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
297                     WHEN NEW.hold_type = 'M'
298                         THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
299                     WHEN NEW.hold_type = 'P'
300                         THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
301                 END AS bib_record;
302     ELSIF TG_OP = 'UPDATE' AND (OLD.target <> NEW.target OR OLD.hold_type <> NEW.hold_type) THEN
303         UPDATE  reporter.hold_request_record
304           SET   target = NEW.target,
305                 hold_type = NEW.hold_type,
306                 bib_record = CASE
307                     WHEN NEW.hold_type = 'T'
308                         THEN NEW.target
309                     WHEN NEW.hold_type = 'I'
310                         THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
311                     WHEN NEW.hold_type = 'V'
312                         THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
313                     WHEN NEW.hold_type IN ('C','R','F')
314                         THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
315                     WHEN NEW.hold_type = 'M'
316                         THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
317                     WHEN NEW.hold_type = 'P'
318                         THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
319                 END
320          WHERE  id = NEW.id;
321     END IF;
322     RETURN NEW;
323 END;
324 $$ LANGUAGE PLPGSQL;
325
326 CREATE TRIGGER reporter_hold_request_record_trigger AFTER INSERT OR UPDATE ON action.hold_request
327     FOR EACH ROW EXECUTE PROCEDURE reporter.hold_request_record_mapper();
328
329 CREATE OR REPLACE VIEW reporter.xact_billing_totals AS
330 SELECT  b.xact,
331         SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided,
332         SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided,
333         SUM( amount ) as total
334   FROM  money.billing b
335   GROUP BY 1;
336
337 CREATE OR REPLACE VIEW reporter.xact_paid_totals AS
338 SELECT  b.xact,
339         SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided,
340         SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided,
341         SUM( amount ) as total
342   FROM  money.payment b
343   GROUP BY 1;
344
345 CREATE OR REPLACE VIEW reporter.overdue_circs AS
346 SELECT  *
347   FROM  "action".circulation
348   WHERE checkin_time is null
349         AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
350         AND due_date < now();
351
352 CREATE OR REPLACE VIEW reporter.overdue_reports AS
353  SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
354    FROM reporter.schedule s
355    JOIN reporter.report r ON r.id = s.report
356    JOIN actor.usr u ON s.runner = u.id
357    JOIN actor.card c ON c.id = u.card
358   WHERE s.start_time IS NULL AND s.run_time < now();
359
360 CREATE OR REPLACE VIEW reporter.pending_reports AS
361  SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
362    FROM reporter.schedule s
363    JOIN reporter.report r ON r.id = s.report
364    JOIN actor.usr u ON s.runner = u.id
365    JOIN actor.card c ON c.id = u.card
366   WHERE s.start_time IS NULL;
367
368 CREATE OR REPLACE VIEW reporter.currently_running AS
369  SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
370    FROM reporter.schedule s
371    JOIN reporter.report r ON r.id = s.report
372    JOIN actor.usr u ON s.runner = u.id
373    JOIN actor.card c ON c.id = u.card
374   WHERE s.start_time IS NOT NULL AND s.complete_time IS NULL;
375
376 COMMIT;
377