2 * Copyright (C) 2004-2008 Georgia Public Library Service
3 * Copyright (C) 2007-2008 Equinox Software, Inc.
4 * Mike Rylander <miker@esilibrary.com>
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.
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.
18 DROP SCHEMA IF EXISTS reporter CASCADE;
22 CREATE SCHEMA reporter;
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(),
30 shared BOOL NOT NULL DEFAULT FALSE,
31 simple_reporter BOOL NOT NULL DEFAULT FALSE,
32 share_with INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
34 CREATE INDEX rpt_tmpl_fldr_owner_idx ON reporter.template_folder (owner);
35 CREATE UNIQUE INDEX rpt_template_folder_once_parent_idx ON reporter.template_folder (name,parent);
36 CREATE UNIQUE INDEX rpt_template_folder_once_idx ON reporter.template_folder (name,owner,simple_reporter) WHERE parent IS NULL;
38 CREATE TABLE reporter.report_folder (
39 id SERIAL PRIMARY KEY,
40 parent INT REFERENCES reporter.report_folder (id) DEFERRABLE INITIALLY DEFERRED,
41 owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
42 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
44 shared BOOL NOT NULL DEFAULT FALSE,
45 simple_reporter BOOL NOT NULL DEFAULT FALSE,
46 share_with INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
48 CREATE INDEX rpt_rpt_fldr_owner_idx ON reporter.report_folder (owner);
49 CREATE UNIQUE INDEX rpt_report_folder_once_parent_idx ON reporter.report_folder (name,parent);
50 CREATE UNIQUE INDEX rpt_report_folder_once_idx ON reporter.report_folder (name,owner,simple_reporter) WHERE parent IS NULL;
52 CREATE TABLE reporter.output_folder (
53 id SERIAL PRIMARY KEY,
54 parent INT REFERENCES reporter.output_folder (id) DEFERRABLE INITIALLY DEFERRED,
55 owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
56 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
58 shared BOOL NOT NULL DEFAULT FALSE,
59 simple_reporter BOOL NOT NULL DEFAULT FALSE,
60 share_with INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
62 CREATE INDEX rpt_output_fldr_owner_idx ON reporter.output_folder (owner);
63 CREATE UNIQUE INDEX rpt_output_folder_once_parent_idx ON reporter.output_folder (name,parent);
64 CREATE UNIQUE INDEX rpt_output_folder_once_idx ON reporter.output_folder (name,owner,simple_reporter) WHERE parent IS NULL;
67 CREATE TABLE reporter.template (
68 id SERIAL PRIMARY KEY,
69 owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
70 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
72 description TEXT NOT NULL DEFAULT '',
74 folder INT NOT NULL REFERENCES reporter.template_folder (id) DEFERRABLE INITIALLY DEFERRED
76 CREATE INDEX rpt_tmpl_owner_idx ON reporter.template (owner);
77 CREATE INDEX rpt_tmpl_fldr_idx ON reporter.template (folder);
78 CREATE UNIQUE INDEX rtp_template_folder_once_idx ON reporter.template (name,folder);
80 CREATE TABLE reporter.report (
81 id SERIAL PRIMARY KEY,
82 owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
83 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
84 name TEXT NOT NULL DEFAULT '',
85 description TEXT NOT NULL DEFAULT '',
86 template INT NOT NULL REFERENCES reporter.template (id) DEFERRABLE INITIALLY DEFERRED,
88 folder INT NOT NULL REFERENCES reporter.report_folder (id) DEFERRABLE INITIALLY DEFERRED,
89 recur BOOL NOT NULL DEFAULT FALSE,
92 CREATE INDEX rpt_rpt_owner_idx ON reporter.report (owner);
93 CREATE INDEX rpt_rpt_fldr_idx ON reporter.report (folder);
94 CREATE UNIQUE INDEX rtp_report_folder_once_idx ON reporter.report (name,folder);
96 CREATE TABLE reporter.schedule (
97 id SERIAL PRIMARY KEY,
98 report INT NOT NULL REFERENCES reporter.report (id) DEFERRABLE INITIALLY DEFERRED,
99 folder INT NOT NULL REFERENCES reporter.output_folder (id) DEFERRABLE INITIALLY DEFERRED,
100 runner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
101 run_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
102 start_time TIMESTAMP WITH TIME ZONE,
103 complete_time TIMESTAMP WITH TIME ZONE,
105 excel_format BOOL NOT NULL DEFAULT TRUE,
106 html_format BOOL NOT NULL DEFAULT TRUE,
107 csv_format BOOL NOT NULL DEFAULT TRUE,
108 chart_pie BOOL NOT NULL DEFAULT FALSE,
109 chart_bar BOOL NOT NULL DEFAULT FALSE,
110 chart_line BOOL NOT NULL DEFAULT FALSE,
114 CREATE INDEX rpt_sched_runner_idx ON reporter.schedule (runner);
115 CREATE INDEX rpt_sched_folder_idx ON reporter.schedule (folder);
116 CREATE UNIQUE INDEX rpt_sched_recurrence_once_idx ON reporter.schedule (report,folder,runner,run_time,COALESCE(email,''));
118 CREATE OR REPLACE VIEW reporter.simple_record AS
125 title.value AS title,
126 uniform_title.value AS uniform_title,
127 author.value AS author,
128 publisher.value AS publisher,
129 SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
130 series_title.value AS series_title,
131 series_statement.value AS series_statement,
132 summary.value AS summary,
133 ARRAY_AGG( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
134 ARRAY_AGG( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
135 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
136 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
137 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
138 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
139 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
140 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
141 FROM biblio.record_entry r
142 JOIN metabib.metarecord_source_map s ON (s.source = r.id)
143 LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
144 LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
145 LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
146 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')
147 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')
148 LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
149 LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
150 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')
151 LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
152 LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
153 GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
155 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
161 evergreen.oils_json_to_text(d.title) AS title,
162 evergreen.oils_json_to_text(d.author) AS author,
163 evergreen.oils_json_to_text(d.publisher) AS publisher,
164 evergreen.oils_json_to_text(d.pubdate) AS pubdate,
165 CASE WHEN d.isbn = 'null'
167 ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.isbn::JSON)))
169 CASE WHEN d.issn = 'null'
171 ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.issn::JSON)))
173 FROM biblio.record_entry r
174 JOIN metabib.wide_display_entry d ON (r.id = d.source);
176 CREATE TABLE reporter.materialized_simple_record AS SELECT * FROM reporter.old_super_simple_record WHERE 1=0;
177 ALTER TABLE reporter.materialized_simple_record ADD PRIMARY KEY (id);
179 CREATE VIEW reporter.super_simple_record AS SELECT * FROM reporter.materialized_simple_record;
181 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT, deleted BOOL) RETURNS BOOL AS $$
184 DELETE FROM reporter.materialized_simple_record WHERE id = r_id;
187 INSERT INTO reporter.materialized_simple_record SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record WHERE id = r_id;
195 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT) RETURNS BOOL AS $$
196 SELECT reporter.simple_rec_update($1, FALSE);
199 CREATE OR REPLACE FUNCTION reporter.simple_rec_delete (r_id BIGINT) RETURNS BOOL AS $$
200 SELECT reporter.simple_rec_update($1, TRUE);
203 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
205 IF TG_OP = 'DELETE' THEN
206 PERFORM reporter.simple_rec_delete(NEW.id);
208 PERFORM reporter.simple_rec_update(NEW.id);
213 $func$ LANGUAGE PLPGSQL;
215 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
216 DROP TRIGGER IF EXISTS bbb_simple_rec_trigger ON biblio.record_entry;
219 CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$
221 TRUNCATE TABLE reporter.materialized_simple_record;
223 INSERT INTO reporter.materialized_simple_record
224 (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn)
225 SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record;
227 CREATE TRIGGER bbb_simple_rec_trigger
228 AFTER INSERT OR UPDATE OR DELETE ON biblio.record_entry
229 FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger();
233 CREATE OR REPLACE FUNCTION reporter.refresh_materialized_simple_record () RETURNS VOID AS $$
234 SELECT reporter.disable_materialized_simple_record_trigger();
235 SELECT reporter.enable_materialized_simple_record_trigger();
238 CREATE OR REPLACE VIEW reporter.asset_call_number_dewey AS
239 SELECT id AS call_number,
240 call_number_dewey(label) AS dewey,
241 CASE WHEN call_number_dewey(label) ~ '^[0-9]+\.?[0-9]*$'::text
242 THEN btrim(to_char(10::double precision * floor(call_number_dewey(label)::double precision / 10::double precision), '000'::text))
244 END AS dewey_block_tens,
245 CASE WHEN call_number_dewey(label) ~ '^[0-9]+\.?[0-9]*$'::text
246 THEN btrim(to_char(100::double precision * floor(call_number_dewey(label)::double precision / 100::double precision), '000'::text))
248 END AS dewey_block_hundreds,
249 CASE WHEN call_number_dewey(label) ~ '^[0-9]+\.?[0-9]*$'::text
250 THEN (btrim(to_char(10::double precision * floor(call_number_dewey(label)::double precision / 10::double precision), '000'::text)) || '-'::text)
251 || btrim(to_char(10::double precision * floor(call_number_dewey(label)::double precision / 10::double precision) + 9::double precision, '000'::text))
253 END AS dewey_range_tens,
254 CASE WHEN call_number_dewey(label) ~ '^[0-9]+\.?[0-9]*$'::text
255 THEN (btrim(to_char(100::double precision * floor(call_number_dewey(label)::double precision / 100::double precision), '000'::text)) || '-'::text)
256 || btrim(to_char(100::double precision * floor(call_number_dewey(label)::double precision / 100::double precision) + 99::double precision, '000'::text))
258 END AS dewey_range_hundreds
259 FROM asset.call_number
260 WHERE call_number_dewey(label) ~ '^[0-9]'::text;
262 CREATE OR REPLACE VIEW reporter.demographic AS
268 WHEN AGE(u.dob) > '18 years'::INTERVAL
271 END AS general_division,
274 THEN 'No Date of Birth Entered'::text
275 WHEN age(u.dob::timestamp with time zone) >= '0 years'::interval and age(u.dob::timestamp with time zone) < '6 years'::interval
276 THEN 'Child 0-5 Years Old'::text
277 WHEN age(u.dob::timestamp with time zone) >= '6 years'::interval and age(u.dob::timestamp with time zone) < '13 years'::interval
278 THEN 'Child 6-12 Years Old'::text
279 WHEN age(u.dob::timestamp with time zone) >= '13 years'::interval and age(u.dob::timestamp with time zone) < '18 years'::interval
280 THEN 'Teen 13-17 Years Old'::text
281 WHEN age(u.dob::timestamp with time zone) >= '18 years'::interval and age(u.dob::timestamp with time zone) < '26 years'::interval
282 THEN 'Adult 18-25 Years Old'::text
283 WHEN age(u.dob::timestamp with time zone) >= '26 years'::interval and age(u.dob::timestamp with time zone) < '50 years'::interval
284 THEN 'Adult 26-49 Years Old'::text
285 WHEN age(u.dob::timestamp with time zone) >= '50 years'::interval and age(u.dob::timestamp with time zone) < '60 years'::interval
286 THEN 'Adult 50-59 Years Old'::text
287 WHEN age(u.dob::timestamp with time zone) >= '60 years'::interval and age(u.dob::timestamp with time zone) < '70 years'::interval
288 THEN 'Adult 60-69 Years Old'::text
289 WHEN age(u.dob::timestamp with time zone) >= '70 years'::interval
290 THEN 'Adult 70+'::text
295 CREATE OR REPLACE VIEW reporter.circ_type AS
297 CASE WHEN opac_renewal OR phone_renewal OR desk_renewal OR auto_renewal
301 FROM action.circulation;
303 -- rhrr needs to be a real table, so it can be fast. To that end, we use
304 -- a materialized view updated via a trigger.
305 CREATE TABLE reporter.hold_request_record AS
313 THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
315 THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
316 WHEN hold_type IN ('C','R','F')
317 THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
319 THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
321 THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target)
323 FROM action.hold_request ahr;
325 CREATE UNIQUE INDEX reporter_hold_request_record_pkey_idx ON reporter.hold_request_record (id);
326 CREATE INDEX reporter_hold_request_record_bib_record_idx ON reporter.hold_request_record (bib_record);
328 ALTER TABLE reporter.hold_request_record ADD PRIMARY KEY USING INDEX reporter_hold_request_record_pkey_idx;
330 CREATE OR REPLACE FUNCTION reporter.hold_request_record_mapper () RETURNS TRIGGER AS $$
332 IF TG_OP = 'INSERT' THEN
333 INSERT INTO reporter.hold_request_record (id, target, hold_type, bib_record)
338 WHEN NEW.hold_type = 'T'
340 WHEN NEW.hold_type = 'I'
341 THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
342 WHEN NEW.hold_type = 'V'
343 THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
344 WHEN NEW.hold_type IN ('C','R','F')
345 THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
346 WHEN NEW.hold_type = 'M'
347 THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
348 WHEN NEW.hold_type = 'P'
349 THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
351 ELSIF TG_OP = 'UPDATE' AND (OLD.target <> NEW.target OR OLD.hold_type <> NEW.hold_type) THEN
352 UPDATE reporter.hold_request_record
353 SET target = NEW.target,
354 hold_type = NEW.hold_type,
356 WHEN NEW.hold_type = 'T'
358 WHEN NEW.hold_type = 'I'
359 THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
360 WHEN NEW.hold_type = 'V'
361 THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
362 WHEN NEW.hold_type IN ('C','R','F')
363 THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
364 WHEN NEW.hold_type = 'M'
365 THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
366 WHEN NEW.hold_type = 'P'
367 THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
375 CREATE TRIGGER reporter_hold_request_record_trigger AFTER INSERT OR UPDATE ON action.hold_request
376 FOR EACH ROW EXECUTE PROCEDURE reporter.hold_request_record_mapper();
378 CREATE OR REPLACE VIEW reporter.xact_billing_totals AS
380 SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided,
381 SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided,
382 SUM( amount ) as total
386 CREATE OR REPLACE VIEW reporter.xact_paid_totals AS
388 SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided,
389 SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided,
390 SUM( amount ) as total
394 CREATE OR REPLACE VIEW reporter.overdue_circs AS
396 FROM "action".circulation
397 WHERE checkin_time is null
398 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
399 AND due_date < now();
401 CREATE OR REPLACE VIEW reporter.overdue_reports AS
402 SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
403 FROM reporter.schedule s
404 JOIN reporter.report r ON r.id = s.report
405 JOIN actor.usr u ON s.runner = u.id
406 JOIN actor.card c ON c.id = u.card
407 WHERE s.start_time IS NULL AND s.run_time < now();
409 CREATE OR REPLACE VIEW reporter.pending_reports AS
410 SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
411 FROM reporter.schedule s
412 JOIN reporter.report r ON r.id = s.report
413 JOIN actor.usr u ON s.runner = u.id
414 JOIN actor.card c ON c.id = u.card
415 WHERE s.start_time IS NULL;
417 CREATE OR REPLACE VIEW reporter.currently_running AS
418 SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
419 FROM reporter.schedule s
420 JOIN reporter.report r ON r.id = s.report
421 JOIN actor.usr u ON s.runner = u.id
422 JOIN actor.card c ON c.id = u.card
423 WHERE s.start_time IS NOT NULL AND s.complete_time IS NULL;
425 CREATE OR REPLACE VIEW reporter.completed_reports AS
429 t.owner AS template_owner,
430 r.owner AS report_owner,
432 t.folder AS template_folder,
433 r.folder AS report_folder,
434 s.folder AS output_folder,
435 r.name AS report_name,
436 t.name AS template_name,
442 FROM reporter.schedule s
443 JOIN reporter.report r ON r.id = s.report
444 JOIN reporter.template t ON t.id = r.template
445 WHERE s.complete_time IS NOT NULL;
447 CREATE OR REPLACE FUNCTION reporter.intersect_user_perm_ou(context_ou BIGINT, staff_id BIGINT, perm_code TEXT)
448 RETURNS BOOLEAN AS $$
449 SELECT CASE WHEN context_ou IN (SELECT * FROM permission.usr_has_perm_at_all(staff_id::INT, perm_code)) THEN TRUE ELSE FALSE END;